marcel 1.2.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,12 +3,46 @@
3
3
  module Marcel
4
4
  class MimeType
5
5
  BINARY = "application/octet-stream"
6
+ MAX_DECLARED_TYPE_BYTES = 8 * 1024
7
+ TOKEN = "[!#$%&'*+\\-.^_`|~0-9A-Za-z]+"
8
+ QUOTED_STRING = '"(?:[\t\x20\x21\x23-\x5B\x5D-\x7E\x80-\xFF]|\\\\[\t\x20-\x7E\x80-\xFF])*"'
9
+ MEDIA_TYPE = %r{\A(#{TOKEN}/#{TOKEN})(?:[ \t]*;[ \t]*#{TOKEN}=(?:#{TOKEN}|#{QUOTED_STRING}))*(?:[ \t]*;[ \t]*)?\z}n
6
10
 
7
11
  class << self
8
- def extend(type, extensions: [], parents: [], magic: nil)
9
- extensions = (Array(extensions) + Array(Marcel::TYPE_EXTS[type])).uniq
10
- parents = (Array(parents) + Array(Marcel::TYPE_PARENTS[type])).uniq
11
- Magic.add(type, extensions: extensions, magic: magic, parents: parents)
12
+ def canonicalize(type, instead_of:)
13
+ Magic.canonicalize type, instead_of: instead_of
14
+ end
15
+
16
+ def extend(type, extensions: nil, aliases: nil, parents: nil, magic: nil)
17
+ type = type.downcase
18
+
19
+ if canonical = Marcel::TYPE_ALIASES[type]
20
+ warn "#{type} is an alias; extending its canonical type #{canonical} instead"
21
+ type = canonical
22
+ end
23
+
24
+ extensions = Array(extensions)
25
+ if extensions.any? && extensions.sort == Array(Marcel::TYPE_EXTS[type]).sort
26
+ warn "#{type} already has extensions #{extensions.inspect}"
27
+ end
28
+ extensions |= Array(Marcel::TYPE_EXTS[type])
29
+
30
+ aliases = Array(aliases)
31
+ existing_aliases = Marcel::TYPE_ALIASES.select { |_, existing| existing == type }.keys
32
+ if aliases.any? && aliases.sort == existing_aliases.sort
33
+ warn "#{type} already has aliases #{aliases.inspect}"
34
+ end
35
+ aliases |= existing_aliases
36
+
37
+ parents = Array(parents)
38
+ if parents.any? && parents.sort == Array(Marcel::TYPE_PARENTS[type]).sort
39
+ warn "#{type} already has parents #{parents.inspect}"
40
+ end
41
+ parents |= Array(Marcel::TYPE_PARENTS[type])
42
+
43
+ # No duplicate-magic warning: matcher order determines precedence, so re-registering
44
+ # an existing matcher legitimately promotes it ahead of the generated tables.
45
+ Magic.add(type, extensions: extensions, magic: magic, aliases: aliases, parents: parents)
12
46
  end
13
47
 
14
48
  # Returns the most appropriate content type for the given file.
@@ -23,7 +57,13 @@ module Marcel
23
57
  #
24
58
  # The most appropriate type is determined by the following:
25
59
  # * type declared by binary magic number data
26
- # * type declared by the first of file name, file extension, or declared MIME type
60
+ # * valid declared MIME type, unless it is application/octet-stream
61
+ # * type inferred from the file name or extension
62
+ #
63
+ # A later candidate is used only if it is more specific than the type already found.
64
+ #
65
+ # The result is a best-effort label, not validation that the file is safe or conforms to
66
+ # the returned type. Treat +name+ and +declared_type+ as hints from their respective sources.
27
67
  #
28
68
  # If no type can be determined, then +application/octet-stream+ is returned.
29
69
  def for(pathname_or_io = nil, name: nil, extension: nil, declared_type: nil)
@@ -37,7 +77,8 @@ module Marcel
37
77
  if pathname_or_io
38
78
  with_io(pathname_or_io) do |io|
39
79
  if magic = Marcel::Magic.by_magic(io)
40
- magic.type.downcase
80
+ type = magic.canonical.type.downcase
81
+ Marcel::Magic::Xml.refine(io, Marcel::Magic::Zip.refine(io, type))
41
82
  end
42
83
  end
43
84
  end
@@ -46,7 +87,7 @@ module Marcel
46
87
  def for_name(name)
47
88
  if name
48
89
  if magic = Marcel::Magic.by_path(name)
49
- magic.type.downcase
90
+ magic.canonical.type.downcase
50
91
  end
51
92
  end
52
93
  end
@@ -54,13 +95,13 @@ module Marcel
54
95
  def for_extension(extension)
55
96
  if extension
56
97
  if magic = Marcel::Magic.by_extension(extension)
57
- magic.type.downcase
98
+ magic.canonical.type.downcase
58
99
  end
59
100
  end
60
101
  end
61
102
 
62
103
  def for_declared_type(declared_type)
63
- type = parse_media_type(declared_type)
104
+ type = Marcel::Magic.canonical(parse_media_type(declared_type))
64
105
 
65
106
  # application/octet-stream is treated as an undeclared/missing type,
66
107
  # allowing the type to be inferred from the filename. If there's no
@@ -70,17 +111,22 @@ module Marcel
70
111
 
71
112
  def with_io(pathname_or_io, &block)
72
113
  if defined?(Pathname) && pathname_or_io.is_a?(Pathname)
73
- pathname_or_io.open(&block)
114
+ pathname_or_io.open("rb", &block)
74
115
  else
75
116
  yield pathname_or_io
76
117
  end
77
118
  end
78
119
 
79
120
  def parse_media_type(content_type)
80
- if content_type
81
- result = content_type.downcase.split(/[;,\s]/, 2).first
82
- result if result && result.index("/")
83
- end
121
+ return unless content_type
122
+ return if content_type.bytesize > MAX_DECLARED_TYPE_BYTES
123
+
124
+ content_type = content_type.dup.force_encoding(Encoding::BINARY)
125
+ content_type.sub!(/\A[ \t\r\n]+/n, "")
126
+ content_type.sub!(/[ \t\r\n]+\z/n, "")
127
+
128
+ media_type = MEDIA_TYPE.match(content_type)
129
+ media_type[1].downcase.force_encoding(Encoding::UTF_8) if media_type
84
130
  end
85
131
 
86
132
  # For some document types (notably Microsoft Office) we recognise the main content