1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
10 #include "base/containers/hash_tables.h"
11 #include "base/lazy_instance.h"
12 #include "base/logging.h"
13 #include "base/stl_util.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "build/build_config.h"
19 #include "net/base/mime_util.h"
20 #include "net/base/platform_mime_util.h"
21 #include "net/http/http_util.h"
27 // Singleton utility class for mime types.
28 class MimeUtil
: public PlatformMimeUtil
{
30 bool GetMimeTypeFromExtension(const base::FilePath::StringType
& ext
,
31 std::string
* mime_type
) const;
33 bool GetMimeTypeFromFile(const base::FilePath
& file_path
,
34 std::string
* mime_type
) const;
36 bool GetWellKnownMimeTypeFromExtension(const base::FilePath::StringType
& ext
,
37 std::string
* mime_type
) const;
39 bool MatchesMimeType(const std::string
&mime_type_pattern
,
40 const std::string
&mime_type
) const;
42 bool ParseMimeTypeWithoutParameter(const std::string
& type_string
,
43 std::string
* top_level_type
,
44 std::string
* subtype
) const;
46 bool IsValidTopLevelMimeType(const std::string
& type_string
) const;
49 friend struct base::DefaultLazyInstanceTraits
<MimeUtil
>;
53 bool GetMimeTypeFromExtensionHelper(const base::FilePath::StringType
& ext
,
54 bool include_platform_types
,
55 std::string
* mime_type
) const;
58 // This variable is Leaky because we need to access it from WorkerPool threads.
59 static base::LazyInstance
<MimeUtil
>::Leaky g_mime_util
=
60 LAZY_INSTANCE_INITIALIZER
;
62 static const MimeInfo primary_mappings
[] = {
63 { "text/html", "html,htm,shtml,shtm" },
64 { "text/css", "css" },
65 { "text/xml", "xml" },
66 { "image/gif", "gif" },
67 { "image/jpeg", "jpeg,jpg" },
68 { "image/webp", "webp" },
69 { "image/png", "png" },
70 { "video/mp4", "mp4,m4v" },
71 { "audio/x-m4a", "m4a" },
72 { "audio/mp3", "mp3" },
73 { "video/ogg", "ogv,ogm" },
74 { "audio/ogg", "ogg,oga,opus" },
75 { "video/webm", "webm" },
76 { "audio/webm", "webm" },
77 { "audio/wav", "wav" },
78 { "application/xhtml+xml", "xhtml,xht,xhtm" },
79 { "application/x-chrome-extension", "crx" },
80 { "multipart/related", "mhtml,mht" }
83 static const MimeInfo secondary_mappings
[] = {
84 { "application/octet-stream", "exe,com,bin" },
85 { "application/gzip", "gz" },
86 { "application/pdf", "pdf" },
87 { "application/postscript", "ps,eps,ai" },
88 { "application/javascript", "js" },
89 { "application/font-woff", "woff" },
90 { "image/bmp", "bmp" },
91 { "image/x-icon", "ico" },
92 { "image/vnd.microsoft.icon", "ico" },
93 { "image/jpeg", "jfif,pjpeg,pjp" },
94 { "image/tiff", "tiff,tif" },
95 { "image/x-xbitmap", "xbm" },
96 { "image/svg+xml", "svg,svgz" },
97 { "image/x-png", "png"},
98 { "message/rfc822", "eml" },
99 { "text/plain", "txt,text" },
100 { "text/html", "ehtml" },
101 { "application/rss+xml", "rss" },
102 { "application/rdf+xml", "rdf" },
103 { "text/xml", "xsl,xbl,xslt" },
104 { "application/vnd.mozilla.xul+xml", "xul" },
105 { "application/x-shockwave-flash", "swf,swl" },
106 { "application/pkcs7-mime", "p7m,p7c,p7z" },
107 { "application/pkcs7-signature", "p7s" },
108 { "application/x-mpegurl", "m3u8" },
111 const char* FindMimeType(const MimeInfo
* mappings
,
113 const std::string
& ext
) {
114 for (size_t i
= 0; i
< mappings_len
; ++i
) {
115 const char* extensions
= mappings
[i
].extensions
;
117 size_t end_pos
= strcspn(extensions
, ",");
118 if (end_pos
== ext
.size() &&
119 base::strncasecmp(extensions
, ext
.data(), ext
.size()) == 0)
120 return mappings
[i
].mime_type
;
121 extensions
+= end_pos
;
124 extensions
+= 1; // skip over comma
130 bool MimeUtil::GetMimeTypeFromExtension(const base::FilePath::StringType
& ext
,
131 string
* result
) const {
132 return GetMimeTypeFromExtensionHelper(ext
, true, result
);
135 bool MimeUtil::GetWellKnownMimeTypeFromExtension(
136 const base::FilePath::StringType
& ext
,
137 string
* result
) const {
138 return GetMimeTypeFromExtensionHelper(ext
, false, result
);
141 bool MimeUtil::GetMimeTypeFromFile(const base::FilePath
& file_path
,
142 string
* result
) const {
143 base::FilePath::StringType file_name_str
= file_path
.Extension();
144 if (file_name_str
.empty())
146 return GetMimeTypeFromExtension(file_name_str
.substr(1), result
);
149 bool MimeUtil::GetMimeTypeFromExtensionHelper(
150 const base::FilePath::StringType
& ext
,
151 bool include_platform_types
,
152 string
* result
) const {
153 // Avoids crash when unable to handle a long file path. See crbug.com/48733.
154 const unsigned kMaxFilePathSize
= 65536;
155 if (ext
.length() > kMaxFilePathSize
)
158 // Reject a string which contains null character.
159 base::FilePath::StringType::size_type nul_pos
=
160 ext
.find(FILE_PATH_LITERAL('\0'));
161 if (nul_pos
!= base::FilePath::StringType::npos
)
164 // We implement the same algorithm as Mozilla for mapping a file extension to
165 // a mime type. That is, we first check a hard-coded list (that cannot be
166 // overridden), and then if not found there, we defer to the system registry.
167 // Finally, we scan a secondary hard-coded list to catch types that we can
168 // deduce but that we also want to allow the OS to override.
170 base::FilePath
path_ext(ext
);
171 const string ext_narrow_str
= path_ext
.AsUTF8Unsafe();
172 const char* mime_type
= FindMimeType(
173 primary_mappings
, arraysize(primary_mappings
), ext_narrow_str
);
179 if (include_platform_types
&& GetPlatformMimeTypeFromExtension(ext
, result
))
182 mime_type
= FindMimeType(secondary_mappings
, arraysize(secondary_mappings
),
192 MimeUtil::MimeUtil() {
195 // Tests for MIME parameter equality. Each parameter in the |mime_type_pattern|
196 // must be matched by a parameter in the |mime_type|. If there are no
197 // parameters in the pattern, the match is a success.
199 // According rfc2045 keys of parameters are case-insensitive, while values may
200 // or may not be case-sensitive, but they are usually case-sensitive. So, this
201 // function matches values in *case-sensitive* manner, however note that this
202 // may produce some false negatives.
203 bool MatchesMimeTypeParameters(const std::string
& mime_type_pattern
,
204 const std::string
& mime_type
) {
205 typedef std::map
<std::string
, std::string
> StringPairMap
;
207 const std::string::size_type semicolon
= mime_type_pattern
.find(';');
208 const std::string::size_type test_semicolon
= mime_type
.find(';');
209 if (semicolon
!= std::string::npos
) {
210 if (test_semicolon
== std::string::npos
)
213 base::StringPairs pattern_parameters
;
214 base::SplitStringIntoKeyValuePairs(mime_type_pattern
.substr(semicolon
+ 1),
215 '=', ';', &pattern_parameters
);
216 base::StringPairs test_parameters
;
217 base::SplitStringIntoKeyValuePairs(mime_type
.substr(test_semicolon
+ 1),
218 '=', ';', &test_parameters
);
220 // Put the parameters to maps with the keys converted to lower case.
221 StringPairMap pattern_parameter_map
;
222 for (const auto& pair
: pattern_parameters
) {
223 pattern_parameter_map
[base::StringToLowerASCII(pair
.first
)] = pair
.second
;
226 StringPairMap test_parameter_map
;
227 for (const auto& pair
: test_parameters
) {
228 test_parameter_map
[base::StringToLowerASCII(pair
.first
)] = pair
.second
;
231 if (pattern_parameter_map
.size() > test_parameter_map
.size())
234 for (const auto& parameter_pair
: pattern_parameter_map
) {
235 const auto& test_parameter_pair_it
=
236 test_parameter_map
.find(parameter_pair
.first
);
237 if (test_parameter_pair_it
== test_parameter_map
.end())
239 if (parameter_pair
.second
!= test_parameter_pair_it
->second
)
247 // This comparison handles absolute maching and also basic
248 // wildcards. The plugin mime types could be:
253 // Also tests mime parameters -- all parameters in the pattern must be present
254 // in the tested type for a match to succeed.
255 bool MimeUtil::MatchesMimeType(const std::string
& mime_type_pattern
,
256 const std::string
& mime_type
) const {
257 if (mime_type_pattern
.empty())
260 std::string::size_type semicolon
= mime_type_pattern
.find(';');
261 const std::string
base_pattern(mime_type_pattern
.substr(0, semicolon
));
262 semicolon
= mime_type
.find(';');
263 const std::string
base_type(mime_type
.substr(0, semicolon
));
265 if (base_pattern
== "*" || base_pattern
== "*/*")
266 return MatchesMimeTypeParameters(mime_type_pattern
, mime_type
);
268 const std::string::size_type star
= base_pattern
.find('*');
269 if (star
== std::string::npos
) {
270 if (base_pattern
.size() == base_type
.size() &&
271 base::strncasecmp(base_pattern
.data(), base_type
.data(),
272 base_pattern
.size()) == 0) {
273 return MatchesMimeTypeParameters(mime_type_pattern
, mime_type
);
279 // Test length to prevent overlap between |left| and |right|.
280 if (base_type
.length() < base_pattern
.length() - 1)
283 const std::string
left(base_pattern
.substr(0, star
));
284 const std::string
right(base_pattern
.substr(star
+ 1));
286 if (!base::StartsWithASCII(base_type
, left
, false))
289 if (!right
.empty() && !base::EndsWith(base_type
, right
, false))
292 return MatchesMimeTypeParameters(mime_type_pattern
, mime_type
);
295 // See http://www.iana.org/assignments/media-types/media-types.xhtml
296 static const char* const legal_top_level_types
[] = {
308 bool MimeUtil::ParseMimeTypeWithoutParameter(
309 const std::string
& type_string
,
310 std::string
* top_level_type
,
311 std::string
* subtype
) const {
312 std::vector
<std::string
> components
;
313 base::SplitString(type_string
, '/', &components
);
314 if (components
.size() != 2 ||
315 !HttpUtil::IsToken(components
[0]) ||
316 !HttpUtil::IsToken(components
[1]))
320 *top_level_type
= components
[0];
322 *subtype
= components
[1];
326 bool MimeUtil::IsValidTopLevelMimeType(const std::string
& type_string
) const {
327 std::string lower_type
= base::StringToLowerASCII(type_string
);
328 for (size_t i
= 0; i
< arraysize(legal_top_level_types
); ++i
) {
329 if (lower_type
.compare(legal_top_level_types
[i
]) == 0)
333 return type_string
.size() > 2 &&
334 base::StartsWithASCII(type_string
, "x-", false);
337 //----------------------------------------------------------------------------
338 // Wrappers for the singleton
339 //----------------------------------------------------------------------------
341 bool GetMimeTypeFromExtension(const base::FilePath::StringType
& ext
,
342 std::string
* mime_type
) {
343 return g_mime_util
.Get().GetMimeTypeFromExtension(ext
, mime_type
);
346 bool GetMimeTypeFromFile(const base::FilePath
& file_path
,
347 std::string
* mime_type
) {
348 return g_mime_util
.Get().GetMimeTypeFromFile(file_path
, mime_type
);
351 bool GetWellKnownMimeTypeFromExtension(const base::FilePath::StringType
& ext
,
352 std::string
* mime_type
) {
353 return g_mime_util
.Get().GetWellKnownMimeTypeFromExtension(ext
, mime_type
);
356 bool GetPreferredExtensionForMimeType(const std::string
& mime_type
,
357 base::FilePath::StringType
* extension
) {
358 return g_mime_util
.Get().GetPreferredExtensionForMimeType(mime_type
,
362 bool MatchesMimeType(const std::string
& mime_type_pattern
,
363 const std::string
& mime_type
) {
364 return g_mime_util
.Get().MatchesMimeType(mime_type_pattern
, mime_type
);
367 bool ParseMimeTypeWithoutParameter(const std::string
& type_string
,
368 std::string
* top_level_type
,
369 std::string
* subtype
) {
370 return g_mime_util
.Get().ParseMimeTypeWithoutParameter(
371 type_string
, top_level_type
, subtype
);
374 bool IsValidTopLevelMimeType(const std::string
& type_string
) {
375 return g_mime_util
.Get().IsValidTopLevelMimeType(type_string
);
380 // From http://www.w3schools.com/media/media_mimeref.asp and
381 // http://plugindoc.mozdev.org/winmime.php
382 static const char* const kStandardImageTypes
[] = {
394 "image/vnd.microsoft.icon",
395 "image/x-cmu-raster",
398 "image/x-portable-anymap",
399 "image/x-portable-bitmap",
400 "image/x-portable-graymap",
401 "image/x-portable-pixmap",
405 "image/x-xwindowdump"
407 static const char* const kStandardAudioTypes
[] = {
423 "audio/vnd.rn-realaudio",
426 static const char* const kStandardVideoTypes
[] = {
443 struct StandardType
{
444 const char* const leading_mime_type
;
445 const char* const* standard_types
;
446 size_t standard_types_len
;
448 static const StandardType kStandardTypes
[] = {
449 { "image/", kStandardImageTypes
, arraysize(kStandardImageTypes
) },
450 { "audio/", kStandardAudioTypes
, arraysize(kStandardAudioTypes
) },
451 { "video/", kStandardVideoTypes
, arraysize(kStandardVideoTypes
) },
455 void GetExtensionsFromHardCodedMappings(
456 const MimeInfo
* mappings
,
458 const std::string
& leading_mime_type
,
459 base::hash_set
<base::FilePath::StringType
>* extensions
) {
460 for (size_t i
= 0; i
< mappings_len
; ++i
) {
461 if (base::StartsWithASCII(mappings
[i
].mime_type
, leading_mime_type
,
463 std::vector
<string
> this_extensions
;
464 base::SplitString(mappings
[i
].extensions
, ',', &this_extensions
);
465 for (size_t j
= 0; j
< this_extensions
.size(); ++j
) {
467 base::FilePath::StringType
extension(
468 base::UTF8ToWide(this_extensions
[j
]));
470 base::FilePath::StringType
extension(this_extensions
[j
]);
472 extensions
->insert(extension
);
478 void GetExtensionsHelper(
479 const char* const* standard_types
,
480 size_t standard_types_len
,
481 const std::string
& leading_mime_type
,
482 base::hash_set
<base::FilePath::StringType
>* extensions
) {
483 for (size_t i
= 0; i
< standard_types_len
; ++i
) {
484 g_mime_util
.Get().GetPlatformExtensionsForMimeType(standard_types
[i
],
488 // Also look up the extensions from hard-coded mappings in case that some
489 // supported extensions are not registered in the system registry, like ogg.
490 GetExtensionsFromHardCodedMappings(primary_mappings
,
491 arraysize(primary_mappings
),
495 GetExtensionsFromHardCodedMappings(secondary_mappings
,
496 arraysize(secondary_mappings
),
501 // Note that the elements in the source set will be appended to the target
504 void HashSetToVector(base::hash_set
<T
>* source
, std::vector
<T
>* target
) {
505 size_t old_target_size
= target
->size();
506 target
->resize(old_target_size
+ source
->size());
508 for (typename
base::hash_set
<T
>::iterator iter
= source
->begin();
509 iter
!= source
->end(); ++iter
, ++i
)
510 (*target
)[old_target_size
+ i
] = *iter
;
515 void GetExtensionsForMimeType(
516 const std::string
& unsafe_mime_type
,
517 std::vector
<base::FilePath::StringType
>* extensions
) {
518 if (unsafe_mime_type
== "*/*" || unsafe_mime_type
== "*")
521 const std::string mime_type
= base::StringToLowerASCII(unsafe_mime_type
);
522 base::hash_set
<base::FilePath::StringType
> unique_extensions
;
524 if (base::EndsWith(mime_type
, "/*", false)) {
525 std::string leading_mime_type
= mime_type
.substr(0, mime_type
.length() - 1);
527 // Find the matching StandardType from within kStandardTypes, or fall
528 // through to the last (default) StandardType.
529 const StandardType
* type
= NULL
;
530 for (size_t i
= 0; i
< arraysize(kStandardTypes
); ++i
) {
531 type
= &(kStandardTypes
[i
]);
532 if (type
->leading_mime_type
&&
533 leading_mime_type
== type
->leading_mime_type
)
537 GetExtensionsHelper(type
->standard_types
,
538 type
->standard_types_len
,
542 g_mime_util
.Get().GetPlatformExtensionsForMimeType(mime_type
,
545 // Also look up the extensions from hard-coded mappings in case that some
546 // supported extensions are not registered in the system registry, like ogg.
547 GetExtensionsFromHardCodedMappings(primary_mappings
,
548 arraysize(primary_mappings
),
552 GetExtensionsFromHardCodedMappings(secondary_mappings
,
553 arraysize(secondary_mappings
),
558 HashSetToVector(&unique_extensions
, extensions
);
561 void AddMultipartValueForUpload(const std::string
& value_name
,
562 const std::string
& value
,
563 const std::string
& mime_boundary
,
564 const std::string
& content_type
,
565 std::string
* post_data
) {
567 // First line is the boundary.
568 post_data
->append("--" + mime_boundary
+ "\r\n");
569 // Next line is the Content-disposition.
570 post_data
->append("Content-Disposition: form-data; name=\"" +
571 value_name
+ "\"\r\n");
572 if (!content_type
.empty()) {
573 // If Content-type is specified, the next line is that.
574 post_data
->append("Content-Type: " + content_type
+ "\r\n");
576 // Leave an empty line and append the value.
577 post_data
->append("\r\n" + value
+ "\r\n");
580 void AddMultipartFinalDelimiterForUpload(const std::string
& mime_boundary
,
581 std::string
* post_data
) {
583 post_data
->append("--" + mime_boundary
+ "--\r\n");