mime-types 3.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mime/types'
@@ -0,0 +1,587 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ module MIME
5
+ end
6
+
7
+ # The definition of one MIME content-type.
8
+ #
9
+ # == Usage
10
+ # require 'mime/types'
11
+ #
12
+ # plaintext = MIME::Types['text/plain'].first
13
+ # # returns [text/plain, text/plain]
14
+ # text = plaintext.first
15
+ # print text.media_type # => 'text'
16
+ # print text.sub_type # => 'plain'
17
+ #
18
+ # puts text.extensions.join(" ") # => 'asc txt c cc h hh cpp'
19
+ #
20
+ # puts text.encoding # => 8bit
21
+ # puts text.binary? # => false
22
+ # puts text.ascii? # => true
23
+ # puts text == 'text/plain' # => true
24
+ # puts MIME::Type.simplified('x-appl/x-zip') # => 'appl/zip'
25
+ #
26
+ # puts MIME::Types.any? { |type|
27
+ # type.content_type == 'text/plain'
28
+ # } # => true
29
+ # puts MIME::Types.all?(&:registered?)
30
+ # # => false
31
+ class MIME::Type
32
+ # Reflects a MIME content-type specification that is not correctly
33
+ # formatted (it isn't +type+/+subtype+).
34
+ class InvalidContentType < ArgumentError
35
+ # :stopdoc:
36
+ def initialize(type_string)
37
+ @type_string = type_string
38
+ end
39
+
40
+ def to_s
41
+ "Invalid Content-Type #{@type_string.inspect}"
42
+ end
43
+ # :startdoc:
44
+ end
45
+
46
+ # Reflects an unsupported MIME encoding.
47
+ class InvalidEncoding < ArgumentError
48
+ # :stopdoc:
49
+ def initialize(encoding)
50
+ @encoding = encoding
51
+ end
52
+
53
+ def to_s
54
+ "Invalid Encoding #{@encoding.inspect}"
55
+ end
56
+ # :startdoc:
57
+ end
58
+
59
+ # The released version of the mime-types library.
60
+ VERSION = '3.3.1'
61
+
62
+ include Comparable
63
+
64
+ # :stopdoc:
65
+ # TODO verify mime-type character restrictions; I am pretty sure that this is
66
+ # too wide open.
67
+ MEDIA_TYPE_RE = %r{([-\w.+]+)/([-\w.+]*)}.freeze
68
+ I18N_RE = /[^[:alnum:]]/.freeze
69
+ BINARY_ENCODINGS = %w(base64 8bit).freeze
70
+ ASCII_ENCODINGS = %w(7bit quoted-printable).freeze
71
+ # :startdoc:
72
+
73
+ private_constant :MEDIA_TYPE_RE, :I18N_RE, :BINARY_ENCODINGS,
74
+ :ASCII_ENCODINGS
75
+
76
+ # Builds a MIME::Type object from the +content_type+, a MIME Content Type
77
+ # value (e.g., 'text/plain' or 'applicaton/x-eruby'). The constructed object
78
+ # is yielded to an optional block for additional configuration, such as
79
+ # associating extensions and encoding information.
80
+ #
81
+ # * When provided a Hash or a MIME::Type, the MIME::Type will be
82
+ # constructed with #init_with.
83
+ # * When provided an Array, the MIME::Type will be constructed using
84
+ # the first element as the content type and the remaining flattened
85
+ # elements as extensions.
86
+ # * Otherwise, the content_type will be used as a string.
87
+ #
88
+ # Yields the newly constructed +self+ object.
89
+ def initialize(content_type) # :yields self:
90
+ @friendly = {}
91
+ @obsolete = @registered = false
92
+ @preferred_extension = @docs = @use_instead = nil
93
+ self.extensions = []
94
+
95
+ case content_type
96
+ when Hash
97
+ init_with(content_type)
98
+ when Array
99
+ self.content_type = content_type.shift
100
+ self.extensions = content_type.flatten
101
+ when MIME::Type
102
+ init_with(content_type.to_h)
103
+ else
104
+ self.content_type = content_type
105
+ end
106
+
107
+ self.encoding ||= :default
108
+ self.xrefs ||= {}
109
+
110
+ yield self if block_given?
111
+ end
112
+
113
+ # Indicates that a MIME type is like another type. This differs from
114
+ # <tt>==</tt> because <tt>x-</tt> prefixes are removed for this comparison.
115
+ def like?(other)
116
+ other = if other.respond_to?(:simplified)
117
+ MIME::Type.simplified(other.simplified, remove_x_prefix: true)
118
+ else
119
+ MIME::Type.simplified(other.to_s, remove_x_prefix: true)
120
+ end
121
+ MIME::Type.simplified(simplified, remove_x_prefix: true) == other
122
+ end
123
+
124
+ # Compares the +other+ MIME::Type against the exact content type or the
125
+ # simplified type (the simplified type will be used if comparing against
126
+ # something that can be treated as a String with #to_s). In comparisons, this
127
+ # is done against the lowercase version of the MIME::Type.
128
+ def <=>(other)
129
+ if other.nil?
130
+ -1
131
+ elsif other.respond_to?(:simplified)
132
+ simplified <=> other.simplified
133
+ else
134
+ filtered = 'silent' if other == :silent
135
+ filtered ||= 'true' if other == true
136
+ filtered ||= other.to_s
137
+
138
+ simplified <=> MIME::Type.simplified(filtered)
139
+ end
140
+ end
141
+
142
+ # Compares the +other+ MIME::Type based on how reliable it is before doing a
143
+ # normal <=> comparison. Used by MIME::Types#[] to sort types. The
144
+ # comparisons involved are:
145
+ #
146
+ # 1. self.simplified <=> other.simplified (ensures that we
147
+ # don't try to compare different types)
148
+ # 2. IANA-registered definitions < other definitions.
149
+ # 3. Complete definitions < incomplete definitions.
150
+ # 4. Current definitions < obsolete definitions.
151
+ # 5. Obselete with use-instead names < obsolete without.
152
+ # 6. Obsolete use-instead definitions are compared.
153
+ #
154
+ # While this method is public, its use is strongly discouraged by consumers
155
+ # of mime-types. In mime-types 3, this method is likely to see substantial
156
+ # revision and simplification to ensure current registered content types sort
157
+ # before unregistered or obsolete content types.
158
+ def priority_compare(other)
159
+ pc = simplified <=> other.simplified
160
+ if pc.zero? || !(extensions & other.extensions).empty?
161
+ pc = if (reg = registered?) != other.registered?
162
+ reg ? -1 : 1 # registered < unregistered
163
+ elsif (comp = complete?) != other.complete?
164
+ comp ? -1 : 1 # complete < incomplete
165
+ elsif (obs = obsolete?) != other.obsolete?
166
+ obs ? 1 : -1 # current < obsolete
167
+ elsif obs and ((ui = use_instead) != (oui = other.use_instead))
168
+ if ui.nil?
169
+ 1
170
+ elsif oui.nil?
171
+ -1
172
+ else
173
+ ui <=> oui
174
+ end
175
+ else
176
+ 0
177
+ end
178
+ end
179
+
180
+ pc
181
+ end
182
+
183
+ # Returns +true+ if the +other+ object is a MIME::Type and the content types
184
+ # match.
185
+ def eql?(other)
186
+ other.kind_of?(MIME::Type) and self == other
187
+ end
188
+
189
+ # Returns the whole MIME content-type string.
190
+ #
191
+ # The content type is a presentation value from the MIME type registry and
192
+ # should not be used for comparison. The case of the content type is
193
+ # preserved, and extension markers (<tt>x-</tt>) are kept.
194
+ #
195
+ # text/plain => text/plain
196
+ # x-chemical/x-pdb => x-chemical/x-pdb
197
+ # audio/QCELP => audio/QCELP
198
+ attr_reader :content_type
199
+ # A simplified form of the MIME content-type string, suitable for
200
+ # case-insensitive comparison, with any extension markers (<tt>x-</tt)
201
+ # removed and converted to lowercase.
202
+ #
203
+ # text/plain => text/plain
204
+ # x-chemical/x-pdb => x-chemical/x-pdb
205
+ # audio/QCELP => audio/qcelp
206
+ attr_reader :simplified
207
+ # Returns the media type of the simplified MIME::Type.
208
+ #
209
+ # text/plain => text
210
+ # x-chemical/x-pdb => x-chemical
211
+ # audio/QCELP => audio
212
+ attr_reader :media_type
213
+ # Returns the media type of the unmodified MIME::Type.
214
+ #
215
+ # text/plain => text
216
+ # x-chemical/x-pdb => x-chemical
217
+ # audio/QCELP => audio
218
+ attr_reader :raw_media_type
219
+ # Returns the sub-type of the simplified MIME::Type.
220
+ #
221
+ # text/plain => plain
222
+ # x-chemical/x-pdb => pdb
223
+ # audio/QCELP => QCELP
224
+ attr_reader :sub_type
225
+ # Returns the media type of the unmodified MIME::Type.
226
+ #
227
+ # text/plain => plain
228
+ # x-chemical/x-pdb => x-pdb
229
+ # audio/QCELP => qcelp
230
+ attr_reader :raw_sub_type
231
+
232
+ ##
233
+ # The list of extensions which are known to be used for this MIME::Type.
234
+ # Non-array values will be coerced into an array with #to_a. Array values
235
+ # will be flattened, +nil+ values removed, and made unique.
236
+ #
237
+ # :attr_accessor: extensions
238
+ def extensions
239
+ @extensions.to_a
240
+ end
241
+
242
+ ##
243
+ def extensions=(value) # :nodoc:
244
+ @extensions = Set[*Array(value).flatten.compact].freeze
245
+ MIME::Types.send(:reindex_extensions, self)
246
+ end
247
+
248
+ # Merge the +extensions+ provided into this MIME::Type. The extensions added
249
+ # will be merged uniquely.
250
+ def add_extensions(*extensions)
251
+ self.extensions += extensions
252
+ end
253
+
254
+ ##
255
+ # The preferred extension for this MIME type. If one is not set and there are
256
+ # exceptions defined, the first extension will be used.
257
+ #
258
+ # When setting #preferred_extensions, if #extensions does not contain this
259
+ # extension, this will be added to #xtensions.
260
+ #
261
+ # :attr_accessor: preferred_extension
262
+
263
+ ##
264
+ def preferred_extension
265
+ @preferred_extension || extensions.first
266
+ end
267
+
268
+ ##
269
+ def preferred_extension=(value) # :nodoc:
270
+ add_extensions(value) if value
271
+ @preferred_extension = value
272
+ end
273
+
274
+ ##
275
+ # The encoding (+7bit+, +8bit+, <tt>quoted-printable</tt>, or +base64+)
276
+ # required to transport the data of this content type safely across a
277
+ # network, which roughly corresponds to Content-Transfer-Encoding. A value of
278
+ # +nil+ or <tt>:default</tt> will reset the #encoding to the
279
+ # #default_encoding for the MIME::Type. Raises ArgumentError if the encoding
280
+ # provided is invalid.
281
+ #
282
+ # If the encoding is not provided on construction, this will be either
283
+ # 'quoted-printable' (for text/* media types) and 'base64' for eveything
284
+ # else.
285
+ #
286
+ # :attr_accessor: encoding
287
+
288
+ ##
289
+ attr_reader :encoding
290
+
291
+ ##
292
+ def encoding=(enc) # :nodoc:
293
+ if enc.nil? or enc == :default
294
+ @encoding = default_encoding
295
+ elsif BINARY_ENCODINGS.include?(enc) or ASCII_ENCODINGS.include?(enc)
296
+ @encoding = enc
297
+ else
298
+ fail InvalidEncoding, enc
299
+ end
300
+ end
301
+
302
+ # Returns the default encoding for the MIME::Type based on the media type.
303
+ def default_encoding
304
+ @media_type == 'text' ? 'quoted-printable' : 'base64'
305
+ end
306
+
307
+ ##
308
+ # Returns the media type or types that should be used instead of this media
309
+ # type, if it is obsolete. If there is no replacement media type, or it is
310
+ # not obsolete, +nil+ will be returned.
311
+ #
312
+ # :attr_accessor: use_instead
313
+
314
+ ##
315
+ def use_instead
316
+ obsolete? ? @use_instead : nil
317
+ end
318
+
319
+ ##
320
+ attr_writer :use_instead
321
+
322
+ # Returns +true+ if the media type is obsolete.
323
+ attr_accessor :obsolete
324
+ alias obsolete? obsolete
325
+
326
+ # The documentation for this MIME::Type.
327
+ attr_accessor :docs
328
+
329
+ # A friendly short description for this MIME::Type.
330
+ #
331
+ # call-seq:
332
+ # text_plain.friendly # => "Text File"
333
+ # text_plain.friendly('en') # => "Text File"
334
+ def friendly(lang = 'en')
335
+ @friendly ||= {}
336
+
337
+ case lang
338
+ when String, Symbol
339
+ @friendly[lang.to_s]
340
+ when Array
341
+ @friendly.update(Hash[*lang])
342
+ when Hash
343
+ @friendly.update(lang)
344
+ else
345
+ fail ArgumentError,
346
+ "Expected a language or translation set, not #{lang.inspect}"
347
+ end
348
+ end
349
+
350
+ # A key suitable for use as a lookup key for translations, such as with
351
+ # the I18n library.
352
+ #
353
+ # call-seq:
354
+ # text_plain.i18n_key # => "text.plain"
355
+ # 3gpp_xml.i18n_key # => "application.vnd-3gpp-bsf-xml"
356
+ # # from application/vnd.3gpp.bsf+xml
357
+ # x_msword.i18n_key # => "application.word"
358
+ # # from application/x-msword
359
+ attr_reader :i18n_key
360
+
361
+ ##
362
+ # The cross-references list for this MIME::Type.
363
+ #
364
+ # :attr_accessor: xrefs
365
+
366
+ ##
367
+ attr_reader :xrefs
368
+
369
+ ##
370
+ def xrefs=(xrefs) # :nodoc:
371
+ @xrefs = MIME::Types::Container.new(xrefs)
372
+ end
373
+
374
+ # The decoded cross-reference URL list for this MIME::Type.
375
+ def xref_urls
376
+ xrefs.flat_map { |type, values|
377
+ name = :"xref_url_for_#{type.tr('-', '_')}"
378
+ respond_to?(name, true) and xref_map(values, name) or values.to_a
379
+ }
380
+ end
381
+
382
+ # Indicates whether the MIME type has been registered with IANA.
383
+ attr_accessor :registered
384
+ alias registered? registered
385
+
386
+ # MIME types can be specified to be sent across a network in particular
387
+ # formats. This method returns +true+ when the MIME::Type encoding is set
388
+ # to <tt>base64</tt>.
389
+ def binary?
390
+ BINARY_ENCODINGS.include?(encoding)
391
+ end
392
+
393
+ # MIME types can be specified to be sent across a network in particular
394
+ # formats. This method returns +false+ when the MIME::Type encoding is
395
+ # set to <tt>base64</tt>.
396
+ def ascii?
397
+ ASCII_ENCODINGS.include?(encoding)
398
+ end
399
+
400
+ # Indicateswhether the MIME type is declared as a signature type.
401
+ attr_accessor :signature
402
+ alias signature? signature
403
+
404
+ # Returns +true+ if the MIME::Type specifies an extension list,
405
+ # indicating that it is a complete MIME::Type.
406
+ def complete?
407
+ !@extensions.empty?
408
+ end
409
+
410
+ # Returns the MIME::Type as a string.
411
+ def to_s
412
+ content_type
413
+ end
414
+
415
+ # Returns the MIME::Type as a string for implicit conversions. This allows
416
+ # MIME::Type objects to appear on either side of a comparison.
417
+ #
418
+ # 'text/plain' == MIME::Type.new('text/plain')
419
+ def to_str
420
+ content_type
421
+ end
422
+
423
+ # Converts the MIME::Type to a JSON string.
424
+ def to_json(*args)
425
+ require 'json'
426
+ to_h.to_json(*args)
427
+ end
428
+
429
+ # Converts the MIME::Type to a hash. The output of this method can also be
430
+ # used to initialize a MIME::Type.
431
+ def to_h
432
+ encode_with({})
433
+ end
434
+
435
+ # Populates the +coder+ with attributes about this record for
436
+ # serialization. The structure of +coder+ should match the structure used
437
+ # with #init_with.
438
+ #
439
+ # This method should be considered a private implementation detail.
440
+ def encode_with(coder)
441
+ coder['content-type'] = @content_type
442
+ coder['docs'] = @docs unless @docs.nil? or @docs.empty?
443
+ coder['friendly'] = @friendly unless @friendly.nil? or @friendly.empty?
444
+ coder['encoding'] = @encoding
445
+ coder['extensions'] = @extensions.to_a unless @extensions.empty?
446
+ coder['preferred-extension'] = @preferred_extension if @preferred_extension
447
+ if obsolete?
448
+ coder['obsolete'] = obsolete?
449
+ coder['use-instead'] = use_instead if use_instead
450
+ end
451
+ unless xrefs.empty?
452
+ {}.tap do |hash|
453
+ xrefs.each do |k, v|
454
+ hash[k] = v.to_a.sort
455
+ end
456
+ coder['xrefs'] = hash
457
+ end
458
+ end
459
+ coder['registered'] = registered?
460
+ coder['signature'] = signature? if signature?
461
+ coder
462
+ end
463
+
464
+ # Initialize an empty object from +coder+, which must contain the
465
+ # attributes necessary for initializing an empty object.
466
+ #
467
+ # This method should be considered a private implementation detail.
468
+ def init_with(coder)
469
+ self.content_type = coder['content-type']
470
+ self.docs = coder['docs'] || ''
471
+ self.encoding = coder['encoding']
472
+ self.extensions = coder['extensions'] || []
473
+ self.preferred_extension = coder['preferred-extension']
474
+ self.obsolete = coder['obsolete'] || false
475
+ self.registered = coder['registered'] || false
476
+ self.signature = coder['signature']
477
+ self.xrefs = coder['xrefs'] || {}
478
+ self.use_instead = coder['use-instead']
479
+
480
+ friendly(coder['friendly'] || {})
481
+ end
482
+
483
+ def inspect # :nodoc:
484
+ # We are intentionally lying here because MIME::Type::Columnar is an
485
+ # implementation detail.
486
+ "#<MIME::Type: #{self}>"
487
+ end
488
+
489
+ class << self
490
+ # MIME media types are case-insensitive, but are typically presented in a
491
+ # case-preserving format in the type registry. This method converts
492
+ # +content_type+ to lowercase.
493
+ #
494
+ # In previous versions of mime-types, this would also remove any extension
495
+ # prefix (<tt>x-</tt>). This is no longer default behaviour, but may be
496
+ # provided by providing a truth value to +remove_x_prefix+.
497
+ def simplified(content_type, remove_x_prefix: false)
498
+ simplify_matchdata(match(content_type), remove_x_prefix)
499
+ end
500
+
501
+ # Converts a provided +content_type+ into a translation key suitable for
502
+ # use with the I18n library.
503
+ def i18n_key(content_type)
504
+ simplify_matchdata(match(content_type), joiner: '.') { |e|
505
+ e.gsub!(I18N_RE, '-')
506
+ }
507
+ end
508
+
509
+ # Return a +MatchData+ object of the +content_type+ against pattern of
510
+ # media types.
511
+ def match(content_type)
512
+ case content_type
513
+ when MatchData
514
+ content_type
515
+ else
516
+ MEDIA_TYPE_RE.match(content_type)
517
+ end
518
+ end
519
+
520
+ private
521
+
522
+ def simplify_matchdata(matchdata, remove_x = false, joiner: '/')
523
+ return nil unless matchdata
524
+
525
+ matchdata.captures.map { |e|
526
+ e.downcase!
527
+ e.sub!(/^x-/, '') if remove_x
528
+ yield e if block_given?
529
+ e
530
+ }.join(joiner)
531
+ end
532
+ end
533
+
534
+ private
535
+
536
+ def content_type=(type_string)
537
+ match = MEDIA_TYPE_RE.match(type_string)
538
+ fail InvalidContentType, type_string if match.nil?
539
+
540
+ @content_type = intern_string(type_string)
541
+ @raw_media_type, @raw_sub_type = match.captures
542
+ @simplified = intern_string(MIME::Type.simplified(match))
543
+ @i18n_key = intern_string(MIME::Type.i18n_key(match))
544
+ @media_type, @sub_type = MEDIA_TYPE_RE.match(@simplified).captures
545
+
546
+ @raw_media_type = intern_string(@raw_media_type)
547
+ @raw_sub_type = intern_string(@raw_sub_type)
548
+ @media_type = intern_string(@media_type)
549
+ @sub_type = intern_string(@sub_type)
550
+ end
551
+
552
+ if String.method_defined?(:-@)
553
+ def intern_string(string)
554
+ -string
555
+ end
556
+ else
557
+ # MRI 2.2 and older don't have a method for string interning,
558
+ # so we simply freeze them for keeping a similar interface
559
+ def intern_string(string)
560
+ string.freeze
561
+ end
562
+ end
563
+
564
+ def xref_map(values, helper)
565
+ values.map { |value| send(helper, value) }
566
+ end
567
+
568
+ def xref_url_for_rfc(value)
569
+ 'http://www.iana.org/go/%s' % value
570
+ end
571
+
572
+ def xref_url_for_draft(value)
573
+ 'http://www.iana.org/go/%s' % value.sub(/\ARFC/, 'draft')
574
+ end
575
+
576
+ def xref_url_for_rfc_errata(value)
577
+ 'http://www.rfc-editor.org/errata_search.php?eid=%s' % value
578
+ end
579
+
580
+ def xref_url_for_person(value)
581
+ 'http://www.iana.org/assignments/media-types/media-types.xhtml#%s' % value
582
+ end
583
+
584
+ def xref_url_for_template(value)
585
+ 'http://www.iana.org/assignments/media-types/%s' % value
586
+ end
587
+ end