class Ur::ContentType

Ur::ContentType represents a Content-Type header field. it parses the media type and its components, as well as any parameters.

this class aims to be permissive in what it will parse. it will not raise any error when given a malformed or syntactically invalid Content-Type string. fields and parameters parsed from invalid Content-Type strings are undefined, but this class generally tries to make the most sense of what it's given.

this class is based on RFCs:

Constants

MEDIA_TYPE_REGEXP

the character ranges in this SHOULD be significantly more restrictive, and the /<subtype> construct should not be optional. however, we'll aim to match whatever media type we are given.

example:

MEDIA_TYPE_REGEXP.match('application/vnd.github+json').named_captures
=>
{
  "media_type" => "application/vnd.github+json",
  "type" => "application",
  "subtype" => "vnd.github+json",
  "facet" => "vnd",
  "suffix" => "json",
}

example of being more permissive than the spec allows:

MEDIA_TYPE_REGEXP.match('where the %$*! am I').named_captures
=>
{
  "media_type" => "where the %$*! am I",
  "type" => "where the %$*! am I",
  "subtype" => nil,
  "facet" => nil,
  "suffix" => nil
}
SOME_TEXT_SUBTYPES

Attributes

facet[R]

@return [String, nil] the 'facet' portion of our media type.

e.g. "vnd" in content-type: application/vnd.github+json; charset="utf-8"
media_type[R]

@return [String, nil] the media type of this content type.

e.g. "application/vnd.github+json" in content-type: application/vnd.github+json; charset="utf-8"
parameters[R]

@return [Hash<String, String>] parameters of this content type.

e.g. {"charset" => "utf-8"} in content-type: application/vnd.github+json; charset="utf-8"
subtype[R]

@return [String, nil] the 'subtype' portion of our media type.

e.g. "vnd.github+json" in content-type: application/vnd.github+json; charset="utf-8"
suffix[R]

@return [String, nil] the 'suffix' portion of our media type.

e.g. "json" in content-type: application/vnd.github+json; charset="utf-8"
type[R]

@return [String, nil] the 'type' portion of our media type.

e.g. "application" in content-type: application/vnd.github+json; charset="utf-8"

Public Class Methods

new(*a) click to toggle source
Calls superclass method
# File lib/ur/content_type.rb, line 68
def initialize(*a)
  super

  scanner = StringScanner.new(self)

  if scanner.scan(MEDIA_TYPE_REGEXP)
    @media_type = scanner[:media_type].strip.freeze if scanner[:media_type]
    @type      = scanner[:type].strip.freeze       if scanner[:type]
    @subtype  = scanner[:subtype].strip.freeze    if scanner[:subtype]
    @facet   = scanner[:facet].strip.freeze      if scanner[:facet]
    @suffix = scanner[:suffix].strip.freeze     if scanner[:suffix]
  end

  @parameters = Hash.new do |h, k|
    if k.respond_to?(:downcase) && k != k.downcase
      h[k.downcase]
    else
      nil
    end
  end

  while scanner.scan(/(;\s*)+/)
    key = scanner.scan(/[^;=\"]*/)
    if key && scanner.scan(/=/)
      value = String.new
      until scanner.eos? || scanner.check(/;/)
        if scanner.scan(/\s+/)
          ws = scanner[0]
          # discard trailing whitespace.
          # other whitespace isn't technically valid but we are permissive so we put it in the value.
          value << ws unless scanner.eos? || scanner.check(/;/)
        elsif scanner.scan(/"/)
          until scanner.eos? || scanner.scan(/"/)
            if scanner.scan(/\\/)
              value << scanner.getch unless scanner.eos?
            end
            value << scanner.scan(/[^\"\\]*/)
          end
        else
          value << scanner.scan(/[^\s;\"]*/)
        end
      end
      @parameters[key.downcase.freeze] = value.freeze
    end
  end

  @parameters.freeze

  freeze
end

Public Instance Methods

binary?(unknown: true) click to toggle source

@param unknown [Boolean] return this value when we have no idea whether

our media type is binary or text.

@return [Boolean] does this content type appear to be binary?

this library makes its best guess based on a very incomplete knowledge
of which media types indicate binary or text.
# File lib/ur/content_type.rb, line 181
def binary?(unknown: true)
  return false if type_text?

  SOME_TEXT_SUBTYPES.each do |cmpsubtype|
    return false if (suffix ? suffix.casecmp?(cmpsubtype) : subtype ? subtype.casecmp?(cmpsubtype) : false)
  end

  # these are generally binary
  return true if type_image? || type_audio? || type_video?

  # we're out of ideas
  return unknown
end
form_urlencoded?() click to toggle source

@return [Boolean] is this a x-www-form-urlencoded content type?

# File lib/ur/content_type.rb, line 206
def form_urlencoded?
  suffix ? suffix.casecmp?('x-www-form-urlencoded'): subtype ? subtype.casecmp?('x-www-form-urlencoded') : false
end
json?() click to toggle source

@return [Boolean] is this a JSON content type?

# File lib/ur/content_type.rb, line 196
def json?
  suffix ? suffix.casecmp?('json') : subtype ? subtype.casecmp?('json') : false
end
subtype?(other_subtype) click to toggle source

@param other_subtype @return [Boolean] is the 'subtype' portion of our media type equal (case-insensitive) to the given other_subtype

# File lib/ur/content_type.rb, line 151
def subtype?(other_subtype)
  subtype && subtype.casecmp?(other_subtype)
end
suffix?(other_suffix) click to toggle source

@param other_suffix @return [Boolean] is the 'suffix' portion of our media type equal (case-insensitive) to the given other_suffix

# File lib/ur/content_type.rb, line 157
def suffix?(other_suffix)
  suffix && suffix.casecmp?(other_suffix)
end
type?(other_type) click to toggle source

@param other_type @return [Boolean] is the 'type' portion of our media type equal (case-insensitive) to the given other_type

# File lib/ur/content_type.rb, line 145
def type?(other_type)
  type && type.casecmp?(other_type)
end
type_application?() click to toggle source

@return [Boolean] is the 'type' portion of our media type 'application'

# File lib/ur/content_type.rb, line 231
def type_application?
  type && type.casecmp?('application')
end
type_audio?() click to toggle source

@return [Boolean] is the 'type' portion of our media type 'audio'

# File lib/ur/content_type.rb, line 221
def type_audio?
  type && type.casecmp?('audio')
end
type_image?() click to toggle source

@return [Boolean] is the 'type' portion of our media type 'image'

# File lib/ur/content_type.rb, line 216
def type_image?
  type && type.casecmp?('image')
end
type_message?() click to toggle source

@return [Boolean] is the 'type' portion of our media type 'message'

# File lib/ur/content_type.rb, line 236
def type_message?
  type && type.casecmp?('message')
end
type_multipart?() click to toggle source

@return [Boolean] is the 'type' portion of our media type 'multipart'

# File lib/ur/content_type.rb, line 241
def type_multipart?
  type && type.casecmp?('multipart')
end
type_text?() click to toggle source

@return [Boolean] is the 'type' portion of our media type 'text'

# File lib/ur/content_type.rb, line 211
def type_text?
  type && type.casecmp?('text')
end
type_video?() click to toggle source

@return [Boolean] is the 'type' portion of our media type 'video'

# File lib/ur/content_type.rb, line 226
def type_video?
  type && type.casecmp?('video')
end
xml?() click to toggle source

@return [Boolean] is this an XML content type?

# File lib/ur/content_type.rb, line 201
def xml?
  suffix ? suffix.casecmp?('xml'): subtype ? subtype.casecmp?('xml') : false
end