class MimeMagic

Mime type detection

def self.add(type, options)

* :comment: Comment string
* :magic: Mime magic specification
* :parents: String list or single string of parent mime types
* :extensions: String list or single string of file extensions
Option keys:

* options: Options hash
* type: Mime type
Add custom mime type. Arguments:
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

def self.all_by_magic(io)

This is a slower operation.
Lookup all mime types by magic content analysis.
def self.all_by_magic(io)
  magic_match(io, :select).map { |mime| new(mime[0]) }
end

def self.by_extension(ext)

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

def self.by_magic(io)

This is a slow operation.
Lookup mime type by magic content analysis.
def self.by_magic(io)
  mime = magic_match(io, :find)
  mime && new(mime[0])
end

def self.by_path(path)

Lookup mime type by filename
def self.by_path(path)
  by_extension(File.extname(path))
end

def self.child?(child, parent)

def self.child?(child, parent)
  child == parent || TYPES.key?(child) && TYPES[child][1].any? {|p| child?(p, parent) }
end

def self.get_matches(parent)

def self.get_matches(parent)
  parent.elements.map {|match|
    if match['mask']
      nil
    else
      type = match['type']
      value = match['value']
      offset = match['offset'].split(':').map {|x| x.to_i }
      offset = offset.size == 2 ? offset[0]..offset[1] : offset[0]
      case type
      when 'string'
        value.gsub!(/\\(x[\dA-Fa-f]{1,2}|0\d{1,3}|\d{1,3}|.)/) { eval("\"\\#{$1}\"") }
      when 'big16'
        value = str2int(value)
        value = ((value >> 8).chr + (value & 0xFF).chr)
      when 'big32'
        value = str2int(value)
        value = (((value >> 24) & 0xFF).chr + ((value >> 16) & 0xFF).chr + ((value >> 8) & 0xFF).chr + (value & 0xFF).chr)
      when 'little16'
        value = str2int(value)
        value = ((value & 0xFF).chr + (value >> 8).chr)
      when 'little32'
        value = str2int(value)
        value = ((value & 0xFF).chr + ((value >> 8) & 0xFF).chr + ((value >> 16) & 0xFF).chr + ((value >> 24) & 0xFF).chr)
      when 'host16' # use little endian
        value = str2int(value)
        value = ((value & 0xFF).chr + (value >> 8).chr)
      when 'host32' # use little endian
        value = str2int(value)
        value = ((value & 0xFF).chr + ((value >> 8) & 0xFF).chr + ((value >> 16) & 0xFF).chr + ((value >> 24) & 0xFF).chr)
      when 'byte'
        value = str2int(value)
        value = value.chr
      end
      children = get_matches(match)
      children.empty? ? [offset, value] : [offset, value, children]
    end
  }.compact
end

def self.magic_match(io, method)

def self.magic_match(io, method)
  return magic_match(StringIO.new(io.to_s), method) unless io.respond_to?(:read)
  io.binmode if io.respond_to?(:binmode)
  io.set_encoding(Encoding::BINARY) if io.respond_to?(:set_encoding)
  buffer = "".encode(Encoding::BINARY)
  MAGIC.send(method) { |type, matches| magic_match_io(io, matches, buffer) }
end

def self.magic_match_io(io, matches, buffer)

def self.magic_match_io(io, matches, buffer)
  matches.any? do |offset, value, children|
    match =
      if Range === offset
        io.read(offset.begin, buffer)
        x = io.read(offset.end - offset.begin + value.bytesize, buffer)
        x && x.include?(value)
      else
        io.read(offset, buffer)
        io.read(value.bytesize, buffer) == value
      end
    io.rewind
    match && (!children || magic_match_io(io, children, buffer))
  end
end

def self.open_mime_database

def self.open_mime_database
  path = MimeMagic::DATABASE_PATH
  File.open(path)
end

def self.parse_database

