# -*- ruby encoding: utf-8 -*- 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 (require 'mime/types'), 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 def add_type_variant(mime_type) # :nodoc: MIME.deprecated(self, __method__, :private) add_type_variant!(mime_type) end def index_extensions(mime_type) # :nodoc: MIME.deprecated(self, __method__, :private) index_extensions!(mime_type) end def defined_types # :nodoc: MIME.deprecated(self, __method__) @type_variants.values.flatten end # Returns the number of known type variants. def count @type_variants.values.reduce(0) { |m, o| m + o.size } end # Iterates through the type variants. def each @type_variants.values.each { |tv| tv.each { |t| yield t } } end @__types__ = nil # Returns a list of MIME::Type objects, which may be empty. The optional # flag parameters are :complete (finds only complete MIME::Type # objects) and :registered (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). # # 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. # # 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. # # This will always return a merged, flatten, priority sorted, unique array. # # 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 # 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 :silent 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 # 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 add_type_variant!(mime_type) index_extensions!(mime_type) end class << self include Enumerable # 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 # MIME::Types#[] against the default MIME::Types registry. def [](type_id, flags = {}) __types__[type_id, flags] end # MIME::Types#count against the default MIME::Types registry. def count __types__.count end # MIME::Types#each against the default MIME::Types registry. def each __types__.each {|t| yield t } end # 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 # MIME::Types#add against the default MIME::Types registry. def add(*types) __types__.add(*types) end # Returns the currently defined cache file, if any. def cache_file MIME.deprecated(self, __method__) ENV['RUBY_MIME_TYPES_CACHE'] end 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 private def lazy_load? (lazy = ENV['RUBY_MIME_TYPES_LAZY_LOAD']) && (lazy != 'false') end def __types__ (defined?(@__types__) and @__types__) or load_default_mime_types end 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 private def add_type_variant!(mime_type) @type_variants[mime_type.simplified] << mime_type end def index_extensions!(mime_type) mime_type.extensions.each { |ext| @extension_index[ext] << mime_type } 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 def match(pattern) @type_variants.select { |k, v| k =~ pattern }.values.flatten end load_default_mime_types unless lazy_load? end # vim: ft=ruby