# frozen_string_literal: true
##
module MIME
##
class Types
end
end
require "mime/type"
# 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.provisional? # => false
# 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
# Creates a new MIME::Types registry.
def initialize
@type_variants = Container.new
@extension_index = Container.new
end
# Returns the number of known type variants.
def count
@type_variants.values.inject(0) { |a, e| a + e.size }
end
def inspect # :nodoc:
"#<#{self.class}: #{count} variants, #{@extension_index.count} extensions>"
end
# Iterates through the type variants.
def each
if block_given?
@type_variants.each_value { |tv| tv.each { |t| yield t } }
else
enum_for(:each)
end
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. Current definitions sort before obsolete ones;
# 4. Obsolete definitions with use-instead clauses sort before those
# without;
# 5. Obsolete definitions use-instead clauses are compared.
# 6. Sort on name.
def [](type_id, complete: false, registered: false)
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, complete, registered).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]
def type_for(filename)
Array(filename).flat_map { |fn|
@extension_index[fn.chomp.downcase[/\.?([^.]*?)$/, 1]]
}.compact.inject(Set.new, :+).sort { |a, b|
a.priority_compare(b)
}
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) || (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.inject(Set.new, :+).to_a, 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(type, quiet = false)
if !quiet && @type_variants[type.simplified].include?(type)
MIME::Types.logger.warn <<-WARNING.chomp.strip
Type #{type} is already registered as a variant of #{type.simplified}.
WARNING
end
add_type_variant!(type)
index_extensions!(type)
end
private
def add_type_variant!(mime_type)
@type_variants.add(mime_type.simplified, mime_type)
end
def reindex_extensions!(mime_type)
return unless @type_variants[mime_type.simplified].include?(mime_type)
index_extensions!(mime_type)
end
def index_extensions!(mime_type)
mime_type.extensions.each { |ext| @extension_index.add(ext, mime_type) }
end
def prune_matches(matches, complete, registered)
matches.delete_if { |e| !e.complete? } if complete
matches.delete_if { |e| !e.registered? } if registered
matches
end
def match(pattern)
@type_variants.select { |k, _|
k =~ pattern
}.values.inject(Set.new, :+)
end
end
require "mime/types/cache"
require "mime/types/container"
require "mime/types/loader"
require "mime/types/logger"
require "mime/types/_columnar"
require "mime/types/registry"