diff options
Diffstat (limited to 'lib/mime/types.rb')
-rw-r--r-- | lib/mime/types.rb | 1164 |
1 files changed, 254 insertions, 910 deletions
diff --git a/lib/mime/types.rb b/lib/mime/types.rb index 40d299f71f..de7546413a 100644 --- a/lib/mime/types.rb +++ b/lib/mime/types.rb @@ -1,966 +1,310 @@ # -*- ruby encoding: utf-8 -*- -# The namespace for MIME applications, tools, and libraries. -module MIME - # Reflects a MIME Content-Type which is in invalid format (e.g., it isn't - # in the form of type/subtype). - class InvalidContentType < RuntimeError; end - - # The definition of one MIME content-type. - # - # == Usage - # require 'mime/types' - # - # plaintext = MIME::Types['text/plain'].first - # # returns [text/plain, text/plain] - # text = plaintext.first - # print text.media_type # => 'text' - # print text.sub_type # => 'plain' - # - # puts text.extensions.join(" ") # => 'asc txt c cc h hh cpp' - # - # puts text.encoding # => 8bit - # puts text.binary? # => false - # puts text.ascii? # => true - # puts text == 'text/plain' # => true - # puts MIME::Type.simplified('x-appl/x-zip') # => 'appl/zip' - # - # puts MIME::Types.any? { |type| - # type.content_type == 'text/plain' - # } # => true - # puts MIME::Types.all?(&:registered?) - # # => false - # - class Type - # The released version of Ruby MIME::Types - VERSION = '1.25' - - include Comparable - - MEDIA_TYPE_RE = %r{([-\w.+]+)/([-\w.+]*)}o - UNREG_RE = %r{[Xx]-}o - ENCODING_RE = %r{(?:base64|7bit|8bit|quoted\-printable)}o - PLATFORM_RE = %r|#{RUBY_PLATFORM}|o - - SIGNATURES = %w(application/pgp-keys application/pgp - application/pgp-signature application/pkcs10 - application/pkcs7-mime application/pkcs7-signature - text/vcard) - - IANA_URL = "http://www.iana.org/assignments/media-types/%s/%s" - RFC_URL = "http://rfc-editor.org/rfc/rfc%s.txt" - DRAFT_URL = "http://datatracker.ietf.org/public/idindex.cgi?command=id_details&filename=%s" - LTSW_URL = "http://www.ltsw.se/knbase/internet/%s.htp" - CONTACT_URL = "http://www.iana.org/assignments/contact-people.htm#%s" - - # Returns +true+ if the simplified type matches the current - def like?(other) - if other.respond_to?(:simplified) - @simplified == other.simplified - else - @simplified == Type.simplified(other) - end - end - - # Compares the MIME::Type against the exact content type or the - # simplified type (the simplified type will be used if comparing against - # something that can be treated as a String with #to_s). In comparisons, - # this is done against the lowercase version of the MIME::Type. - def <=>(other) - if other.respond_to?(:content_type) - @content_type.downcase <=> other.content_type.downcase - elsif other.respond_to?(:to_s) - @simplified <=> Type.simplified(other.to_s) - else - @content_type.downcase <=> other.downcase - end - end - - # Compares the MIME::Type based on how reliable it is before doing a - # normal <=> comparison. Used by MIME::Types#[] to sort types. The - # comparisons involved are: - # - # 1. self.simplified <=> other.simplified (ensures that we - # don't try to compare different types) - # 2. IANA-registered definitions < other definitions. - # 3. Generic definitions < platform definitions. - # 3. Complete definitions < incomplete definitions. - # 4. Current definitions < obsolete definitions. - # 5. Obselete with use-instead references < obsolete without. - # 6. Obsolete use-instead definitions are compared. - def priority_compare(other) - pc = simplified <=> other.simplified - - if pc.zero? - pc = if registered? != other.registered? - registered? ? -1 : 1 # registered < unregistered - elsif platform? != other.platform? - platform? ? 1 : -1 # generic < platform - elsif complete? != other.complete? - pc = complete? ? -1 : 1 # complete < incomplete - elsif obsolete? != other.obsolete? - pc = obsolete? ? 1 : -1 # current < obsolete - end - - if pc.zero? and obsolete? and (use_instead != other.use_instead) - pc = if use_instead.nil? - -1 - elsif other.use_instead.nil? - 1 - else - use_instead <=> other.use_instead - end - end - end - - pc - end - - # Returns +true+ if the other object is a MIME::Type and the content - # types match. - def eql?(other) - other.kind_of?(MIME::Type) and self == other - end - - # Returns the whole MIME content-type string. - # - # text/plain => text/plain - # x-chemical/x-pdb => x-chemical/x-pdb - attr_reader :content_type - # Returns the media type of the simplified MIME type. - # - # text/plain => text - # x-chemical/x-pdb => chemical - attr_reader :media_type - # Returns the media type of the unmodified MIME type. - # - # text/plain => text - # x-chemical/x-pdb => x-chemical - attr_reader :raw_media_type - # Returns the sub-type of the simplified MIME type. - # - # text/plain => plain - # x-chemical/x-pdb => pdb - attr_reader :sub_type - # Returns the media type of the unmodified MIME type. - # - # text/plain => plain - # x-chemical/x-pdb => x-pdb - attr_reader :raw_sub_type - # The MIME types main- and sub-label can both start with <tt>x-</tt>, - # which indicates that it is a non-registered name. Of course, after - # registration this flag can disappear, adds to the confusing - # proliferation of MIME types. The simplified string has the <tt>x-</tt> - # removed and are translated to lowercase. - # - # text/plain => text/plain - # x-chemical/x-pdb => chemical/pdb - attr_reader :simplified - # The list of extensions which are known to be used for this MIME::Type. - # Non-array values will be coerced into an array with #to_a. Array - # values will be flattened and +nil+ values removed. - attr_accessor :extensions - remove_method :extensions= ; - def extensions=(ext) #:nodoc: - @extensions = [ext].flatten.compact - end - - # The encoding (7bit, 8bit, quoted-printable, or base64) required to - # transport the data of this content type safely across a network, which - # roughly corresponds to Content-Transfer-Encoding. A value of +nil+ or - # <tt>:default</tt> will reset the #encoding to the #default_encoding - # for the MIME::Type. Raises ArgumentError if the encoding provided is - # invalid. - # - # If the encoding is not provided on construction, this will be either - # 'quoted-printable' (for text/* media types) and 'base64' for eveything - # else. - attr_accessor :encoding - remove_method :encoding= ; - def encoding=(enc) #:nodoc: - if enc.nil? or enc == :default - @encoding = self.default_encoding - elsif enc =~ ENCODING_RE - @encoding = enc - else - raise ArgumentError, "The encoding must be nil, :default, base64, 7bit, 8bit, or quoted-printable." - end - end - - # The regexp for the operating system that this MIME::Type is specific - # to. - attr_accessor :system - remove_method :system= ; - def system=(os) #:nodoc: - if os.nil? or os.kind_of?(Regexp) - @system = os - else - @system = %r|#{os}| - end - end - # Returns the default encoding for the MIME::Type based on the media - # type. - attr_reader :default_encoding - remove_method :default_encoding - def default_encoding - (@media_type == 'text') ? 'quoted-printable' : 'base64' - end - - # Returns the media type or types that should be used instead of this - # media type, if it is obsolete. If there is no replacement media type, - # or it is not obsolete, +nil+ will be returned. - attr_reader :use_instead - remove_method :use_instead - def use_instead - return nil unless @obsolete - @use_instead - end - - # Returns +true+ if the media type is obsolete. - def obsolete? - @obsolete ? true : false - end - # Sets the obsolescence indicator for this media type. - attr_writer :obsolete - - # The documentation for this MIME::Type. Documentation about media - # types will be found on a media type definition as a comment. - # Documentation will be found through #docs. - attr_accessor :docs - remove_method :docs= ; - def docs=(d) - if d - a = d.scan(%r{use-instead:#{MEDIA_TYPE_RE}}) - - if a.empty? - @use_instead = nil - else - @use_instead = a.map { |el| "#{el[0]}/#{el[1]}" } - end - end - @docs = d - end - - # The encoded URL list for this MIME::Type. See #urls for more - # information. - attr_accessor :url - # The decoded URL list for this MIME::Type. - # The special URL value IANA will be translated into: - # http://www.iana.org/assignments/media-types/<mediatype>/<subtype> - # - # The special URL value RFC### will be translated into: - # http://www.rfc-editor.org/rfc/rfc###.txt - # - # The special URL value DRAFT:name will be translated into: - # https://datatracker.ietf.org/public/idindex.cgi? - # command=id_detail&filename=<name> - # - # The special URL value LTSW will be translated into: - # http://www.ltsw.se/knbase/internet/<mediatype>.htp - # - # The special URL value [token] will be translated into: - # http://www.iana.org/assignments/contact-people.htm#<token> - # - # These values will be accessible through #urls, which always returns an - # array. - def urls - @url.map do |el| - case el - when %r{^IANA$} - IANA_URL % [ @media_type, @sub_type ] - when %r{^RFC(\d+)$} - RFC_URL % $1 - when %r{^DRAFT:(.+)$} - DRAFT_URL % $1 - when %r{^LTSW$} - LTSW_URL % @media_type - when %r{^\{([^=]+)=([^\}]+)\}} - [$1, $2] - when %r{^\[([^=]+)=([^\]]+)\]} - [$1, CONTACT_URL % $2] - when %r{^\[([^\]]+)\]} - CONTACT_URL % $1 - else - el - end - end - end - - class << self - # The MIME types main- and sub-label can both start with <tt>x-</tt>, - # which indicates that it is a non-registered name. Of course, after - # registration this flag can disappear, adds to the confusing - # proliferation of MIME types. The simplified string has the - # <tt>x-</tt> removed and are translated to lowercase. - def simplified(content_type) - matchdata = MEDIA_TYPE_RE.match(content_type) - - if matchdata.nil? - simplified = nil - else - media_type = matchdata.captures[0].downcase.gsub(UNREG_RE, '') - subtype = matchdata.captures[1].downcase.gsub(UNREG_RE, '') - simplified = "#{media_type}/#{subtype}" - end - simplified - end - - # Creates a MIME::Type from an array in the form of: - # [type-name, [extensions], encoding, system] - # - # +extensions+, +encoding+, and +system+ are optional. - # - # MIME::Type.from_array("application/x-ruby", ['rb'], '8bit') - # MIME::Type.from_array(["application/x-ruby", ['rb'], '8bit']) - # - # These are equivalent to: - # - # MIME::Type.new('application/x-ruby') do |t| - # t.extensions = %w(rb) - # t.encoding = '8bit' - # end - def from_array(*args) #:yields MIME::Type.new: - # Dereferences the array one level, if necessary. - args = args.first if args.first.kind_of? Array - - unless args.size.between?(1, 8) - raise ArgumentError, "Array provided must contain between one and eight elements." - end - - MIME::Type.new(args.shift) do |t| - t.extensions, t.encoding, t.system, t.obsolete, t.docs, t.url, - t.registered = *args - yield t if block_given? - end - end - - # Creates a MIME::Type from a hash. Keys are case-insensitive, - # dashes may be replaced with underscores, and the internal Symbol - # of the lowercase-underscore version can be used as well. That is, - # Content-Type can be provided as content-type, Content_Type, - # content_type, or :content_type. - # - # Known keys are <tt>Content-Type</tt>, - # <tt>Content-Transfer-Encoding</tt>, <tt>Extensions</tt>, and - # <tt>System</tt>. - # - # MIME::Type.from_hash('Content-Type' => 'text/x-yaml', - # 'Content-Transfer-Encoding' => '8bit', - # 'System' => 'linux', - # 'Extensions' => ['yaml', 'yml']) - # - # This is equivalent to: - # - # MIME::Type.new('text/x-yaml') do |t| - # t.encoding = '8bit' - # t.system = 'linux' - # t.extensions = ['yaml', 'yml'] - # end - def from_hash(hash) #:yields MIME::Type.new: - type = {} - hash.each_pair do |k, v| - type[k.to_s.tr('A-Z', 'a-z').gsub(/-/, '_').to_sym] = v - end - - MIME::Type.new(type[:content_type]) do |t| - t.extensions = type[:extensions] - t.encoding = type[:content_transfer_encoding] - t.system = type[:system] - t.obsolete = type[:obsolete] - t.docs = type[:docs] - t.url = type[:url] - t.registered = type[:registered] - - yield t if block_given? - end - end - - # Essentially a copy constructor. - # - # MIME::Type.from_mime_type(plaintext) - # - # is equivalent to: - # - # MIME::Type.new(plaintext.content_type.dup) do |t| - # t.extensions = plaintext.extensions.dup - # t.system = plaintext.system.dup - # t.encoding = plaintext.encoding.dup - # end - def from_mime_type(mime_type) #:yields the new MIME::Type: - MIME::Type.new(mime_type.content_type.dup) do |t| - t.extensions = mime_type.extensions.map { |e| e.dup } - t.url = mime_type.url && mime_type.url.map { |e| e.dup } - - mime_type.system && t.system = mime_type.system.dup - mime_type.encoding && t.encoding = mime_type.encoding.dup - - t.obsolete = mime_type.obsolete? - t.registered = mime_type.registered? - - mime_type.docs && t.docs = mime_type.docs.dup - - yield t if block_given? - end - end - end - - # Builds a MIME::Type object from the provided MIME Content Type value - # (e.g., 'text/plain' or 'applicaton/x-eruby'). The constructed object - # is yielded to an optional block for additional configuration, such as - # associating extensions and encoding information. - def initialize(content_type) #:yields self: - matchdata = MEDIA_TYPE_RE.match(content_type) - - if matchdata.nil? - raise InvalidContentType, "Invalid Content-Type provided ('#{content_type}')" - end - - @content_type = content_type - @raw_media_type = matchdata.captures[0] - @raw_sub_type = matchdata.captures[1] - - @simplified = MIME::Type.simplified(@content_type) - matchdata = MEDIA_TYPE_RE.match(@simplified) - @media_type = matchdata.captures[0] - @sub_type = matchdata.captures[1] - - self.extensions = nil - self.encoding = :default - self.system = nil - self.registered = true - self.url = nil - self.obsolete = nil - self.docs = nil - - yield self if block_given? - end - - # MIME content-types which are not regestered by IANA nor defined in - # RFCs are required to start with <tt>x-</tt>. This counts as well for - # a new media type as well as a new sub-type of an existing media - # type. If either the media-type or the content-type begins with - # <tt>x-</tt>, this method will return +false+. - def registered? - if (@raw_media_type =~ UNREG_RE) || (@raw_sub_type =~ UNREG_RE) - false - else - @registered - end - end - attr_writer :registered #:nodoc: - - # MIME types can be specified to be sent across a network in particular - # formats. This method returns +true+ when the MIME type encoding is set - # to <tt>base64</tt>. - def binary? - @encoding == 'base64' - end - - # MIME types can be specified to be sent across a network in particular - # formats. This method returns +false+ when the MIME type encoding is - # set to <tt>base64</tt>. - def ascii? - not binary? - end - - # Returns +true+ when the simplified MIME type is in the list of known - # digital signatures. - def signature? - SIGNATURES.include?(@simplified.downcase) - end - - # Returns +true+ if the MIME::Type is specific to an operating system. - def system? - not @system.nil? - end - - # Returns +true+ if the MIME::Type is specific to the current operating - # system as represented by RUBY_PLATFORM. - def platform? - system? and (RUBY_PLATFORM =~ @system) - end +require 'mime/type' +require 'mime/types/cache' +require 'mime/types/loader' + +# MIME::Types is a registry of MIME types. It is both a class (created with +# MIME::Types.new) and a default registry (loaded automatically or through +# interactions with MIME::Types.[] and MIME::Types.type_for). +# +# == The Default mime-types Registry +# +# The default mime-types registry is loaded automatically when the library +# is required (<tt>require 'mime/types'</tt>), but it may be lazily loaded +# (loaded on first use) with the use of the environment variable +# +RUBY_MIME_TYPES_LAZY_LOAD+ having any value other than +false+. The +# initial startup is about 14× faster (~10 ms vs ~140 ms), but the +# registry will be loaded at some point in the future. +# +# The default mime-types registry can also be loaded from a Marshal cache +# file specific to the version of MIME::Types being loaded. This will be +# handled automatically with the use of a file referred to in the +# environment variable +RUBY_MIME_TYPES_CACHE+. MIME::Types will attempt to +# load the registry from this cache file (MIME::Type::Cache.load); if it +# cannot be loaded (because the file does not exist, there is an error, or +# the data is for a different version of mime-types), the default registry +# will be loaded from the normal JSON version and then the cache file will +# be *written* to the location indicated by +RUBY_MIME_TYPES_CACHE+. Cache +# file loads just over 4½× faster (~30 ms vs ~140 ms). +# loads. +# +# Notes: +# * The loading of the default registry is *not* atomic; when using a +# multi-threaded environment, it is recommended that lazy loading is not +# used and mime-types is loaded as early as possible. +# * Cache files should be specified per application in a multiprocess +# environment and should be initialized during deployment or before +# forking to minimize the chance that the multiple processes will be +# trying to write to the same cache file at the same time, or that two +# applications that are on different versions of mime-types would be +# thrashing the cache. +# * Unless cache files are preinitialized, the application using the +# mime-types cache file must have read/write permission to the cache file. +# +# == Usage +# require 'mime/types' +# +# plaintext = MIME::Types['text/plain'] +# print plaintext.media_type # => 'text' +# print plaintext.sub_type # => 'plain' +# +# puts plaintext.extensions.join(" ") # => 'asc txt c cc h hh cpp' +# +# puts plaintext.encoding # => 8bit +# puts plaintext.binary? # => false +# puts plaintext.ascii? # => true +# puts plaintext.obsolete? # => false +# puts plaintext.registered? # => true +# puts plaintext == 'text/plain' # => true +# puts MIME::Type.simplified('x-appl/x-zip') # => 'appl/zip' +# +class MIME::Types + # The release version of Ruby MIME::Types + VERSION = MIME::Type::VERSION + + include Enumerable + + # The data version. + attr_reader :data_version + + # Creates a new MIME::Types registry. + def initialize + @type_variants = Container.new + @extension_index = Container.new + @data_version = VERSION.dup.freeze + end - # Returns +true+ if the MIME::Type specifies an extension list, - # indicating that it is a complete MIME::Type. - def complete? - not @extensions.empty? - end + def add_type_variant(mime_type) # :nodoc: + MIME.deprecated(self, __method__, :private) + add_type_variant!(mime_type) + end - # Returns the MIME type as a string. - def to_s - @content_type - end + def index_extensions(mime_type) # :nodoc: + MIME.deprecated(self, __method__, :private) + index_extensions!(mime_type) + end - # Returns the MIME type as a string for implicit conversions. - def to_str - @content_type - end + def defined_types # :nodoc: + MIME.deprecated(self, __method__) + @type_variants.values.flatten + end - # Returns the MIME type as an array suitable for use with - # MIME::Type.from_array. - def to_a - [ @content_type, @extensions, @encoding, @system, @obsolete, @docs, - @url, registered? ] - end + # Returns the number of known type variants. + def count + @type_variants.values.reduce(0) { |m, o| m + o.size } + end - # Returns the MIME type as an array suitable for use with - # MIME::Type.from_hash. - def to_hash - { 'Content-Type' => @content_type, - 'Content-Transfer-Encoding' => @encoding, - 'Extensions' => @extensions, - 'System' => @system, - 'Obsolete' => @obsolete, - 'Docs' => @docs, - 'URL' => @url, - 'Registered' => registered?, - } - end + # Iterates through the type variants. + def each + @type_variants.values.each { |tv| tv.each { |t| yield t } } end - # = MIME::Types - # MIME types are used in MIME-compliant communications, as in e-mail or - # HTTP traffic, to indicate the type of content which is transmitted. - # MIME::Types provides the ability for detailed information about MIME - # entities (provided as a set of MIME::Type objects) to be determined and - # used programmatically. There are many types defined by RFCs and vendors, - # so the list is long but not complete; don't hesitate to ask to add - # additional information. This library follows the IANA collection of MIME - # types (see below for reference). + @__types__ = nil + + # Returns a list of MIME::Type objects, which may be empty. The optional + # flag parameters are <tt>:complete</tt> (finds only complete MIME::Type + # objects) and <tt>:registered</tt> (finds only MIME::Types that are + # registered). It is possible for multiple matches to be returned for + # either type (in the example below, 'text/plain' returns two values -- + # one for the general case, and one for VMS systems). # - # == Description - # MIME types are used in MIME entities, as in email or HTTP traffic. It is - # useful at times to have information available about MIME types (or, - # inversely, about files). A MIME::Type stores the known information about - # one MIME type. + # puts "\nMIME::Types['text/plain']" + # MIME::Types['text/plain'].each { |t| puts t.to_a.join(", ") } # - # == Usage - # require 'mime/types' + # puts "\nMIME::Types[/^image/, complete: true]" + # MIME::Types[/^image/, :complete => true].each do |t| + # puts t.to_a.join(", ") + # end # - # plaintext = MIME::Types['text/plain'] - # print plaintext.media_type # => 'text' - # print plaintext.sub_type # => 'plain' + # If multiple type definitions are returned, returns them sorted as + # follows: + # 1. Complete definitions sort before incomplete ones; + # 2. IANA-registered definitions sort before LTSW-recorded + # definitions. + # 3. Generic definitions sort before platform-specific ones; + # 4. Current definitions sort before obsolete ones; + # 5. Obsolete definitions with use-instead clauses sort before those + # without; + # 6. Obsolete definitions use-instead clauses are compared. + # 7. Sort on name. # - # puts plaintext.extensions.join(" ") # => 'asc txt c cc h hh cpp' + # An additional flag of :platform (finds only MIME::Types for the current + # platform) is currently supported but deprecated. + def [](type_id, flags = {}) + if flags[:platform] + MIME.deprecated(self, __method__, "using the :platform flag") + end + + matches = case type_id + when MIME::Type + @type_variants[type_id.simplified] + when Regexp + match(type_id) + else + @type_variants[MIME::Type.simplified(type_id)] + end + + prune_matches(matches, flags).sort { |a, b| a.priority_compare(b) } + end + + # Return the list of MIME::Types which belongs to the file based on its + # filename extension. If there is no extension, the filename will be used + # as the matching criteria on its own. # - # puts plaintext.encoding # => 8bit - # puts plaintext.binary? # => false - # puts plaintext.ascii? # => true - # puts plaintext.obsolete? # => false - # puts plaintext.registered? # => true - # puts plaintext == 'text/plain' # => true - # puts MIME::Type.simplified('x-appl/x-zip') # => 'appl/zip' + # This will always return a merged, flatten, priority sorted, unique array. # - # This module is built to conform to the MIME types of RFCs 2045 and 2231. - # It follows the official IANA registry at - # http://www.iana.org/assignments/media-types/ and - # ftp://ftp.iana.org/assignments/media-types with some unofficial types - # added from the the collection at - # http://www.ltsw.se/knbase/internet/mime.htp - class Types - # The released version of Ruby MIME::Types - VERSION = MIME::Type::VERSION - DATA_VERSION = (VERSION.to_f * 100).to_i - - # The data version. - attr_reader :data_version - - class HashWithArrayDefault < Hash # :nodoc: - def initialize - super { |h, k| h[k] = [] } - end - - def marshal_dump - {}.merge(self) - end - - def marshal_load(hash) - self.merge!(hash) - end + # puts MIME::Types.type_for('citydesk.xml') + # => [application/xml, text/xml] + # puts MIME::Types.type_for('citydesk.gif') + # => [image/gif] + # puts MIME::Types.type_for(%w(citydesk.xml citydesk.gif)) + # => [application/xml, image/gif, text/xml] + # + # If +platform+ is +true+, then only file types that are specific to the + # current platform will be returned. This parameter has been deprecated. + def type_for(filename, platform = false) + types = [ filename ].flatten.map { |fn| + @extension_index[File.basename(fn.chomp.downcase).gsub(/.*\./o, '')] + }.flatten.sort { |a, b| a.priority_compare(b) }.uniq + + if platform + MIME.deprecated(self, __method__, + "using the platform parameter") + types.select(&:platform?) + else + types end + end + alias_method :of, :type_for - class CacheContainer # :nodoc: - attr_reader :version, :data - def initialize(version, data) - @version, @data = version, data + # Add one or more MIME::Type objects to the set of known types. If the + # type is already known, a warning will be displayed. + # + # The last parameter may be the value <tt>:silent</tt> or +true+ which + # will suppress duplicate MIME type warnings. + def add(*types) + quiet = ((types.last == :silent) or (types.last == true)) + + types.each do |mime_type| + case mime_type + when true, false, nil, Symbol + nil + when MIME::Types + variants = mime_type.instance_variable_get(:@type_variants) + add(*variants.values.flatten, quiet) + when Array + add(*mime_type, quiet) + else + add_type(mime_type, quiet) end end + end - def initialize(data_version = DATA_VERSION) - @type_variants = HashWithArrayDefault.new - @extension_index = HashWithArrayDefault.new - @data_version = data_version + # Add a single MIME::Type object to the set of known types. If the type is + # already known, a warning will be displayed. The +quiet+ parameter may be + # a truthy value to suppress that warning. + def add_type(mime_type, quiet = false) + if !quiet and @type_variants[mime_type.simplified].include?(mime_type) + warn("Type %s is already registered as a variant of %s." % [ + mime_type, mime_type.simplified ]) end - def add_type_variant(mime_type) #:nodoc: - @type_variants[mime_type.simplified] << mime_type - end + add_type_variant!(mime_type) + index_extensions!(mime_type) + end + + class << self + include Enumerable - def index_extensions(mime_type) #:nodoc: - mime_type.extensions.each { |ext| @extension_index[ext] << mime_type } + # Load MIME::Types from a v1 file registry. + # + # This method has been deprecated. + def load_from_file(filename) + MIME.deprecated(self, __method__) + MIME::Types::Loader.load_from_v1(filename) end - def defined_types #:nodoc: - @type_variants.values.flatten + # MIME::Types#[] against the default MIME::Types registry. + def [](type_id, flags = {}) + __types__[type_id, flags] end - # Returns the number of known types. A shortcut of MIME::Types[//].size. - # (Keep in mind that this is memory intensive, cache the result to spare - # resources) + # MIME::Types#count against the default MIME::Types registry. def count - defined_types.size + __types__.count end + # MIME::Types#each against the default MIME::Types registry. def each - defined_types.each { |t| yield t } + __types__.each {|t| yield t } end - @__types__ = nil + # MIME::Types#type_for against the default MIME::Types registry. + def type_for(filename, platform = false) + __types__.type_for(filename, platform) + end + alias_method :of, :type_for - # Returns a list of MIME::Type objects, which may be empty. The optional - # flag parameters are :complete (finds only complete MIME::Type objects) - # and :platform (finds only MIME::Types for the current platform). It is - # possible for multiple matches to be returned for either type (in the - # example below, 'text/plain' returns two values -- one for the general - # case, and one for VMS systems. - # - # puts "\nMIME::Types['text/plain']" - # MIME::Types['text/plain'].each { |t| puts t.to_a.join(", ") } - # - # puts "\nMIME::Types[/^image/, :complete => true]" - # MIME::Types[/^image/, :complete => true].each do |t| - # puts t.to_a.join(", ") - # end - # - # If multiple type definitions are returned, returns them sorted as - # follows: - # 1. Complete definitions sort before incomplete ones; - # 2. IANA-registered definitions sort before LTSW-recorded - # definitions. - # 3. Generic definitions sort before platform-specific ones; - # 4. Current definitions sort before obsolete ones; - # 5. Obsolete definitions with use-instead clauses sort before those - # without; - # 6. Obsolete definitions use-instead clauses are compared. - # 7. Sort on name. - def [](type_id, flags = {}) - matches = case type_id - when MIME::Type - @type_variants[type_id.simplified] - when Regexp - match(type_id) - else - @type_variants[MIME::Type.simplified(type_id)] - end - - prune_matches(matches, flags).sort { |a, b| a.priority_compare(b) } + # MIME::Types#add against the default MIME::Types registry. + def add(*types) + __types__.add(*types) end - # Return the list of MIME::Types which belongs to the file based on its - # filename extension. If +platform+ is +true+, then only file types that - # are specific to the current platform will be returned. - # - # This will always return an array. - # - # puts "MIME::Types.type_for('citydesk.xml') - # => [application/xml, text/xml] - # puts "MIME::Types.type_for('citydesk.gif') - # => [image/gif] - def type_for(filename, platform = false) - ext = filename.chomp.downcase.gsub(/.*\./o, '') - list = @extension_index[ext] - list.delete_if { |e| not e.platform? } if platform - list + # Returns the currently defined cache file, if any. + def cache_file + MIME.deprecated(self, __method__) + ENV['RUBY_MIME_TYPES_CACHE'] end - # A synonym for MIME::Types.type_for - def of(filename, platform = false) - type_for(filename, platform) + def add_type_variant(mime_type) # :nodoc: + __types__.add_type_variant(mime_type) end - # Add one or more MIME::Type objects to the set of known types. Each - # type should be experimental (e.g., 'application/x-ruby'). If the type - # is already known, a warning will be displayed. - # - # <strong>Please inform the maintainer of this module when registered - # types are missing.</strong> - def add(*types) - types.each do |mime_type| - if mime_type.kind_of? MIME::Types - add(*mime_type.defined_types) - else - if @type_variants.include?(mime_type.simplified) - if @type_variants[mime_type.simplified].include?(mime_type) - warn "Type #{mime_type} already registered as a variant of #{mime_type.simplified}." unless defined? MIME::Types::LOAD - end - end - add_type_variant(mime_type) - index_extensions(mime_type) - end - end + def index_extensions(mime_type) # :nodoc: + __types__.index_extensions(mime_type) end private - def prune_matches(matches, flags) - matches.delete_if { |e| not e.complete? } if flags[:complete] - matches.delete_if { |e| not e.platform? } if flags[:platform] - matches + def lazy_load? + (lazy = ENV['RUBY_MIME_TYPES_LAZY_LOAD']) && (lazy != 'false') end - def match(pattern) - matches = @type_variants.select { |k, v| k =~ pattern } - if matches.respond_to? :values - matches.values.flatten - else - matches.map { |m| m.last }.flatten - end + def __types__ + (defined?(@__types__) and @__types__) or load_default_mime_types end - class << self - def add_type_variant(mime_type) #:nodoc: - __types__.add_type_variant(mime_type) - end - - def index_extensions(mime_type) #:nodoc: - __types__.index_extensions(mime_type) - end - - # The regular expression used to match a file-based MIME type - # definition. - TEXT_FORMAT_RE = %r{ - \A - \s* - ([*])? # 0: Unregistered? - (!)? # 1: Obsolete? - (?:(\w+):)? # 2: Platform marker - #{MIME::Type::MEDIA_TYPE_RE}? # 3,4: Media type - (?:\s+@([^\s]+))? # 5: Extensions - (?:\s+:(#{MIME::Type::ENCODING_RE}))? # 6: Encoding - (?:\s+'(.+))? # 7: URL list - (?:\s+=(.+))? # 8: Documentation - (?:\s*([#].*)?)? - \s* - \z - }x - - # Build the type list from a file in the format: - # - # [*][!][os:]mt/st[<ws>@ext][<ws>:enc][<ws>'url-list][<ws>=docs] - # - # == * - # An unofficial MIME type. This should be used if and only if the MIME type - # is not properly specified (that is, not under either x-type or - # vnd.name.type). - # - # == ! - # An obsolete MIME type. May be used with an unofficial MIME type. - # - # == os: - # Platform-specific MIME type definition. - # - # == mt - # The media type. - # - # == st - # The media subtype. - # - # == <ws>@ext - # The list of comma-separated extensions. - # - # == <ws>:enc - # The encoding. - # - # == <ws>'url-list - # The list of comma-separated URLs. - # - # == <ws>=docs - # The documentation string. - # - # That is, everything except the media type and the subtype is optional. The - # more information that's available, though, the richer the values that can - # be provided. - def load_from_file(filename) #:nodoc: - if defined? ::Encoding - data = File.open(filename, 'r:UTF-8:-') { |f| f.read } - else - data = File.open(filename) { |f| f.read } - end - data = data.split($/) - mime = MIME::Types.new - data.each_with_index { |line, index| - item = line.chomp.strip - next if item.empty? - - begin - m = TEXT_FORMAT_RE.match(item).captures - rescue Exception - puts "#{filename}:#{index}: Parsing error in MIME type definitions." - puts "=> #{line}" - raise - end - - unregistered, obsolete, platform, mediatype, subtype, extensions, - encoding, urls, docs, comment = *m - - if mediatype.nil? - if comment.nil? - puts "#{filename}:#{index}: Parsing error in MIME type definitions." - puts "=> #{line}" - raise RuntimeError - end - - next - end - - extensions &&= extensions.split(/,/) - urls &&= urls.split(/,/) - - mime_type = MIME::Type.new("#{mediatype}/#{subtype}") do |t| - t.extensions = extensions - t.encoding = encoding - t.system = platform - t.obsolete = obsolete - t.registered = false if unregistered - t.docs = docs - t.url = urls - end - - mime.add(mime_type) - } - mime - end - - # Returns a list of MIME::Type objects, which may be empty. The - # optional flag parameters are :complete (finds only complete - # MIME::Type objects) and :platform (finds only MIME::Types for the - # current platform). It is possible for multiple matches to be - # returned for either type (in the example below, 'text/plain' returns - # two values -- one for the general case, and one for VMS systems. - # - # puts "\nMIME::Types['text/plain']" - # MIME::Types['text/plain'].each { |t| puts t.to_a.join(", ") } - # - # puts "\nMIME::Types[/^image/, :complete => true]" - # MIME::Types[/^image/, :complete => true].each do |t| - # puts t.to_a.join(", ") - # end - def [](type_id, flags = {}) - __types__[type_id, flags] - end - - include Enumerable - - def count - __types__.count - end - - def each - __types__.each {|t| yield t } - end - - # Return the list of MIME::Types which belongs to the file based on - # its filename extension. If +platform+ is +true+, then only file - # types that are specific to the current platform will be returned. - # - # This will always return an array. - # - # puts "MIME::Types.type_for('citydesk.xml') - # => [application/xml, text/xml] - # puts "MIME::Types.type_for('citydesk.gif') - # => [image/gif] - def type_for(filename, platform = false) - __types__.type_for(filename, platform) - end - - # A synonym for MIME::Types.type_for - def of(filename, platform = false) - __types__.type_for(filename, platform) - end - - # Add one or more MIME::Type objects to the set of known types. Each - # type should be experimental (e.g., 'application/x-ruby'). If the - # type is already known, a warning will be displayed. - # - # <strong>Please inform the maintainer of this module when registered - # types are missing.</strong> - def add(*types) - __types__.add(*types) - end - - # Returns the currently defined cache file, if any. - def cache_file - ENV['RUBY_MIME_TYPES_CACHE'] - end - - private - def load_mime_types_from_cache - load_mime_types_from_cache! if cache_file - end - - def load_mime_types_from_cache! - raise ArgumentError, "No RUBY_MIME_TYPES_CACHE set." unless cache_file - return false unless File.exists? cache_file - - begin - data = File.read(cache_file) - container = Marshal.load(data) - - if container.version == VERSION - @__types__ = Marshal.load(container.data) - true - else - false - end - rescue => e - warn "Could not load MIME::Types cache: #{e}" - false - end - end - - def write_mime_types_to_cache - write_mime_types_to_cache! if cache_file - end - - def write_mime_types_to_cache! - raise ArgumentError, "No RUBY_MIME_TYPES_CACHE set." unless cache_file - - File.open(cache_file, 'w') do |f| - cache = MIME::Types::CacheContainer.new(VERSION, - Marshal.dump(__types__)) - f.write Marshal.dump(cache) - end - - true - end - - def load_and_parse_mime_types - const_set(:LOAD, true) unless $DEBUG - Dir[File.join(File.dirname(__FILE__), 'types', '*')].sort.each { |f| - add(load_from_file(f)) - } - remove_const :LOAD if defined? LOAD + def load_default_mime_types + @__types__ = MIME::Types::Cache.load + unless @__types__ + @__types__ = MIME::Types::Loader.load + MIME::Types::Cache.save(@__types__) end + @__types__ + end + end - def lazy_load? - (lazy = ENV['RUBY_MIME_TYPES_LAZY_LOAD']) && (lazy != 'false') - end + private + def add_type_variant!(mime_type) + @type_variants[mime_type.simplified] << mime_type + end - def __types__ - load_mime_types unless @__types__ - @__types__ - end + def index_extensions!(mime_type) + mime_type.extensions.each { |ext| @extension_index[ext] << mime_type } + end - def load_mime_types - @__types__ = new(VERSION) - unless load_mime_types_from_cache - load_and_parse_mime_types - write_mime_types_to_cache - end - end - end + def prune_matches(matches, flags) + matches.delete_if { |e| not e.complete? } if flags[:complete] + matches.delete_if { |e| not e.platform? } if flags[:platform] + matches.delete_if { |e| not e.registered? } if flags[:registered] + matches + end - load_mime_types unless lazy_load? + def match(pattern) + @type_variants.select { |k, v| k =~ pattern }.values.flatten end + + load_default_mime_types unless lazy_load? end # vim: ft=ruby |