class CloudEvents::ContentType
A parsed content-type header.
This object represents the information contained in a Content-Type, obtained by parsing the header according to RFC 2045.
Case-insensitive fields, such as media_type
and subtype, are normalized to lower case.
If parsing fails, this class will try to get as much information as it can, and fill the rest with defaults as recommended in RFC 2045 sec 5.2. In case of a parsing error, the {#error_message} field will be set.
This object is immutable, and Ractor-shareable on Ruby 3.
Attributes
A “canonical” header content string with spacing and capitalization normalized. @return [String]
The charset, defaulting to “us-ascii” if none is explicitly set. @return [String]
The error message when parsing, or `nil` if there was no error message. @return [String,nil]
The media type. @return [String]
An array of parameters, each element as a two-element array of the parameter name and value. @return [Array<Array(String,String)>]
The original header content string. @return [String]
The entire content subtype (which could include an extension delimited by a plus sign). @return [String]
The portion of the content subtype before any plus sign. @return [String]
The portion of the content subtype after any plus sign, or nil if there is no plus sign in the subtype. @return [String,nil]
The original header content string. @return [String]
Public Class Methods
Parse the given header value.
@param string [String] Content-Type header value in RFC 2045 format @param default_charset [String] Optional. The charset to use if none is
specified. Defaults to `us-ascii`.
# File lib/cloud_events/content_type.rb, line 27 def initialize string, default_charset: nil @string = string.to_s @media_type = "text" @subtype_base = @subtype = "plain" @subtype_format = nil @params = [] @charset = default_charset || "us-ascii" @error_message = nil parse consume_comments string.strip @canonical_string = "#{@media_type}/#{@subtype}" + @params.map { |k, v| "; #{k}=#{maybe_quote v}" }.join full_freeze end
Public Instance Methods
@private
# File lib/cloud_events/content_type.rb, line 111 def == other other.is_a?(ContentType) && canonical_string == other.canonical_string end
@private
# File lib/cloud_events/content_type.rb, line 117 def hash canonical_string.hash end
An array of values for the given parameter name @param key [String] @return [Array<String>]
# File lib/cloud_events/content_type.rb, line 105 def param_values key key = key.downcase @params.inject([]) { |a, (k, v)| key == k ? a << v : a } end
Private Instance Methods
# File lib/cloud_events/content_type.rb, line 184 def consume_comments str return str unless str.start_with? "(" index = 1 loop do char = str[index] case char when nil raise ParseError, "Comment never finished" when ")" break when "\\" index += 2 when "(" str = consume_comments str[index..-1] index = 0 else index += 1 end end index += 1 consume_comments str[index..-1].strip end
# File lib/cloud_events/content_type.rb, line 153 def consume_special str, expected, error_message: nil raise ParseError, error_message || "Expected #{expected.inspect}" unless str.start_with? expected consume_comments str[1..-1].strip end
# File lib/cloud_events/content_type.rb, line 144 def consume_token str, downcase: false, error_message: nil match = /^([\w!#$%&'*+.\^`{|}-]+)(.*)$/.match str raise ParseError, error_message || "Expected token" unless match token = match[1] token.downcase! if downcase str = consume_comments match[2].strip [token, str] end
# File lib/cloud_events/content_type.rb, line 158 def consume_token_or_quoted str, error_message: nil return consume_token str unless str.start_with? '"' arr = [] index = 1 loop do char = str[index] case char when nil raise ParseError, error_message || "Quoted-string never finished" when "\"" break when "\\" char = str[index + 1] raise ParseError, error_message || "Quoted-string never finished" unless char arr << char index += 2 else arr << char index += 1 end end index += 1 str = consume_comments str[index..-1].strip [arr.join, str] end
# File lib/cloud_events/content_type.rb, line 213 def full_freeze instance_variables.each do |iv| instance_variable_get(iv).freeze end @params.each do |name_val| name_val[0].freeze name_val[1].freeze name_val.freeze end freeze end
# File lib/cloud_events/content_type.rb, line 207 def maybe_quote str return str if /^[\w!#$%&'*+.\^`{|}-]+$/ =~ str str = str.gsub("\\", "\\\\\\\\").gsub("\"", "\\\\\"") "\"#{str}\"" end
# File lib/cloud_events/content_type.rb, line 127 def parse str @media_type, str = consume_token str, downcase: true, error_message: "Failed to parse media type" str = consume_special str, "/" @subtype, str = consume_token str, downcase: true, error_message: "Failed to parse subtype" @subtype_base, @subtype_format = @subtype.split "+", 2 until str.empty? str = consume_special str, ";" name, str = consume_token str, downcase: true, error_message: "Faled to parse attribute name" str = consume_special str, "=", error_message: "Failed to find value for attribute #{name}" val, str = consume_token_or_quoted str, error_message: "Failed to parse value for attribute #{name}" @params << [name, val] @charset = val if name == "charset" end rescue ParseError => e @error_message = e.message end