// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include "base/containers/hash_tables.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "net/base/mime_util.h" #include "net/base/platform_mime_util.h" #include "net/http/http_util.h" using std::string; namespace net { // Singleton utility class for mime types. class MimeUtil : public PlatformMimeUtil { public: bool GetMimeTypeFromExtension(const base::FilePath::StringType& ext, std::string* mime_type) const; bool GetMimeTypeFromFile(const base::FilePath& file_path, std::string* mime_type) const; bool GetWellKnownMimeTypeFromExtension(const base::FilePath::StringType& ext, std::string* mime_type) const; bool MatchesMimeType(const std::string &mime_type_pattern, const std::string &mime_type) const; bool ParseMimeTypeWithoutParameter(const std::string& type_string, std::string* top_level_type, std::string* subtype) const; bool IsValidTopLevelMimeType(const std::string& type_string) const; private: friend struct base::DefaultLazyInstanceTraits; MimeUtil(); bool GetMimeTypeFromExtensionHelper(const base::FilePath::StringType& ext, bool include_platform_types, std::string* mime_type) const; }; // class MimeUtil // This variable is Leaky because we need to access it from WorkerPool threads. static base::LazyInstance::Leaky g_mime_util = LAZY_INSTANCE_INITIALIZER; static const MimeInfo primary_mappings[] = { { "text/html", "html,htm,shtml,shtm" }, { "text/css", "css" }, { "text/xml", "xml" }, { "image/gif", "gif" }, { "image/jpeg", "jpeg,jpg" }, { "image/webp", "webp" }, { "image/png", "png" }, { "video/mp4", "mp4,m4v" }, { "audio/x-m4a", "m4a" }, { "audio/mp3", "mp3" }, { "video/ogg", "ogv,ogm" }, { "audio/ogg", "ogg,oga,opus" }, { "video/webm", "webm" }, { "audio/webm", "webm" }, { "audio/wav", "wav" }, { "application/xhtml+xml", "xhtml,xht,xhtm" }, { "application/x-chrome-extension", "crx" }, { "multipart/related", "mhtml,mht" } }; static const MimeInfo secondary_mappings[] = { { "application/octet-stream", "exe,com,bin" }, { "application/gzip", "gz" }, { "application/pdf", "pdf" }, { "application/postscript", "ps,eps,ai" }, { "application/javascript", "js" }, { "application/font-woff", "woff" }, { "image/bmp", "bmp" }, { "image/x-icon", "ico" }, { "image/vnd.microsoft.icon", "ico" }, { "image/jpeg", "jfif,pjpeg,pjp" }, { "image/tiff", "tiff,tif" }, { "image/x-xbitmap", "xbm" }, { "image/svg+xml", "svg,svgz" }, { "image/x-png", "png"}, { "message/rfc822", "eml" }, { "text/plain", "txt,text" }, { "text/html", "ehtml" }, { "application/rss+xml", "rss" }, { "application/rdf+xml", "rdf" }, { "text/xml", "xsl,xbl,xslt" }, { "application/vnd.mozilla.xul+xml", "xul" }, { "application/x-shockwave-flash", "swf,swl" }, { "application/pkcs7-mime", "p7m,p7c,p7z" }, { "application/pkcs7-signature", "p7s" }, { "application/x-mpegurl", "m3u8" }, }; const char* FindMimeType(const MimeInfo* mappings, size_t mappings_len, const char* ext) { size_t ext_len = strlen(ext); for (size_t i = 0; i < mappings_len; ++i) { const char* extensions = mappings[i].extensions; for (;;) { size_t end_pos = strcspn(extensions, ","); if (end_pos == ext_len && base::strncasecmp(extensions, ext, ext_len) == 0) return mappings[i].mime_type; extensions += end_pos; if (!*extensions) break; extensions += 1; // skip over comma } } return NULL; } bool MimeUtil::GetMimeTypeFromExtension(const base::FilePath::StringType& ext, string* result) const { return GetMimeTypeFromExtensionHelper(ext, true, result); } bool MimeUtil::GetWellKnownMimeTypeFromExtension( const base::FilePath::StringType& ext, string* result) const { return GetMimeTypeFromExtensionHelper(ext, false, result); } bool MimeUtil::GetMimeTypeFromFile(const base::FilePath& file_path, string* result) const { base::FilePath::StringType file_name_str = file_path.Extension(); if (file_name_str.empty()) return false; return GetMimeTypeFromExtension(file_name_str.substr(1), result); } bool MimeUtil::GetMimeTypeFromExtensionHelper( const base::FilePath::StringType& ext, bool include_platform_types, string* result) const { // Avoids crash when unable to handle a long file path. See crbug.com/48733. const unsigned kMaxFilePathSize = 65536; if (ext.length() > kMaxFilePathSize) return false; // Reject a string which contains null character. base::FilePath::StringType::size_type nul_pos = ext.find(FILE_PATH_LITERAL('\0')); if (nul_pos != base::FilePath::StringType::npos) return false; // We implement the same algorithm as Mozilla for mapping a file extension to // a mime type. That is, we first check a hard-coded list (that cannot be // overridden), and then if not found there, we defer to the system registry. // Finally, we scan a secondary hard-coded list to catch types that we can // deduce but that we also want to allow the OS to override. base::FilePath path_ext(ext); const string ext_narrow_str = path_ext.AsUTF8Unsafe(); const char* mime_type = FindMimeType(primary_mappings, arraysize(primary_mappings), ext_narrow_str.c_str()); if (mime_type) { *result = mime_type; return true; } if (include_platform_types && GetPlatformMimeTypeFromExtension(ext, result)) return true; mime_type = FindMimeType(secondary_mappings, arraysize(secondary_mappings), ext_narrow_str.c_str()); if (mime_type) { *result = mime_type; return true; } return false; } MimeUtil::MimeUtil() { } // Tests for MIME parameter equality. Each parameter in the |mime_type_pattern| // must be matched by a parameter in the |mime_type|. If there are no // parameters in the pattern, the match is a success. // // According rfc2045 keys of parameters are case-insensitive, while values may // or may not be case-sensitive, but they are usually case-sensitive. So, this // function matches values in *case-sensitive* manner, however note that this // may produce some false negatives. bool MatchesMimeTypeParameters(const std::string& mime_type_pattern, const std::string& mime_type) { typedef std::map StringPairMap; const std::string::size_type semicolon = mime_type_pattern.find(';'); const std::string::size_type test_semicolon = mime_type.find(';'); if (semicolon != std::string::npos) { if (test_semicolon == std::string::npos) return false; base::StringPairs pattern_parameters; base::SplitStringIntoKeyValuePairs(mime_type_pattern.substr(semicolon + 1), '=', ';', &pattern_parameters); base::StringPairs test_parameters; base::SplitStringIntoKeyValuePairs(mime_type.substr(test_semicolon + 1), '=', ';', &test_parameters); // Put the parameters to maps with the keys converted to lower case. StringPairMap pattern_parameter_map; for (const auto& pair : pattern_parameters) { pattern_parameter_map[base::StringToLowerASCII(pair.first)] = pair.second; } StringPairMap test_parameter_map; for (const auto& pair : test_parameters) { test_parameter_map[base::StringToLowerASCII(pair.first)] = pair.second; } if (pattern_parameter_map.size() > test_parameter_map.size()) return false; for (const auto& parameter_pair : pattern_parameter_map) { const auto& test_parameter_pair_it = test_parameter_map.find(parameter_pair.first); if (test_parameter_pair_it == test_parameter_map.end()) return false; if (parameter_pair.second != test_parameter_pair_it->second) return false; } } return true; } // This comparison handles absolute maching and also basic // wildcards. The plugin mime types could be: // application/x-foo // application/* // application/*+xml // * // Also tests mime parameters -- all parameters in the pattern must be present // in the tested type for a match to succeed. bool MimeUtil::MatchesMimeType(const std::string& mime_type_pattern, const std::string& mime_type) const { if (mime_type_pattern.empty()) return false; std::string::size_type semicolon = mime_type_pattern.find(';'); const std::string base_pattern(mime_type_pattern.substr(0, semicolon)); semicolon = mime_type.find(';'); const std::string base_type(mime_type.substr(0, semicolon)); if (base_pattern == "*" || base_pattern == "*/*") return MatchesMimeTypeParameters(mime_type_pattern, mime_type); const std::string::size_type star = base_pattern.find('*'); if (star == std::string::npos) { if (base_pattern.size() == base_type.size() && base::strncasecmp(base_pattern.c_str(), base_type.c_str(), base_pattern.size()) == 0) { return MatchesMimeTypeParameters(mime_type_pattern, mime_type); } else { return false; } } // Test length to prevent overlap between |left| and |right|. if (base_type.length() < base_pattern.length() - 1) return false; const std::string left(base_pattern.substr(0, star)); const std::string right(base_pattern.substr(star + 1)); if (!StartsWithASCII(base_type, left, false)) return false; if (!right.empty() && !EndsWith(base_type, right, false)) return false; return MatchesMimeTypeParameters(mime_type_pattern, mime_type); } // See http://www.iana.org/assignments/media-types/media-types.xhtml static const char* const legal_top_level_types[] = { "application", "audio", "example", "image", "message", "model", "multipart", "text", "video", }; bool MimeUtil::ParseMimeTypeWithoutParameter( const std::string& type_string, std::string* top_level_type, std::string* subtype) const { std::vector components; base::SplitString(type_string, '/', &components); if (components.size() != 2 || !HttpUtil::IsToken(components[0]) || !HttpUtil::IsToken(components[1])) return false; if (top_level_type) *top_level_type = components[0]; if (subtype) *subtype = components[1]; return true; } bool MimeUtil::IsValidTopLevelMimeType(const std::string& type_string) const { std::string lower_type = base::StringToLowerASCII(type_string); for (size_t i = 0; i < arraysize(legal_top_level_types); ++i) { if (lower_type.compare(legal_top_level_types[i]) == 0) return true; } return type_string.size() > 2 && StartsWithASCII(type_string, "x-", false); } //---------------------------------------------------------------------------- // Wrappers for the singleton //---------------------------------------------------------------------------- bool GetMimeTypeFromExtension(const base::FilePath::StringType& ext, std::string* mime_type) { return g_mime_util.Get().GetMimeTypeFromExtension(ext, mime_type); } bool GetMimeTypeFromFile(const base::FilePath& file_path, std::string* mime_type) { return g_mime_util.Get().GetMimeTypeFromFile(file_path, mime_type); } bool GetWellKnownMimeTypeFromExtension(const base::FilePath::StringType& ext, std::string* mime_type) { return g_mime_util.Get().GetWellKnownMimeTypeFromExtension(ext, mime_type); } bool GetPreferredExtensionForMimeType(const std::string& mime_type, base::FilePath::StringType* extension) { return g_mime_util.Get().GetPreferredExtensionForMimeType(mime_type, extension); } bool MatchesMimeType(const std::string& mime_type_pattern, const std::string& mime_type) { return g_mime_util.Get().MatchesMimeType(mime_type_pattern, mime_type); } bool ParseMimeTypeWithoutParameter(const std::string& type_string, std::string* top_level_type, std::string* subtype) { return g_mime_util.Get().ParseMimeTypeWithoutParameter( type_string, top_level_type, subtype); } bool IsValidTopLevelMimeType(const std::string& type_string) { return g_mime_util.Get().IsValidTopLevelMimeType(type_string); } namespace { // From http://www.w3schools.com/media/media_mimeref.asp and // http://plugindoc.mozdev.org/winmime.php static const char* const kStandardImageTypes[] = { "image/bmp", "image/cis-cod", "image/gif", "image/ief", "image/jpeg", "image/webp", "image/pict", "image/pipeg", "image/png", "image/svg+xml", "image/tiff", "image/vnd.microsoft.icon", "image/x-cmu-raster", "image/x-cmx", "image/x-icon", "image/x-portable-anymap", "image/x-portable-bitmap", "image/x-portable-graymap", "image/x-portable-pixmap", "image/x-rgb", "image/x-xbitmap", "image/x-xpixmap", "image/x-xwindowdump" }; static const char* const kStandardAudioTypes[] = { "audio/aac", "audio/aiff", "audio/amr", "audio/basic", "audio/midi", "audio/mp3", "audio/mp4", "audio/mpeg", "audio/mpeg3", "audio/ogg", "audio/vorbis", "audio/wav", "audio/webm", "audio/x-m4a", "audio/x-ms-wma", "audio/vnd.rn-realaudio", "audio/vnd.wave" }; static const char* const kStandardVideoTypes[] = { "video/avi", "video/divx", "video/flc", "video/mp4", "video/mpeg", "video/ogg", "video/quicktime", "video/sd-video", "video/webm", "video/x-dv", "video/x-m4v", "video/x-mpeg", "video/x-ms-asf", "video/x-ms-wmv" }; struct StandardType { const char* const leading_mime_type; const char* const* standard_types; size_t standard_types_len; }; static const StandardType kStandardTypes[] = { { "image/", kStandardImageTypes, arraysize(kStandardImageTypes) }, { "audio/", kStandardAudioTypes, arraysize(kStandardAudioTypes) }, { "video/", kStandardVideoTypes, arraysize(kStandardVideoTypes) }, { NULL, NULL, 0 } }; void GetExtensionsFromHardCodedMappings( const MimeInfo* mappings, size_t mappings_len, const std::string& leading_mime_type, base::hash_set* extensions) { for (size_t i = 0; i < mappings_len; ++i) { if (StartsWithASCII(mappings[i].mime_type, leading_mime_type, false)) { std::vector this_extensions; base::SplitString(mappings[i].extensions, ',', &this_extensions); for (size_t j = 0; j < this_extensions.size(); ++j) { #if defined(OS_WIN) base::FilePath::StringType extension( base::UTF8ToWide(this_extensions[j])); #else base::FilePath::StringType extension(this_extensions[j]); #endif extensions->insert(extension); } } } } void GetExtensionsHelper( const char* const* standard_types, size_t standard_types_len, const std::string& leading_mime_type, base::hash_set* extensions) { for (size_t i = 0; i < standard_types_len; ++i) { g_mime_util.Get().GetPlatformExtensionsForMimeType(standard_types[i], extensions); } // Also look up the extensions from hard-coded mappings in case that some // supported extensions are not registered in the system registry, like ogg. GetExtensionsFromHardCodedMappings(primary_mappings, arraysize(primary_mappings), leading_mime_type, extensions); GetExtensionsFromHardCodedMappings(secondary_mappings, arraysize(secondary_mappings), leading_mime_type, extensions); } // Note that the elements in the source set will be appended to the target // vector. template void HashSetToVector(base::hash_set* source, std::vector* target) { size_t old_target_size = target->size(); target->resize(old_target_size + source->size()); size_t i = 0; for (typename base::hash_set::iterator iter = source->begin(); iter != source->end(); ++iter, ++i) (*target)[old_target_size + i] = *iter; } } // namespace void GetExtensionsForMimeType( const std::string& unsafe_mime_type, std::vector* extensions) { if (unsafe_mime_type == "*/*" || unsafe_mime_type == "*") return; const std::string mime_type = base::StringToLowerASCII(unsafe_mime_type); base::hash_set unique_extensions; if (EndsWith(mime_type, "/*", false)) { std::string leading_mime_type = mime_type.substr(0, mime_type.length() - 1); // Find the matching StandardType from within kStandardTypes, or fall // through to the last (default) StandardType. const StandardType* type = NULL; for (size_t i = 0; i < arraysize(kStandardTypes); ++i) { type = &(kStandardTypes[i]); if (type->leading_mime_type && leading_mime_type == type->leading_mime_type) break; } DCHECK(type); GetExtensionsHelper(type->standard_types, type->standard_types_len, leading_mime_type, &unique_extensions); } else { g_mime_util.Get().GetPlatformExtensionsForMimeType(mime_type, &unique_extensions); // Also look up the extensions from hard-coded mappings in case that some // supported extensions are not registered in the system registry, like ogg. GetExtensionsFromHardCodedMappings(primary_mappings, arraysize(primary_mappings), mime_type, &unique_extensions); GetExtensionsFromHardCodedMappings(secondary_mappings, arraysize(secondary_mappings), mime_type, &unique_extensions); } HashSetToVector(&unique_extensions, extensions); } void AddMultipartValueForUpload(const std::string& value_name, const std::string& value, const std::string& mime_boundary, const std::string& content_type, std::string* post_data) { DCHECK(post_data); // First line is the boundary. post_data->append("--" + mime_boundary + "\r\n"); // Next line is the Content-disposition. post_data->append("Content-Disposition: form-data; name=\"" + value_name + "\"\r\n"); if (!content_type.empty()) { // If Content-type is specified, the next line is that. post_data->append("Content-Type: " + content_type + "\r\n"); } // Leave an empty line and append the value. post_data->append("\r\n" + value + "\r\n"); } void AddMultipartFinalDelimiterForUpload(const std::string& mime_boundary, std::string* post_data) { DCHECK(post_data); post_data->append("--" + mime_boundary + "--\r\n"); } } // namespace net