File: mime_type.rb

package info (click to toggle)
ruby-marcel 1.0.4%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 13,236 kB
  • sloc: xml: 7,071; ruby: 601; makefile: 7; javascript: 3
file content (98 lines) | stat: -rw-r--r-- 3,395 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# frozen_string_literal: true

module Marcel
  class MimeType
    BINARY = "application/octet-stream"

    class << self
      def extend(type, extensions: [], parents: [], magic: nil)
        extensions = (Array(extensions) + Array(Marcel::TYPE_EXTS[type])).uniq
        parents = (Array(parents) + Array(Marcel::TYPE_PARENTS[type])).uniq
        Magic.add(type, extensions: extensions, magic: magic, parents: parents)
      end

      # Returns the most appropriate content type for the given file.
      #
      # The first argument should be a +Pathname+ or an +IO+. If it is a +Pathname+, the specified
      # file will be opened first.
      #
      # Optional parameters:
      # * +name+: file name, if known
      # * +extension+: file extension, if known
      # * +declared_type+: MIME type, if known
      #
      # The most appropriate type is determined by the following:
      # * type declared by binary magic number data
      # * type declared by the first of file name, file extension, or declared MIME type
      #
      # If no type can be determined, then +application/octet-stream+ is returned.
      def for(pathname_or_io = nil, name: nil, extension: nil, declared_type: nil)
        filename_type = for_name(name) || for_extension(extension)
        most_specific_type for_data(pathname_or_io), for_declared_type(declared_type), filename_type, BINARY
      end

      private

        def for_data(pathname_or_io)
          if pathname_or_io
            with_io(pathname_or_io) do |io|
              if magic = Marcel::Magic.by_magic(io)
                magic.type.downcase
              end
            end
          end
        end

        def for_name(name)
          if name
            if magic = Marcel::Magic.by_path(name)
              magic.type.downcase
            end
          end
        end

        def for_extension(extension)
          if extension
            if magic = Marcel::Magic.by_extension(extension)
              magic.type.downcase
            end
          end
        end

        def for_declared_type(declared_type)
          type = parse_media_type(declared_type)

          # application/octet-stream is treated as an undeclared/missing type,
          # allowing the type to be inferred from the filename. If there's no
          # filename extension, then the type falls back to binary anyway.
          type unless type == BINARY
        end

        def with_io(pathname_or_io, &block)
          if defined?(Pathname) && pathname_or_io.is_a?(Pathname)
            pathname_or_io.open(&block)
          else
            yield pathname_or_io
          end
        end

        def parse_media_type(content_type)
          if content_type
            result = content_type.downcase.split(/[;,\s]/, 2).first
            result if result && result.index("/")
          end
        end

        # For some document types (notably Microsoft Office) we recognise the main content
        # type with magic, but not the specific subclass. In this situation, if we can get a more
        # specific class using either the name or declared_type, we should use that in preference
        def most_specific_type(*candidates)
          candidates.compact.uniq.reduce do |type, candidate|
            Marcel::Magic.child?(candidate, type) ? candidate : type
          end
        end
    end
  end
end

require 'marcel/mime_type/definitions'