blob: 4c0d1e0cb7c19ca86903525f301d1331d124de8f [file] [log] [blame]
// 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 <algorithm>
#include <iterator>
#include <map>
#include <string>
#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>;
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<MimeUtil>::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 std::string& 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.size() &&
base::strncasecmp(extensions, ext.data(), ext.size()) == 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);
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);
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<std::string, std::string> 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.data(), base_type.data(),
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<std::string> 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<base::FilePath::StringType>* extensions) {
for (size_t i = 0; i < mappings_len; ++i) {
if (StartsWithASCII(mappings[i].mime_type, leading_mime_type, false)) {
std::vector<string> 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<base::FilePath::StringType>* 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<class T>
void HashSetToVector(base::hash_set<T>* source, std::vector<T>* target) {
size_t old_target_size = target->size();
target->resize(old_target_size + source->size());
size_t i = 0;
for (typename base::hash_set<T>::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<base::FilePath::StringType>* extensions) {
if (unsafe_mime_type == "*/*" || unsafe_mime_type == "*")
return;
const std::string mime_type = base::StringToLowerASCII(unsafe_mime_type);
base::hash_set<base::FilePath::StringType> 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