def self.parse_database
  file = open_mime_database
  doc = Nokogiri::XML(file)
  extensions = {}
  types = {}
  magics = []
  (doc/'mime-info/mime-type').each do |mime|
    comments = Hash[*(mime/'comment').map {|comment| [comment['xml:lang'], comment.inner_text] }.flatten]
    type = mime['type']
    subclass = (mime/'sub-class-of').map{|x| x['type']}
    exts = (mime/'glob').map{|x| x['pattern'] =~ /^\*\.([^\[\]]+)$/ ? $1.downcase : nil }.compact
    (mime/'magic').each do |magic|
      priority = magic['priority'].to_i
      matches = get_matches(magic)
      magics << [priority, type, matches]
    end
    if !exts.empty?
      exts.each{|x|
        extensions[x] = type if !extensions.include?(x)
      }
      types[type] = [exts,subclass,comments[nil]]
    end
  end
  magics = magics.sort {|a,b| [-a[0],a[1]] <=> [-b[0],b[1]] }
  common_types = [
    "image/jpeg",                                                              # .jpg
    "image/png",                                                               # .png
    "image/gif",                                                               # .gif
    "image/tiff",                                                              # .tiff
    "image/bmp",                                                               # .bmp
    "image/vnd.adobe.photoshop",                                               # .psd
    "image/webp",                                                              # .webp
    "image/svg+xml",                                                           # .svg
    "video/x-msvideo",                                                         # .avi
    "video/x-ms-wmv",                                                          # .wmv
    "video/mp4",                                                               # .mp4, .m4v
    "video/quicktime",                                                         # .mov
    "video/mpeg",                                                              # .mpeg
    "video/ogg",                                                               # .ogv
    "video/webm",                                                              # .webm
    "video/x-matroska",                                                        # .mkv
    "video/x-flv",                                                             # .flv
    "audio/mpeg",                                                              # .mp3
    "audio/x-wav",                                                             # .wav
    "audio/aac",                                                               # .aac
    "audio/flac",                                                              # .flac
    "audio/mp4",                                                               # .m4a
    "audio/ogg",                                                               # .ogg
    "application/pdf",                                                         # .pdf
    "application/msword",                                                      # .doc
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document", # .docx
    "application/vnd.ms-powerpoint",                                           # .pps
    "application/vnd.openxmlformats-officedocument.presentationml.slideshow",  # .ppsx
    "application/vnd.ms-excel",                                                # .pps
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",       # .ppsx
  ]
  common_magics = common_types.map do |common_type|
    magics.find { |_, type, _| type == common_type }
  end
  magics = (common_magics.compact + magics).uniq
  extensions.keys.sort.each do |key|
    EXTENSIONS[key] = extensions[key]
  end
  types.keys.sort.each do |key|
    exts = types[key][0]
    parents = types[key][1].sort
    comment = types[key][2]
    TYPES[key] = [exts, parents, comment]
  end
  magics.each do |priority, type, matches|
    MAGIC << [type, matches]
  end
end

def self.remove(type)

* type: The mime type to remove. All associated extensions and magic are removed too.
you're seeing impossible conflicts (for instance, application/x-gmc-link).
Removes a mime type from the dictionary. You might want to do this if
def self.remove(type)
  EXTENSIONS.delete_if {|ext, t| t == type }
  MAGIC.delete_if {|t, m| t == type }
  TYPES.delete(type)
end

def self.str2int(s)

def self.str2int(s)
  return s.to_i(16) if s[0..1].downcase == '0x'
  return s.to_i(8) if s[0..0].downcase == '0'
  s.to_i(10)
end

def audio?; mediatype == 'audio'; end

def audio?; mediatype == 'audio'; end

def child_of?(parent)

Returns true if type is child of parent type
def child_of?(parent)
  MimeMagic.child?(type, parent)
end

def comment

Get mime comment
def comment
  (TYPES.key?(type) ? TYPES[type][2] : nil).to_s
end

def eql?(other)

Allow comparison with string
def eql?(other)
  type == other.to_s
end

def extensions

Get string list of file extensions
def extensions
  TYPES.key?(type) ? TYPES[type][0] : []
end

def hash

def hash
  type.hash
end

def image?; mediatype == 'image'; end

Mediatype shortcuts
def image?; mediatype == 'image'; end

def initialize(type)

Mime type by type string
def initialize(type)
  @type = type
  @mediatype, @subtype = type.split('/', 2)
end

def text?; mediatype == 'text' || child_of?('text/plain'); end

Returns true if type is a text format
def text?; mediatype == 'text' || child_of?('text/plain'); end

def to_s

Return type as string
def to_s
  type
end

def video?; mediatype == 'video'; end

def video?; mediatype == 'video'; end