| // 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. | 
 |  | 
 | // Detecting mime types is a tricky business because we need to balance | 
 | // compatibility concerns with security issues.  Here is a survey of how other | 
 | // browsers behave and then a description of how we intend to behave. | 
 | // | 
 | // HTML payload, no Content-Type header: | 
 | // * IE 7: Render as HTML | 
 | // * Firefox 2: Render as HTML | 
 | // * Safari 3: Render as HTML | 
 | // * Opera 9: Render as HTML | 
 | // | 
 | // Here the choice seems clear: | 
 | // => Chrome: Render as HTML | 
 | // | 
 | // HTML payload, Content-Type: "text/plain": | 
 | // * IE 7: Render as HTML | 
 | // * Firefox 2: Render as text | 
 | // * Safari 3: Render as text (Note: Safari will Render as HTML if the URL | 
 | //                                   has an HTML extension) | 
 | // * Opera 9: Render as text | 
 | // | 
 | // Here we choose to follow the majority (and break some compatibility with IE). | 
 | // Many folks dislike IE's behavior here. | 
 | // => Chrome: Render as text | 
 | // We generalize this as follows.  If the Content-Type header is text/plain | 
 | // we won't detect dangerous mime types (those that can execute script). | 
 | // | 
 | // HTML payload, Content-Type: "application/octet-stream": | 
 | // * IE 7: Render as HTML | 
 | // * Firefox 2: Download as application/octet-stream | 
 | // * Safari 3: Render as HTML | 
 | // * Opera 9: Render as HTML | 
 | // | 
 | // We follow Firefox. | 
 | // => Chrome: Download as application/octet-stream | 
 | // One factor in this decision is that IIS 4 and 5 will send | 
 | // application/octet-stream for .xhtml files (because they don't recognize | 
 | // the extension).  We did some experiments and it looks like this doesn't occur | 
 | // very often on the web.  We choose the more secure option. | 
 | // | 
 | // GIF payload, no Content-Type header: | 
 | // * IE 7: Render as GIF | 
 | // * Firefox 2: Render as GIF | 
 | // * Safari 3: Download as Unknown (Note: Safari will Render as GIF if the | 
 | //                                        URL has an GIF extension) | 
 | // * Opera 9: Render as GIF | 
 | // | 
 | // The choice is clear. | 
 | // => Chrome: Render as GIF | 
 | // Once we decide to render HTML without a Content-Type header, there isn't much | 
 | // reason not to render GIFs. | 
 | // | 
 | // GIF payload, Content-Type: "text/plain": | 
 | // * IE 7: Render as GIF | 
 | // * Firefox 2: Download as application/octet-stream (Note: Firefox will | 
 | //                              Download as GIF if the URL has an GIF extension) | 
 | // * Safari 3: Download as Unknown (Note: Safari will Render as GIF if the | 
 | //                                        URL has an GIF extension) | 
 | // * Opera 9: Render as GIF | 
 | // | 
 | // Displaying as text/plain makes little sense as the content will look like | 
 | // gibberish.  Here, we could change our minds and download. | 
 | // => Chrome: Render as GIF | 
 | // | 
 | // GIF payload, Content-Type: "application/octet-stream": | 
 | // * IE 7: Render as GIF | 
 | // * Firefox 2: Download as application/octet-stream (Note: Firefox will | 
 | //                              Download as GIF if the URL has an GIF extension) | 
 | // * Safari 3: Download as Unknown (Note: Safari will Render as GIF if the | 
 | //                                        URL has an GIF extension) | 
 | // * Opera 9: Render as GIF | 
 | // | 
 | // We used to render as GIF here, but the problem is that some sites want to | 
 | // trigger downloads by sending application/octet-stream (even though they | 
 | // should be sending Content-Disposition: attachment).  Although it is safe | 
 | // to render as GIF from a security perspective, we actually get better | 
 | // compatibility if we don't sniff from application/octet stream at all. | 
 | // => Chrome: Download as application/octet-stream | 
 | // | 
 | // XHTML payload, Content-Type: "text/xml": | 
 | // * IE 7: Render as XML | 
 | // * Firefox 2: Render as HTML | 
 | // * Safari 3: Render as HTML | 
 | // * Opera 9: Render as HTML | 
 | // The layout tests rely on us rendering this as HTML. | 
 | // But we're conservative in XHTML detection, as this runs afoul of the | 
 | // "don't detect dangerous mime types" rule. | 
 | // | 
 | // Note that our definition of HTML payload is much stricter than IE's | 
 | // definition and roughly the same as Firefox's definition. | 
 |  | 
 | #include <string> | 
 |  | 
 | #include "net/base/mime_sniffer.h" | 
 |  | 
 | #include "base/basictypes.h" | 
 | #include "base/logging.h" | 
 | #include "base/metrics/histogram.h" | 
 | #include "base/string_util.h" | 
 | #include "googleurl/src/gurl.h" | 
 | #include "net/base/mime_util.h" | 
 |  | 
 | namespace net { | 
 |  | 
 | // The number of content bytes we need to use all our magic numbers.  Feel free | 
 | // to increase this number if you add a longer magic number. | 
 | static const size_t kBytesRequiredForMagic = 42; | 
 |  | 
 | struct MagicNumber { | 
 |   const char* mime_type; | 
 |   const char* magic; | 
 |   size_t magic_len; | 
 |   bool is_string; | 
 | }; | 
 |  | 
 | #define MAGIC_NUMBER(mime_type, magic) \ | 
 |   { (mime_type), (magic), sizeof(magic)-1, false }, | 
 |  | 
 | // Magic strings are case insensitive and must not include '\0' characters | 
 | #define MAGIC_STRING(mime_type, magic) \ | 
 |   { (mime_type), (magic), sizeof(magic)-1, true }, | 
 |  | 
 | static const MagicNumber kMagicNumbers[] = { | 
 |   // Source: HTML 5 specification | 
 |   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") | 
 |   // Source: Mozilla | 
 |   MAGIC_NUMBER("text/plain", "#!")  // Script | 
 |   MAGIC_NUMBER("text/plain", "%!")  // Script, similar to PS | 
 |   MAGIC_NUMBER("text/plain", "From") | 
 |   MAGIC_NUMBER("text/plain", ">From") | 
 |   // Chrome specific | 
 |   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") | 
 |   // TODO(abarth): we don't handle partial byte matches yet | 
 |   // MAGIC_NUMBER("video/mpeg", "\x00\x00\x01\xB") | 
 |   // MAGIC_NUMBER("audio/mpeg", "\xFF\xE") | 
 |   // MAGIC_NUMBER("audio/mpeg", "\xFF\xF") | 
 |   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")  // EXE | 
 |   // Sniffing for Flash: | 
 |   // | 
 |   //   MAGIC_NUMBER("application/x-shockwave-flash", "CWS") | 
 |   //   MAGIC_NUMBER("application/x-shockwave-flash", "FLV") | 
 |   //   MAGIC_NUMBER("application/x-shockwave-flash", "FWS") | 
 |   // | 
 |   // Including these magic number for Flash is a trade off. | 
 |   // | 
 |   // Pros: | 
 |   //   * Flash is an important and popular file format | 
 |   // | 
 |   // Cons: | 
 |   //   * These patterns are fairly weak | 
 |   //   * If we mistakenly decide something is Flash, we will execute it | 
 |   //     in the origin of an unsuspecting site.  This could be a security | 
 |   //     vulnerability if the site allows users to upload content. | 
 |   // | 
 |   // On balance, we do not include these patterns. | 
 | }; | 
 |  | 
 | // Our HTML sniffer differs slightly from Mozilla.  For example, Mozilla will | 
 | // decide that a document that begins "<!DOCTYPE SOAP-ENV:Envelope PUBLIC " is | 
 | // HTML, but we will not. | 
 |  | 
 | #define MAGIC_HTML_TAG(tag) \ | 
 |   MAGIC_STRING("text/html", "<" tag) | 
 |  | 
 | static const MagicNumber kSniffableTags[] = { | 
 |   // XML processing directive.  Although this is not an HTML mime type, we sniff | 
 |   // for this in the HTML phase because text/xml is just as powerful as HTML and | 
 |   // we want to leverage our white space skipping technology. | 
 |   MAGIC_NUMBER("text/xml", "<?xml")  // Mozilla | 
 |   // DOCTYPEs | 
 |   MAGIC_HTML_TAG("!DOCTYPE html")  // HTML5 spec | 
 |   // Sniffable tags, ordered by how often they occur in sniffable documents. | 
 |   MAGIC_HTML_TAG("script")  // HTML5 spec, Mozilla | 
 |   MAGIC_HTML_TAG("html")  // HTML5 spec, Mozilla | 
 |   MAGIC_HTML_TAG("!--") | 
 |   MAGIC_HTML_TAG("head")  // HTML5 spec, Mozilla | 
 |   MAGIC_HTML_TAG("iframe")  // Mozilla | 
 |   MAGIC_HTML_TAG("h1")  // Mozilla | 
 |   MAGIC_HTML_TAG("div")  // Mozilla | 
 |   MAGIC_HTML_TAG("font")  // Mozilla | 
 |   MAGIC_HTML_TAG("table")  // Mozilla | 
 |   MAGIC_HTML_TAG("a")  // Mozilla | 
 |   MAGIC_HTML_TAG("style")  // Mozilla | 
 |   MAGIC_HTML_TAG("title")  // Mozilla | 
 |   MAGIC_HTML_TAG("b")  // Mozilla | 
 |   MAGIC_HTML_TAG("body")  // Mozilla | 
 |   MAGIC_HTML_TAG("br") | 
 |   MAGIC_HTML_TAG("p")  // Mozilla | 
 | }; | 
 |  | 
 | static base::Histogram* UMASnifferHistogramGet(const char* name, | 
 |                                                int array_size) { | 
 |   base::Histogram* counter = | 
 |       base::LinearHistogram::FactoryGet(name, 1, array_size - 1, array_size, | 
 |       base::Histogram::kUmaTargetedHistogramFlag); | 
 |   return counter; | 
 | } | 
 |  | 
 | // Compare content header to a magic number where magic_entry can contain '.' | 
 | // for single character of anything, allowing some bytes to be skipped. | 
 | 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 MatchMagicNumber(const char* content, size_t size, | 
 |                              const MagicNumber* magic_entry, | 
 |                              std::string* result) { | 
 |   const size_t len = magic_entry->magic_len; | 
 |  | 
 |   // Keep kBytesRequiredForMagic honest. | 
 |   DCHECK_LE(len, kBytesRequiredForMagic); | 
 |  | 
 |   // To compare with magic strings, we need to compute strlen(content), but | 
 |   // content might not actually have a null terminator.  In that case, we | 
 |   // pretend the length is content_size. | 
 |   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) { | 
 |       // String comparisons are case-insensitive | 
 |       match = (base::strncasecmp(magic_entry->magic, content, len) == 0); | 
 |     } | 
 |   } else { | 
 |     if (size >= len) | 
 |       match = MagicCmp(magic_entry->magic, content, len); | 
 |   } | 
 |  | 
 |   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::Histogram* 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; | 
 | } | 
 |  | 
 | // Truncates |size| to |max_size| and returns true if |size| is at least | 
 | // |max_size|. | 
 | static bool TruncateSize(const size_t max_size, size_t* size) { | 
 |   // Keep kMaxBytesToSniff honest. | 
 |   DCHECK_LE(static_cast<int>(max_size), kMaxBytesToSniff); | 
 |  | 
 |   if (*size >= max_size) { | 
 |     *size = max_size; | 
 |     return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | // Returns true and sets result if the content appears to be HTML. | 
 | // Clears have_enough_content if more data could possibly change the result. | 
 | static bool SniffForHTML(const char* content, | 
 |                          size_t size, | 
 |                          bool* have_enough_content, | 
 |                          std::string* result) { | 
 |   // For HTML, we are willing to consider up to 512 bytes. This may be overly | 
 |   // conservative as IE only considers 256. | 
 |   *have_enough_content &= TruncateSize(512, &size); | 
 |  | 
 |   // We adopt a strategy similar to that used by Mozilla to sniff HTML tags, | 
 |   // but with some modifications to better match the HTML5 spec. | 
 |   const char* const end = content + size; | 
 |   const char* pos; | 
 |   for (pos = content; pos < end; ++pos) { | 
 |     if (!IsAsciiWhitespace(*pos)) | 
 |       break; | 
 |   } | 
 |   static base::Histogram* counter(NULL); | 
 |   if (!counter) | 
 |     counter = UMASnifferHistogramGet("mime_sniffer.kSniffableTags2", | 
 |                                      arraysize(kSniffableTags)); | 
 |   // |pos| now points to first non-whitespace character (or at end). | 
 |   return CheckForMagicNumbers(pos, end - pos, | 
 |                               kSniffableTags, arraysize(kSniffableTags), | 
 |                               counter, result); | 
 | } | 
 |  | 
 | // Returns true and sets result if the content matches any of kMagicNumbers. | 
 | // Clears have_enough_content if more data could possibly change the result. | 
 | static bool SniffForMagicNumbers(const char* content, | 
 |                                  size_t size, | 
 |                                  bool* have_enough_content, | 
 |                                  std::string* result) { | 
 |   *have_enough_content &= TruncateSize(kBytesRequiredForMagic, &size); | 
 |  | 
 |   // Check our big table of Magic Numbers | 
 |   static base::Histogram* counter(NULL); | 
 |   if (!counter) | 
 |     counter = UMASnifferHistogramGet("mime_sniffer.kMagicNumbers2", | 
 |                                      arraysize(kMagicNumbers)); | 
 |   return CheckForMagicNumbers(content, size, | 
 |                               kMagicNumbers, arraysize(kMagicNumbers), | 
 |                               counter, result); | 
 | } | 
 |  | 
 | // Byte order marks | 
 | static const MagicNumber kMagicXML[] = { | 
 |   // We want to be very conservative in interpreting text/xml content as | 
 |   // XHTML -- we just want to sniff enough to make unit tests pass. | 
 |   // So we match explicitly on this, and don't match other ways of writing | 
 |   // it in semantically-equivalent ways. | 
 |   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")  // UTF-8 | 
 | }; | 
 |  | 
 | // Returns true and sets result if the content appears to contain XHTML or a | 
 | // feed. | 
 | // Clears have_enough_content if more data could possibly change the result. | 
 | // | 
 | // TODO(evanm): this is similar but more conservative than what Safari does, | 
 | // while HTML5 has a different recommendation -- what should we do? | 
 | // TODO(evanm): this is incorrect for documents whose encoding isn't a superset | 
 | // of ASCII -- do we care? | 
 | static bool SniffXML(const char* content, | 
 |                      size_t size, | 
 |                      bool* have_enough_content, | 
 |                      std::string* result) { | 
 |   // We allow at most 300 bytes of content before we expect the opening tag. | 
 |   *have_enough_content &= TruncateSize(300, &size); | 
 |   const char* pos = content; | 
 |   const char* const end = content + size; | 
 |  | 
 |   // This loop iterates through tag-looking offsets in the file. | 
 |   // We want to skip XML processing instructions (of the form "<?xml ...") | 
 |   // and stop at the first "plain" tag, then make a decision on the mime-type | 
 |   // based on the name (or possibly attributes) of that tag. | 
 |   static base::Histogram* 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) { | 
 |       // Skip XML declarations. | 
 |       ++pos; | 
 |       continue; | 
 |     } else if (base::strncasecmp(pos, "<!DOCTYPE", | 
 |                                  sizeof("<!DOCTYPE")-1) == 0) { | 
 |       // Skip DOCTYPE declarations. | 
 |       ++pos; | 
 |       continue; | 
 |     } | 
 |  | 
 |     if (CheckForMagicNumbers(pos, end - pos, | 
 |                              kMagicXML, arraysize(kMagicXML), | 
 |                              counter, result)) | 
 |       return true; | 
 |  | 
 |     // TODO(evanm): handle RSS 1.0, which is an RDF format and more difficult | 
 |     // to identify. | 
 |  | 
 |     // If we get here, we've hit an initial tag that hasn't matched one of the | 
 |     // above tests.  Abort. | 
 |     return true; | 
 |   } | 
 |  | 
 |   // We iterated too far without finding a start tag. | 
 |   // If we have more content to look at, we aren't going to change our mind by | 
 |   // seeing more bytes from the network. | 
 |   return pos < end; | 
 | } | 
 |  | 
 | // Byte order marks | 
 | static const MagicNumber kByteOrderMark[] = { | 
 |   MAGIC_NUMBER("text/plain", "\xFE\xFF")  // UTF-16BE | 
 |   MAGIC_NUMBER("text/plain", "\xFF\xFE")  // UTF-16LE | 
 |   MAGIC_NUMBER("text/plain", "\xEF\xBB\xBF")  // UTF-8 | 
 | }; | 
 |  | 
 | // Whether a given byte looks like it might be part of binary content. | 
 | // Source: HTML5 spec | 
 | static char kByteLooksBinary[] = { | 
 |   1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1,  // 0x00 - 0x0F | 
 |   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,  // 0x10 - 0x1F | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x20 - 0x2F | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x30 - 0x3F | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x40 - 0x4F | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x50 - 0x5F | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x60 - 0x6F | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x70 - 0x7F | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x80 - 0x8F | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x90 - 0x9F | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xA0 - 0xAF | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xB0 - 0xBF | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xC0 - 0xCF | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xD0 - 0xDF | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xE0 - 0xEF | 
 |   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xF0 - 0xFF | 
 | }; | 
 |  | 
 | // Returns true and sets result to "application/octet-stream" if the content | 
 | // appears to be binary data. Otherwise, returns false and sets "text/plain". | 
 | // Clears have_enough_content if more data could possibly change the result. | 
 | static bool SniffBinary(const char* content, | 
 |                         size_t size, | 
 |                         bool* have_enough_content, | 
 |                         std::string* result) { | 
 |   // There is no concensus about exactly how to sniff for binary content. | 
 |   // * IE 7: Don't sniff for binary looking bytes, but trust the file extension. | 
 |   // * Firefox 3.5: Sniff first 4096 bytes for a binary looking byte. | 
 |   // Here, we side with FF, but with a smaller buffer. This size was chosen | 
 |   // because it is small enough to comfortably fit into a single packet (after | 
 |   // allowing for headers) and yet large enough to account for binary formats | 
 |   // that have a significant amount of ASCII at the beginning (crbug.com/15314). | 
 |   const bool is_truncated = TruncateSize(kMaxBytesToSniff, &size); | 
 |  | 
 |   // First, we look for a BOM. | 
 |   static base::Histogram* counter(NULL); | 
 |   if (!counter) | 
 |     counter = UMASnifferHistogramGet("mime_sniffer.kByteOrderMark2", | 
 |                                      arraysize(kByteOrderMark)); | 
 |   std::string unused; | 
 |   if (CheckForMagicNumbers(content, size, | 
 |                            kByteOrderMark, arraysize(kByteOrderMark), | 
 |                            counter, &unused)) { | 
 |     // If there is BOM, we think the buffer is not binary. | 
 |     result->assign("text/plain"); | 
 |     return false; | 
 |   } | 
 |  | 
 |   // Next we look to see if any of the bytes "look binary." | 
 |   for (size_t i = 0; i < size; ++i) { | 
 |     // If we a see a binary-looking byte, we think the content is binary. | 
 |     if (kByteLooksBinary[static_cast<unsigned char>(content[i])]) { | 
 |       result->assign("application/octet-stream"); | 
 |       return true; | 
 |     } | 
 |   } | 
 |  | 
 |   // No evidence either way. Default to non-binary and, if truncated, clear | 
 |   // have_enough_content because there could be a binary looking byte in the | 
 |   // truncated data. | 
 |   *have_enough_content &= is_truncated; | 
 |   result->assign("text/plain"); | 
 |   return false; | 
 | } | 
 |  | 
 | static bool IsUnknownMimeType(const std::string& mime_type) { | 
 |   // TODO(tc): Maybe reuse some code in net/http/http_response_headers.* here. | 
 |   // If we do, please be careful not to alter the semantics at all. | 
 |   static const char* kUnknownMimeTypes[] = { | 
 |     // Empty mime types are as unknown as they get. | 
 |     "", | 
 |     // The unknown/unknown type is popular and uninformative | 
 |     "unknown/unknown", | 
 |     // The second most popular unknown mime type is application/unknown | 
 |     "application/unknown", | 
 |     // Firefox rejects a mime type if it is exactly */* | 
 |     "*/*", | 
 |   }; | 
 |   static base::Histogram* 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) { | 
 |     // Firefox rejects a mime type if it does not contain a slash | 
 |     counter->Add(arraysize(kUnknownMimeTypes)); | 
 |     return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | // Returns true and sets result if the content appears to be a crx (chrome | 
 | // extension) file. | 
 | // Clears have_enough_content if more data could possibly change the result. | 
 | 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::Histogram* counter(NULL); | 
 |   if (!counter) | 
 |     counter = UMASnifferHistogramGet("mime_sniffer.kSniffCRX", 3); | 
 |  | 
 |   // Technically, the crx magic number is just Cr24, but the bytes after that | 
 |   // are a version number which changes infrequently. Including it in the | 
 |   // sniffing gives us less room for error. If the version number ever changes, | 
 |   // we can just add an entry to this list. | 
 |   // | 
 |   // TODO(aa): If we ever have another magic number, we'll want to pass a | 
 |   // histogram into CheckForMagicNumbers(), below, to see which one matched. | 
 |   static const struct MagicNumber kCRXMagicNumbers[] = { | 
 |     MAGIC_NUMBER("application/x-chrome-extension", "Cr24\x02\x00\x00\x00") | 
 |   }; | 
 |  | 
 |   // Only consider files that have the extension ".crx". | 
 |   static const char kCRXExtension[] = ".crx"; | 
 |   // Ignore null by subtracting 1. | 
 |   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::Histogram* should_sniff_counter(NULL); | 
 |   if (!should_sniff_counter) | 
 |     should_sniff_counter = | 
 |         UMASnifferHistogramGet("mime_sniffer.ShouldSniffMimeType2", 3); | 
 |   bool sniffable_scheme = url.is_empty() || | 
 |                           url.SchemeIs("http") || | 
 |                           url.SchemeIs("https") || | 
 |                           url.SchemeIs("ftp") || | 
 |                           url.SchemeIsFile() || | 
 |                           url.SchemeIsFileSystem(); | 
 |   if (!sniffable_scheme) { | 
 |     should_sniff_counter->Add(1); | 
 |     return false; | 
 |   } | 
 |  | 
 |   static const char* kSniffableTypes[] = { | 
 |     // Many web servers are misconfigured to send text/plain for many | 
 |     // different types of content. | 
 |     "text/plain", | 
 |     // We want to sniff application/octet-stream for | 
 |     // application/x-chrome-extension, but nothing else. | 
 |     "application/octet-stream", | 
 |     // XHTML and Atom/RSS feeds are often served as plain xml instead of | 
 |     // their more specific mime types. | 
 |     "text/xml", | 
 |     "application/xml", | 
 |   }; | 
 |   static base::Histogram* 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)) { | 
 |     // The web server didn't specify a content type or specified a mime | 
 |     // type that we ignore. | 
 |     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);  // sanity check | 
 |   DCHECK(content); | 
 |   DCHECK(result); | 
 |  | 
 |   // By default, we assume we have enough content. | 
 |   // Each sniff routine may unset this if it wasn't provided enough content. | 
 |   bool have_enough_content = true; | 
 |  | 
 |   // By default, we'll return the type hint. | 
 |   // Each sniff routine may modify this if it has a better guess.. | 
 |   result->assign(type_hint); | 
 |  | 
 |   // Cache information about the type_hint | 
 |   const bool hint_is_unknown_mime_type = IsUnknownMimeType(type_hint); | 
 |  | 
 |   // First check for HTML | 
 |   if (hint_is_unknown_mime_type) { | 
 |     // We're only willing to sniff HTML if the server has not supplied a mime | 
 |     // type, or if the type it did supply indicates that it doesn't know what | 
 |     // the type should be. | 
 |     if (SniffForHTML(content, content_size, &have_enough_content, result)) | 
 |       return true;  // We succeeded in sniffing HTML.  No more content needed. | 
 |   } | 
 |  | 
 |   // We're only willing to sniff for binary in 3 cases: | 
 |   // 1. The server has not supplied a mime type. | 
 |   // 2. The type it did supply indicates that it doesn't know what the type | 
 |   //    should be. | 
 |   // 3. The type is "text/plain" which is the default on some web servers and | 
 |   //    could be indicative of a mis-configuration that we shield the user from. | 
 |   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 the server said the content was text/plain and it doesn't appear | 
 |       // to be binary, then we trust it. | 
 |       if (hint_is_text_plain) { | 
 |         return have_enough_content; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // If we have plain XML, sniff XML subtypes. | 
 |   if (type_hint == "text/xml" || type_hint == "application/xml") { | 
 |     // We're not interested in sniffing these types for images and the like. | 
 |     // Instead, we're looking explicitly for a feed.  If we don't find one | 
 |     // we're done and return early. | 
 |     if (SniffXML(content, content_size, &have_enough_content, result)) | 
 |       return true; | 
 |     return have_enough_content; | 
 |   } | 
 |  | 
 |   // CRX files (chrome extensions) have a special sniffing algorithm. It is | 
 |   // tighter than the others because we don't have to match legacy behavior. | 
 |   if (SniffCRX(content, content_size, url, type_hint, | 
 |                &have_enough_content, result)) | 
 |     return true; | 
 |  | 
 |   // We're not interested in sniffing for magic numbers when the type_hint | 
 |   // is application/octet-stream.  Time to bail out. | 
 |   if (type_hint == "application/octet-stream") | 
 |     return have_enough_content; | 
 |  | 
 |   // Now we look in our large table of magic numbers to see if we can find | 
 |   // anything that matches the content. | 
 |   if (SniffForMagicNumbers(content, content_size, | 
 |                            &have_enough_content, result)) | 
 |     return true;  // We've matched a magic number.  No more content needed. | 
 |  | 
 |   return have_enough_content; | 
 | } | 
 |  | 
 | }  // namespace net |