class Marcel::Magic
Mime type detection
def self.add(type, options)
* :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 TYPE_EXTS[type] = extensions parents = [options[:parents]].flatten.compact TYPE_PARENTS[type] = parents unless parents.empty? extensions.each {|ext| EXTENSIONS[ext] = type } MAGIC.unshift [type, options[:magic]] if options[:magic] end
def self.all_by_magic(io)
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)
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)
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)
def self.by_path(path) by_extension(File.extname(path)) end
def self.child?(child, parent)
def self.child?(child, parent) child == parent || TYPE_PARENTS[child]&.any? {|p| child?(p, parent) } 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) buffer = "".b 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 value if value.is_a?(Regexp) match_regex(io, offset, value, buffer) elsif 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 end io.rewind match && (!children || magic_match_io(io, children, buffer)) end end
def self.match_regex(io, offset, regexp, buffer)
def self.match_regex(io, offset, regexp, buffer) start = offset.is_a?(Range) ? offset.begin : offset io.read(start, buffer) if start > 0 data = io.read(256, buffer) return false unless data data.match?(regexp) end
def self.remove(type)
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 } TYPE_EXTS.delete(type) TYPE_PARENTS.delete(type) end
def audio?; mediatype == 'audio'; end
def audio?; mediatype == 'audio'; end
def child_of?(parent)
def child_of?(parent) self.class.child?(type, parent) end
def comment
def comment nil # deprecated end
def eql?(other)
def eql?(other) type == other.to_s end
def extensions
def extensions TYPE_EXTS[type] || [] end
def hash
def hash type.hash end
def image?; mediatype == 'image'; end
def image?; mediatype == 'image'; end
def initialize(type)
def initialize(type) @type = type @mediatype, @subtype = type.split('/', 2) end
def text?; mediatype == 'text' || child_of?('text/plain'); end
def text?; mediatype == 'text' || child_of?('text/plain'); end
def to_s
def to_s type end
def video?; mediatype == 'video'; end
def video?; mediatype == 'video'; end