marcel 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -3
- data/lib/marcel/magic.rb +8 -7
- data/lib/marcel/mime_type/definitions.rb +5 -3
- data/lib/marcel/mime_type.rb +27 -33
- data/lib/marcel/tables.rb +1498 -1258
- data/lib/marcel/version.rb +3 -1
- data/lib/marcel.rb +2 -0
- metadata +8 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d72cd32835e2c94cb305350ca65c70471ba0003730705c42f880571344b9a79c
|
4
|
+
data.tar.gz: bf87bf4e6739db822a9e474677d4785aad74a37add0ad88fba3c2daac24bb142
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 002d7bf37923e0ce1da283ee7d41f36f604c454aa0924066013590805bb5bcaa878fab1cda9a884544afbcaf589384e0960a80774723f202a7c6dfa637bea05a
|
7
|
+
data.tar.gz: c3c361fc72ceec92d38e938613f8e1bbb7821cbe7221a59255d5d6eb8012aafd775b38f431c34df3fb5e9b443fedfdcda500fb76a2cb52e03cbe6028a042c26e
|
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# Marcel
|
2
2
|
|
3
|
-
Marcel attempts to choose the most appropriate content type for a given file by looking at the binary data, the filename, and any declared type (perhaps passed as a request header):
|
4
|
-
|
5
|
-
It's used like this:
|
3
|
+
Marcel attempts to choose the most appropriate content type for a given file by looking at the binary data, the filename, and any declared type (perhaps passed as a request header). This is done via the `Marcel::MimeType.for` method, and is used like this:
|
6
4
|
|
7
5
|
```ruby
|
8
6
|
Marcel::MimeType.for Pathname.new("example.gif")
|
@@ -43,6 +41,14 @@ Marcel::MimeType.for Pathname.new("example.png"), name: "example.ai"
|
|
43
41
|
# As "application/illustrator" is not a more specific type of "image/png", the filename is ignored
|
44
42
|
```
|
45
43
|
|
44
|
+
Custom file types not supported by Marcel can be added using `Marcel::MimeType.extend`.
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
Marcel::MimeType.extend "text/custom", extensions: %w( customtxt )
|
48
|
+
Marcel::MimeType.for name: "file.customtxt"
|
49
|
+
# => "text/custom"
|
50
|
+
```
|
51
|
+
|
46
52
|
## Motivation
|
47
53
|
|
48
54
|
Marcel was extracted from Basecamp 3, in order to make our file detection logic both easily reusable but more importantly, easily testable. Test fixtures have been added for all of the most common file types uploaded to Basecamp, and other common file types too. We hope to expand this test coverage with other file types as and when problems are identified.
|
data/lib/marcel/magic.rb
CHANGED
@@ -29,9 +29,9 @@ module Marcel
|
|
29
29
|
# * <i>:comment</i>: Comment string
|
30
30
|
def self.add(type, options)
|
31
31
|
extensions = [options[:extensions]].flatten.compact
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
TYPE_EXTS[type] = extensions
|
33
|
+
parents = [options[:parents]].flatten.compact
|
34
|
+
TYPE_PARENTS[type] = parents unless parents.empty?
|
35
35
|
extensions.each {|ext| EXTENSIONS[ext] = type }
|
36
36
|
MAGIC.unshift [type, options[:magic]] if options[:magic]
|
37
37
|
end
|
@@ -42,7 +42,8 @@ module Marcel
|
|
42
42
|
def self.remove(type)
|
43
43
|
EXTENSIONS.delete_if {|ext, t| t == type }
|
44
44
|
MAGIC.delete_if {|t, m| t == type }
|
45
|
-
|
45
|
+
TYPE_EXTS.delete(type)
|
46
|
+
TYPE_PARENTS.delete(type)
|
46
47
|
end
|
47
48
|
|
48
49
|
# Returns true if type is a text format
|
@@ -60,12 +61,12 @@ module Marcel
|
|
60
61
|
|
61
62
|
# Get string list of file extensions
|
62
63
|
def extensions
|
63
|
-
|
64
|
+
TYPE_EXTS[type] || []
|
64
65
|
end
|
65
66
|
|
66
67
|
# Get mime comment
|
67
68
|
def comment
|
68
|
-
|
69
|
+
nil # deprecated
|
69
70
|
end
|
70
71
|
|
71
72
|
# Lookup mime type by file extension
|
@@ -110,7 +111,7 @@ module Marcel
|
|
110
111
|
alias == eql?
|
111
112
|
|
112
113
|
def self.child?(child, parent)
|
113
|
-
child == parent ||
|
114
|
+
child == parent || TYPE_PARENTS[child]&.any? {|p| child?(p, parent) }
|
114
115
|
end
|
115
116
|
|
116
117
|
def self.magic_match(io, method)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
Marcel::MimeType.extend "text/plain", extensions: %w( txt asc )
|
2
4
|
|
3
5
|
Marcel::MimeType.extend "application/illustrator", parents: "application/pdf"
|
@@ -31,7 +33,7 @@ Marcel::MimeType.extend "application/vnd.apple.numbers", extensions: %w( numbers
|
|
31
33
|
Marcel::MimeType.extend "application/vnd.apple.keynote", extensions: %w( key ), parents: "application/zip"
|
32
34
|
|
33
35
|
Marcel::MimeType.extend "audio/aac", extensions: %w( aac ), parents: "audio/x-aac"
|
34
|
-
|
36
|
+
Marcel::MimeType.extend("audio/ogg", extensions: %w( ogg oga ), magic: [[0, 'OggS', [[29, 'vorbis']]]])
|
35
37
|
|
36
38
|
Marcel::MimeType.extend "image/vnd.dwg", magic: [[0, "AC10"]]
|
37
39
|
|
@@ -57,8 +59,8 @@ Marcel::MimeType.extend(
|
|
57
59
|
"application/vnd.ms-access",
|
58
60
|
extensions: %w( mdb mde accdb accde ),
|
59
61
|
magic: [
|
60
|
-
[0, "\x00\x01\x00\x00\x53\x74\x61\x6e\x64\x61\x72\x64\x20\x4a\x65\x74\x20\x44\x42"], # \x00\x01\
|
61
|
-
[0, "\x00\x01\x00\x00\x53\x74\x61\x6e\x64\x61\x72\x64\x20\x41\x43\x45\x20\x44\x42"], # \x00\x01\
|
62
|
+
[0, "\x00\x01\x00\x00\x53\x74\x61\x6e\x64\x61\x72\x64\x20\x4a\x65\x74\x20\x44\x42"], # "\x00\x01\x00Standard Jet DB"
|
63
|
+
[0, "\x00\x01\x00\x00\x53\x74\x61\x6e\x64\x61\x72\x64\x20\x41\x43\x45\x20\x44\x42"], # "\x00\x01\x00Standard ACE DB"
|
62
64
|
],
|
63
65
|
parents: "application/x-msaccess"
|
64
66
|
)
|
data/lib/marcel/mime_type.rb
CHANGED
@@ -1,30 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Marcel
|
2
4
|
class MimeType
|
3
5
|
BINARY = "application/octet-stream"
|
4
6
|
|
5
7
|
class << self
|
6
8
|
def extend(type, extensions: [], parents: [], magic: nil)
|
7
|
-
|
8
|
-
|
9
|
-
extensions
|
10
|
-
parents = (Array(parents) + existing[1]).uniq
|
11
|
-
comment = existing[2]
|
12
|
-
|
13
|
-
Magic.add(type, extensions: extensions, magic: magic, parents: parents, comment: comment)
|
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)
|
14
12
|
end
|
15
13
|
|
14
|
+
# Returns the most appropriate content type for the given file.
|
15
|
+
#
|
16
|
+
# The first argument should be a +Pathname+ or an +IO+. If it is a +Pathname+, the specified
|
17
|
+
# file will be opened first.
|
18
|
+
#
|
19
|
+
# Optional parameters:
|
20
|
+
# * +name+: file name, if known
|
21
|
+
# * +extension+: file extension, if known
|
22
|
+
# * +declared_type+: MIME type, if known
|
23
|
+
#
|
24
|
+
# The most appropriate type is determined by the following:
|
25
|
+
# * type declared by binary magic number data
|
26
|
+
# * type declared by the first of file name, file extension, or declared MIME type
|
27
|
+
#
|
28
|
+
# If no type can be determined, then +application/octet-stream+ is returned.
|
16
29
|
def for(pathname_or_io = nil, name: nil, extension: nil, declared_type: nil)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
if type_from_data
|
21
|
-
most_specific_type type_from_data, fallback_type
|
22
|
-
else
|
23
|
-
fallback_type
|
24
|
-
end
|
30
|
+
filename_type = for_name(name) || for_extension(extension)
|
31
|
+
most_specific_type for_data(pathname_or_io), for_declared_type(declared_type), filename_type, BINARY
|
25
32
|
end
|
26
33
|
|
27
34
|
private
|
35
|
+
|
28
36
|
def for_data(pathname_or_io)
|
29
37
|
if pathname_or_io
|
30
38
|
with_io(pathname_or_io) do |io|
|
@@ -52,11 +60,7 @@ module Marcel
|
|
52
60
|
end
|
53
61
|
|
54
62
|
def for_declared_type(declared_type)
|
55
|
-
|
56
|
-
|
57
|
-
if type != BINARY && !type.nil?
|
58
|
-
type.downcase
|
59
|
-
end
|
63
|
+
parse_media_type(declared_type)
|
60
64
|
end
|
61
65
|
|
62
66
|
def with_io(pathname_or_io, &block)
|
@@ -77,19 +81,9 @@ module Marcel
|
|
77
81
|
# For some document types (notably Microsoft Office) we recognise the main content
|
78
82
|
# type with magic, but not the specific subclass. In this situation, if we can get a more
|
79
83
|
# specific class using either the name or declared_type, we should use that in preference
|
80
|
-
def most_specific_type(
|
81
|
-
|
82
|
-
|
83
|
-
else
|
84
|
-
from_magic_type
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def root_types(type)
|
89
|
-
if TYPES[type].nil? || TYPES[type][1].empty?
|
90
|
-
[ type ]
|
91
|
-
else
|
92
|
-
TYPES[type][1].map {|t| root_types t }.flatten
|
84
|
+
def most_specific_type(*candidates)
|
85
|
+
candidates.compact.uniq.reduce do |type, candidate|
|
86
|
+
Marcel::Magic.child?(candidate, type) ? candidate : type
|
93
87
|
end
|
94
88
|
end
|
95
89
|
end
|