This source file includes following definitions.
- UMASnifferHistogramGet
- MagicCmp
- MagicMaskCmp
- MatchMagicNumber
- CheckForMagicNumbers
- TruncateSize
- SniffForHTML
- SniffForMagicNumbers
- SniffForOfficeDocs
- IsOfficeType
- SniffForInvalidOfficeDocs
- SniffXML
- SniffBinary
- IsUnknownMimeType
- SniffCRX
- ShouldSniffMimeType
- SniffMimeType
- SniffMimeTypeFromLocalData
#include <string>
#include "net/base/mime_sniffer.h"
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_util.h"
#include "net/base/mime_util.h"
#include "url/gurl.h"
namespace net {
static const size_t kBytesRequiredForMagic = 42;
struct MagicNumber {
const char* mime_type;
const char* magic;
size_t magic_len;
bool is_string;
const char* mask;
};
#define MAGIC_NUMBER(mime_type, magic) \
{ (mime_type), (magic), sizeof(magic)-1, false, NULL },
template <int MagicSize, int MaskSize>
class VerifySizes {
COMPILE_ASSERT(MagicSize == MaskSize, sizes_must_be_equal);
public:
enum { SIZES = MagicSize };
};
#define verified_sizeof(magic, mask) \
VerifySizes<sizeof(magic), sizeof(mask)>::SIZES
#define MAGIC_MASK(mime_type, magic, mask) \
{ (mime_type), (magic), verified_sizeof(magic, mask)-1, false, (mask) },
#define MAGIC_STRING(mime_type, magic) \
{ (mime_type), (magic), sizeof(magic)-1, true, NULL },
static const MagicNumber kMagicNumbers[] = {
MAGIC_NUMBER("application/pdf", "%PDF-")
MAGIC_NUMBER("application/postscript", "%!PS-Adobe-")
MAGIC_NUMBER("image/gif", "GIF87a")
MAGIC_NUMBER("image/gif", "GIF89a")
MAGIC_NUMBER("image/png", "\x89" "PNG\x0D\x0A\x1A\x0A")
MAGIC_NUMBER("image/jpeg", "\xFF\xD8\xFF")
MAGIC_NUMBER("image/bmp", "BM")
MAGIC_NUMBER("text/plain", "#!")
MAGIC_NUMBER("text/plain", "%!")
MAGIC_NUMBER("text/plain", "From")
MAGIC_NUMBER("text/plain", ">From")
MAGIC_NUMBER("application/x-gzip", "\x1F\x8B\x08")
MAGIC_NUMBER("audio/x-pn-realaudio", "\x2E\x52\x4D\x46")
MAGIC_NUMBER("video/x-ms-asf",
"\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C")
MAGIC_NUMBER("image/tiff", "I I")
MAGIC_NUMBER("image/tiff", "II*")
MAGIC_NUMBER("image/tiff", "MM\x00*")
MAGIC_NUMBER("audio/mpeg", "ID3")
MAGIC_NUMBER("image/webp", "RIFF....WEBPVP8 ")
MAGIC_NUMBER("video/webm", "\x1A\x45\xDF\xA3")
MAGIC_NUMBER("application/zip", "PK\x03\x04")
MAGIC_NUMBER("application/x-rar-compressed", "Rar!\x1A\x07\x00")
MAGIC_NUMBER("application/x-msmetafile", "\xD7\xCD\xC6\x9A")
MAGIC_NUMBER("application/octet-stream", "MZ")
};
static const size_t kBytesRequiredForOfficeMagic = 8;
static const MagicNumber kOfficeMagicNumbers[] = {
MAGIC_NUMBER("CFB", "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1")
MAGIC_NUMBER("OOXML", "PK\x03\x04")
};
enum OfficeDocType {
DOC_TYPE_WORD,
DOC_TYPE_EXCEL,
DOC_TYPE_POWERPOINT,
DOC_TYPE_NONE
};
struct OfficeExtensionType {
OfficeDocType doc_type;
const char* extension;
size_t extension_len;
};
#define OFFICE_EXTENSION(type, extension) \
{ (type), (extension), sizeof(extension) - 1 },
static const OfficeExtensionType kOfficeExtensionTypes[] = {
OFFICE_EXTENSION(DOC_TYPE_WORD, ".doc")
OFFICE_EXTENSION(DOC_TYPE_EXCEL, ".xls")
OFFICE_EXTENSION(DOC_TYPE_POWERPOINT, ".ppt")
OFFICE_EXTENSION(DOC_TYPE_WORD, ".docx")
OFFICE_EXTENSION(DOC_TYPE_EXCEL, ".xlsx")
OFFICE_EXTENSION(DOC_TYPE_POWERPOINT, ".pptx")
};
static const MagicNumber kExtraMagicNumbers[] = {
MAGIC_NUMBER("image/x-xbitmap", "#define")
MAGIC_NUMBER("image/x-icon", "\x00\x00\x01\x00")
MAGIC_NUMBER("image/svg+xml", "<?xml_version=")
MAGIC_NUMBER("audio/wav", "RIFF....WAVEfmt ")
MAGIC_NUMBER("video/avi", "RIFF....AVI LIST")
MAGIC_NUMBER("audio/ogg", "OggS")
MAGIC_MASK("video/mpeg", "\x00\x00\x01\xB0", "\xFF\xFF\xFF\xF0")
MAGIC_MASK("audio/mpeg", "\xFF\xE0", "\xFF\xE0")
MAGIC_NUMBER("video/3gpp", "....ftyp3g")
MAGIC_NUMBER("video/3gpp", "....ftypavcl")
MAGIC_NUMBER("video/mp4", "....ftyp")
MAGIC_NUMBER("video/quicktime", "....moov")
MAGIC_NUMBER("application/x-shockwave-flash", "CWS")
MAGIC_NUMBER("application/x-shockwave-flash", "FWS")
MAGIC_NUMBER("video/x-flv", "FLV")
MAGIC_NUMBER("audio/x-flac", "fLaC")
MAGIC_NUMBER("image/x-canon-cr2", "II\x2a\x00\x10\x00\x00\x00CR")
MAGIC_NUMBER("image/x-canon-crw", "II\x1a\x00\x00\x00HEAPCCDR")
MAGIC_NUMBER("image/x-minolta-mrw", "\x00MRM")
MAGIC_NUMBER("image/x-olympus-orf", "MMOR")
MAGIC_NUMBER("image/x-olympus-orf", "IIRO")
MAGIC_NUMBER("image/x-olympus-orf", "IIRS")
MAGIC_NUMBER("image/x-fuji-raf", "FUJIFILMCCD-RAW ")
MAGIC_NUMBER("image/x-panasonic-raw",
"IIU\x00\x08\x00\x00\x00")
MAGIC_NUMBER("image/x-panasonic-raw",
"IIU\x00\x18\x00\x00\x00")
MAGIC_NUMBER("image/x-phaseone-raw", "MMMMRaw")
MAGIC_NUMBER("image/x-x3f", "FOVb")
};
#define MAGIC_HTML_TAG(tag) \
MAGIC_STRING("text/html", "<" tag)
static const MagicNumber kSniffableTags[] = {
MAGIC_NUMBER("text/xml", "<?xml")
MAGIC_HTML_TAG("!DOCTYPE html")
MAGIC_HTML_TAG("script")
MAGIC_HTML_TAG("html")
MAGIC_HTML_TAG("!--")
MAGIC_HTML_TAG("head")
MAGIC_HTML_TAG("iframe")
MAGIC_HTML_TAG("h1")
MAGIC_HTML_TAG("div")
MAGIC_HTML_TAG("font")
MAGIC_HTML_TAG("table")
MAGIC_HTML_TAG("a")
MAGIC_HTML_TAG("style")
MAGIC_HTML_TAG("title")
MAGIC_HTML_TAG("b")
MAGIC_HTML_TAG("body")
MAGIC_HTML_TAG("br")
MAGIC_HTML_TAG("p")
};
static base::HistogramBase* UMASnifferHistogramGet(const char* name,
int array_size) {
base::HistogramBase* counter =
base::LinearHistogram::FactoryGet(name, 1, array_size - 1, array_size,
base::HistogramBase::kUmaTargetedHistogramFlag);
return counter;
}
static bool MagicCmp(const char* magic_entry, const char* content, size_t len) {
while (len) {
if ((*magic_entry != '.') && (*magic_entry != *content))
return false;
++magic_entry;
++content;
--len;
}
return true;
}
static bool MagicMaskCmp(const char* magic_entry,
const char* content,
size_t len,
const char* mask) {
while (len) {
if ((*magic_entry != '.') && (*magic_entry != (*mask & *content)))
return false;
++magic_entry;
++content;
++mask;
--len;
}
return true;
}
static bool MatchMagicNumber(const char* content,
size_t size,
const MagicNumber& magic_entry,
std::string* result) {
const size_t len = magic_entry.magic_len;
DCHECK_LE(len, kBytesRequiredForMagic);
const char* end = static_cast<const char*>(memchr(content, '\0', size));
const size_t content_strlen =
(end != NULL) ? static_cast<size_t>(end - content) : size;
bool match = false;
if (magic_entry.is_string) {
if (content_strlen >= len) {
match = (base::strncasecmp(magic_entry.magic, content, len) == 0);
}
} else {
if (size >= len) {
if (!magic_entry.mask) {
match = MagicCmp(magic_entry.magic, content, len);
} else {
match = MagicMaskCmp(magic_entry.magic, content, len, magic_entry.mask);
}
}
}
if (match) {
result->assign(magic_entry.mime_type);
return true;
}
return false;
}
static bool CheckForMagicNumbers(const char* content, size_t size,
const MagicNumber* magic, size_t magic_len,
base::HistogramBase* counter,
std::string* result) {
for (size_t i = 0; i < magic_len; ++i) {
if (MatchMagicNumber(content, size, magic[i], result)) {
if (counter) counter->Add(static_cast<int>(i));
return true;
}
}
return false;
}
static bool TruncateSize(const size_t max_size, size_t* size) {
DCHECK_LE(static_cast<int>(max_size), kMaxBytesToSniff);
if (*size >= max_size) {
*size = max_size;
return true;
}
return false;
}
static bool SniffForHTML(const char* content,
size_t size,
bool* have_enough_content,
std::string* result) {
*have_enough_content &= TruncateSize(512, &size);
const char* const end = content + size;
const char* pos;
for (pos = content; pos < end; ++pos) {
if (!IsAsciiWhitespace(*pos))
break;
}
static base::HistogramBase* counter(NULL);
if (!counter) {
counter = UMASnifferHistogramGet("mime_sniffer.kSniffableTags2",
arraysize(kSniffableTags));
}
return CheckForMagicNumbers(pos, end - pos,
kSniffableTags, arraysize(kSniffableTags),
counter, result);
}
static bool SniffForMagicNumbers(const char* content,
size_t size,
bool* have_enough_content,
std::string* result) {
*have_enough_content &= TruncateSize(kBytesRequiredForMagic, &size);
static base::HistogramBase* counter(NULL);
if (!counter) {
counter = UMASnifferHistogramGet("mime_sniffer.kMagicNumbers2",
arraysize(kMagicNumbers));
}
return CheckForMagicNumbers(content, size,
kMagicNumbers, arraysize(kMagicNumbers),
counter, result);
}
static bool SniffForOfficeDocs(const char* content,
size_t size,
const GURL& url,
bool* have_enough_content,
std::string* result) {
*have_enough_content &= TruncateSize(kBytesRequiredForOfficeMagic, &size);
std::string office_version;
if (!CheckForMagicNumbers(content, size,
kOfficeMagicNumbers, arraysize(kOfficeMagicNumbers),
NULL, &office_version))
return false;
OfficeDocType type = DOC_TYPE_NONE;
for (size_t i = 0; i < arraysize(kOfficeExtensionTypes); ++i) {
std::string url_path = url.path();
if (url_path.length() < kOfficeExtensionTypes[i].extension_len)
continue;
const char* extension =
&url_path[url_path.length() - kOfficeExtensionTypes[i].extension_len];
if (0 == base::strncasecmp(extension, kOfficeExtensionTypes[i].extension,
kOfficeExtensionTypes[i].extension_len)) {
type = kOfficeExtensionTypes[i].doc_type;
break;
}
}
if (type == DOC_TYPE_NONE)
return false;
if (office_version == "CFB") {
switch (type) {
case DOC_TYPE_WORD:
*result = "application/msword";
return true;
case DOC_TYPE_EXCEL:
*result = "application/vnd.ms-excel";
return true;
case DOC_TYPE_POWERPOINT:
*result = "application/vnd.ms-powerpoint";
return true;
case DOC_TYPE_NONE:
NOTREACHED();
return false;
}
} else if (office_version == "OOXML") {
switch (type) {
case DOC_TYPE_WORD:
*result = "application/vnd.openxmlformats-officedocument."
"wordprocessingml.document";
return true;
case DOC_TYPE_EXCEL:
*result = "application/vnd.openxmlformats-officedocument."
"spreadsheetml.sheet";
return true;
case DOC_TYPE_POWERPOINT:
*result = "application/vnd.openxmlformats-officedocument."
"presentationml.presentation";
return true;
case DOC_TYPE_NONE:
NOTREACHED();
return false;
}
}
NOTREACHED();
return false;
}
static bool IsOfficeType(const std::string& type_hint) {
return (type_hint == "application/msword" ||
type_hint == "application/vnd.ms-excel" ||
type_hint == "application/vnd.ms-powerpoint" ||
type_hint == "application/vnd.openxmlformats-officedocument."
"wordprocessingml.document" ||
type_hint == "application/vnd.openxmlformats-officedocument."
"spreadsheetml.sheet" ||
type_hint == "application/vnd.openxmlformats-officedocument."
"presentationml.presentation" ||
type_hint == "application/vnd.ms-excel.sheet.macroenabled.12" ||
type_hint == "application/vnd.ms-word.document.macroenabled.12" ||
type_hint == "application/vnd.ms-powerpoint.presentation."
"macroenabled.12" ||
type_hint == "application/mspowerpoint" ||
type_hint == "application/msexcel" ||
type_hint == "application/vnd.ms-word" ||
type_hint == "application/vnd.ms-word.document.12" ||
type_hint == "application/vnd.msword");
}
static bool SniffForInvalidOfficeDocs(const char* content,
size_t size,
const GURL& url,
std::string* result) {
if (!TruncateSize(kBytesRequiredForOfficeMagic, &size))
return false;
std::string office_version;
if (!CheckForMagicNumbers(content, size,
kOfficeMagicNumbers, arraysize(kOfficeMagicNumbers),
NULL, &office_version)) {
*result = "application/octet-stream";
}
return true;
}
static const MagicNumber kMagicXML[] = {
MAGIC_STRING("application/xhtml+xml",
"<html xmlns=\"http://www.w3.org/1999/xhtml\"")
MAGIC_STRING("application/atom+xml", "<feed")
MAGIC_STRING("application/rss+xml", "<rss")
};
static bool SniffXML(const char* content,
size_t size,
bool* have_enough_content,
std::string* result) {
*have_enough_content &= TruncateSize(300, &size);
const char* pos = content;
const char* const end = content + size;
static base::HistogramBase* counter(NULL);
if (!counter) {
counter = UMASnifferHistogramGet("mime_sniffer.kMagicXML2",
arraysize(kMagicXML));
}
const int kMaxTagIterations = 5;
for (int i = 0; i < kMaxTagIterations && pos < end; ++i) {
pos = reinterpret_cast<const char*>(memchr(pos, '<', end - pos));
if (!pos)
return false;
if (base::strncasecmp(pos, "<?xml", sizeof("<?xml") - 1) == 0) {
++pos;
continue;
} else if (base::strncasecmp(pos, "<!DOCTYPE",
sizeof("<!DOCTYPE") - 1) == 0) {
++pos;
continue;
}
if (CheckForMagicNumbers(pos, end - pos,
kMagicXML, arraysize(kMagicXML),
counter, result))
return true;
return true;
}
return pos < end;
}
static const MagicNumber kByteOrderMark[] = {
MAGIC_NUMBER("text/plain", "\xFE\xFF")
MAGIC_NUMBER("text/plain", "\xFF\xFE")
MAGIC_NUMBER("text/plain", "\xEF\xBB\xBF")
};
static char kByteLooksBinary[] = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
static bool SniffBinary(const char* content,
size_t size,
bool* have_enough_content,
std::string* result) {
const bool is_truncated = TruncateSize(kMaxBytesToSniff, &size);
static base::HistogramBase* counter(NULL);
if (!counter) {
counter = UMASnifferHistogramGet("mime_sniffer.kByteOrderMark2",
arraysize(kByteOrderMark));
}
std::string unused;
if (CheckForMagicNumbers(content, size,
kByteOrderMark, arraysize(kByteOrderMark),
counter, &unused)) {
result->assign("text/plain");
return false;
}
for (size_t i = 0; i < size; ++i) {
if (kByteLooksBinary[static_cast<unsigned char>(content[i])]) {
result->assign("application/octet-stream");
return true;
}
}
*have_enough_content &= is_truncated;
result->assign("text/plain");
return false;
}
static bool IsUnknownMimeType(const std::string& mime_type) {
static const char* kUnknownMimeTypes[] = {
"",
"unknown/unknown",
"application/unknown",
"*/*",
};
static base::HistogramBase* counter(NULL);
if (!counter) {
counter = UMASnifferHistogramGet("mime_sniffer.kUnknownMimeTypes2",
arraysize(kUnknownMimeTypes) + 1);
}
for (size_t i = 0; i < arraysize(kUnknownMimeTypes); ++i) {
if (mime_type == kUnknownMimeTypes[i]) {
counter->Add(i);
return true;
}
}
if (mime_type.find('/') == std::string::npos) {
counter->Add(arraysize(kUnknownMimeTypes));
return true;
}
return false;
}
static bool SniffCRX(const char* content,
size_t size,
const GURL& url,
const std::string& type_hint,
bool* have_enough_content,
std::string* result) {
static base::HistogramBase* counter(NULL);
if (!counter)
counter = UMASnifferHistogramGet("mime_sniffer.kSniffCRX", 3);
static const struct MagicNumber kCRXMagicNumbers[] = {
MAGIC_NUMBER("application/x-chrome-extension", "Cr24\x02\x00\x00\x00")
};
static const char kCRXExtension[] = ".crx";
static const int kExtensionLength = arraysize(kCRXExtension) - 1;
if (url.path().rfind(kCRXExtension, std::string::npos, kExtensionLength) ==
url.path().size() - kExtensionLength) {
counter->Add(1);
} else {
return false;
}
*have_enough_content &= TruncateSize(kBytesRequiredForMagic, &size);
if (CheckForMagicNumbers(content, size,
kCRXMagicNumbers, arraysize(kCRXMagicNumbers),
NULL, result)) {
counter->Add(2);
} else {
return false;
}
return true;
}
bool ShouldSniffMimeType(const GURL& url, const std::string& mime_type) {
static base::HistogramBase* should_sniff_counter(NULL);
if (!should_sniff_counter) {
should_sniff_counter =
UMASnifferHistogramGet("mime_sniffer.ShouldSniffMimeType2", 3);
}
bool sniffable_scheme = url.is_empty() ||
url.SchemeIsHTTPOrHTTPS() ||
url.SchemeIs("ftp") ||
#if defined(OS_ANDROID)
url.SchemeIs("content") ||
#endif
url.SchemeIsFile() ||
url.SchemeIsFileSystem();
if (!sniffable_scheme) {
should_sniff_counter->Add(1);
return false;
}
static const char* kSniffableTypes[] = {
"text/plain",
"application/octet-stream",
"text/xml",
"application/xml",
"application/msword",
"application/vnd.ms-excel",
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.ms-excel.sheet.macroenabled.12",
"application/vnd.ms-word.document.macroenabled.12",
"application/vnd.ms-powerpoint.presentation.macroenabled.12",
"application/mspowerpoint",
"application/msexcel",
"application/vnd.ms-word",
"application/vnd.ms-word.document.12",
"application/vnd.msword",
};
static base::HistogramBase* counter(NULL);
if (!counter) {
counter = UMASnifferHistogramGet("mime_sniffer.kSniffableTypes2",
arraysize(kSniffableTypes) + 1);
}
for (size_t i = 0; i < arraysize(kSniffableTypes); ++i) {
if (mime_type == kSniffableTypes[i]) {
counter->Add(i);
should_sniff_counter->Add(2);
return true;
}
}
if (IsUnknownMimeType(mime_type)) {
counter->Add(arraysize(kSniffableTypes));
should_sniff_counter->Add(2);
return true;
}
should_sniff_counter->Add(1);
return false;
}
bool SniffMimeType(const char* content,
size_t content_size,
const GURL& url,
const std::string& type_hint,
std::string* result) {
DCHECK_LT(content_size, 1000000U);
DCHECK(content);
DCHECK(result);
bool have_enough_content = true;
result->assign(type_hint);
if (IsOfficeType(type_hint))
return SniffForInvalidOfficeDocs(content, content_size, url, result);
const bool hint_is_unknown_mime_type = IsUnknownMimeType(type_hint);
if (hint_is_unknown_mime_type) {
if (SniffForHTML(content, content_size, &have_enough_content, result))
return true;
}
const bool hint_is_text_plain = (type_hint == "text/plain");
if (hint_is_unknown_mime_type || hint_is_text_plain) {
if (!SniffBinary(content, content_size, &have_enough_content, result)) {
if (hint_is_text_plain) {
return have_enough_content;
}
}
}
if (type_hint == "text/xml" || type_hint == "application/xml") {
if (SniffXML(content, content_size, &have_enough_content, result))
return true;
return have_enough_content;
}
if (SniffCRX(content, content_size, url, type_hint,
&have_enough_content, result))
return true;
if (SniffForOfficeDocs(content, content_size, url,
&have_enough_content, result))
return true;
if (type_hint == "application/octet-stream")
return have_enough_content;
if (SniffForMagicNumbers(content, content_size,
&have_enough_content, result))
return true;
return have_enough_content;
}
bool SniffMimeTypeFromLocalData(const char* content,
size_t size,
std::string* result) {
if (CheckForMagicNumbers(content, size, kExtraMagicNumbers,
arraysize(kExtraMagicNumbers), NULL, result))
return true;
return CheckForMagicNumbers(content, size, kMagicNumbers,
arraysize(kMagicNumbers), NULL, result);
}
}