# # MimeMagic is a library to detect the mime type of a file by extension or by content. # # Usage # ===== # # require 'mimemagic' # MimeMagic.by_extension('html').text? # MimeMagic.by_extension('.html').child_of? 'text/plain' # MimeMagic.by_path('filename.txt') # MimeMagic.by_magic(File.open('test.html')) # etc... # # Authors # ======= # Daniel Mendler # require 'epitools/mimemagic_tables' require 'stringio' # Mime type detection class MimeMagic VERSION = '0.1.8b' attr_reader :type, :mediatype, :subtype # Mime type by type string def initialize(type) @type = type @mediatype, @subtype = type.split('/', 2) end # Add custom mime type. Arguments: # * type: Mime type # * options: Options hash # # Option keys: # * :extensions: String list or single string of file extensions # * :parents: String list or single string of parent mime types # * :magic: Mime magic specification # * :comment: Comment string def self.add(type, options) extensions = [options[:extensions]].flatten.compact TYPES[type] = [extensions, [options[:parents]].flatten.compact, options[:comment]] extensions.each {|ext| EXTENSIONS[ext] = type } MAGIC.unshift [type, options[:magic]] if options[:magic] end # Removes a mime type from the dictionary. You might want to do this if # you're seeing impossible conflicts (for instance, application/x-gmc-link). # * type: The mime type to remove. All associated extensions and magic are removed too. def self.remove(type) EXTENSIONS.delete_if {|ext, t| t == type } MAGIC.delete_if {|t, m| t == type } TYPES.delete(type) end # Returns true if type is a text format def text?; mediatype == 'text' || child_of?('text/plain'); end # Mediatype shortcuts def image?; mediatype == 'image'; end def audio?; mediatype == 'audio'; end def video?; mediatype == 'video'; end # Returns true if type is child of parent type def child_of?(parent) MimeMagic.child?(type, parent) end # Get string list of file extensions def extensions TYPES.key?(type) ? TYPES[type][0] : [] end # Default extension def ext extensions.first end # Get mime comment def comment (TYPES.key?(type) ? TYPES[type][2] : nil).to_s end # Lookup mime type by file extension def self.by_extension(ext) ext = ext.to_s.downcase mime = ext[0..0] == '.' ? EXTENSIONS[ext[1..-1]] : EXTENSIONS[ext] mime && new(mime) end # Lookup mime type by filename def self.by_path(path) by_extension(File.extname(path)) end # Lookup mime type by magic content analysis. # This is a slow operation. def self.by_magic(io) if !(io.respond_to?(:seek) && io.respond_to?(:read)) io = StringIO.new(io.to_s, 'rb:binary') end mime = MAGIC.find {|type, matches| magic_match(io, matches) } mime && new(mime[0]) end # Return type as string def to_s type end # Allow comparison with string def ==(x) type == x.to_s end # allow comparison with hashes def hash type.hash end # allow comparison with something else def eql?(other) self.type == other.type end private def self.child?(child, parent) child == parent || TYPES.key?(child) && TYPES[child][1].any? {|p| child?(p, parent) } end def self.magic_match(io, matches) matches.any? do |offset, value, children| match = if Range === offset io.seek(offset.begin) io.read(offset.end - offset.begin + value.length).include?(value) else io.seek(offset) value == io.read(value.length) end match && (!children || magic_match(io, children)) end rescue false end private_class_method :magic_match end