• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2006-2008 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.
4 
5 // Detecting mime types is a tricky business because we need to balance
6 // compatibility concerns with security issues.  Here is a survey of how other
7 // browsers behave and then a description of how we intend to behave.
8 //
9 // HTML payload, no Content-Type header:
10 // * IE 7: Render as HTML
11 // * Firefox 2: Render as HTML
12 // * Safari 3: Render as HTML
13 // * Opera 9: Render as HTML
14 //
15 // Here the choice seems clear:
16 // => Chrome: Render as HTML
17 //
18 // HTML payload, Content-Type: "text/plain":
19 // * IE 7: Render as HTML
20 // * Firefox 2: Render as text
21 // * Safari 3: Render as text (Note: Safari will Render as HTML if the URL
22 //                                   has an HTML extension)
23 // * Opera 9: Render as text
24 //
25 // Here we choose to follow the majority (and break some compatibility with IE).
26 // Many folks dislike IE's behavior here.
27 // => Chrome: Render as text
28 // We generalize this as follows.  If the Content-Type header is text/plain
29 // we won't detect dangerous mime types (those that can execute script).
30 //
31 // HTML payload, Content-Type: "application/octet-stream":
32 // * IE 7: Render as HTML
33 // * Firefox 2: Download as application/octet-stream
34 // * Safari 3: Render as HTML
35 // * Opera 9: Render as HTML
36 //
37 // We follow Firefox.
38 // => Chrome: Download as application/octet-stream
39 // One factor in this decision is that IIS 4 and 5 will send
40 // application/octet-stream for .xhtml files (because they don't recognize
41 // the extension).  We did some experiments and it looks like this doesn't occur
42 // very often on the web.  We choose the more secure option.
43 //
44 // GIF payload, no Content-Type header:
45 // * IE 7: Render as GIF
46 // * Firefox 2: Render as GIF
47 // * Safari 3: Download as Unknown (Note: Safari will Render as GIF if the
48 //                                        URL has an GIF extension)
49 // * Opera 9: Render as GIF
50 //
51 // The choice is clear.
52 // => Chrome: Render as GIF
53 // Once we decide to render HTML without a Content-Type header, there isn't much
54 // reason not to render GIFs.
55 //
56 // GIF payload, Content-Type: "text/plain":
57 // * IE 7: Render as GIF
58 // * Firefox 2: Download as application/octet-stream (Note: Firefox will
59 //                              Download as GIF if the URL has an GIF extension)
60 // * Safari 3: Download as Unknown (Note: Safari will Render as GIF if the
61 //                                        URL has an GIF extension)
62 // * Opera 9: Render as GIF
63 //
64 // Displaying as text/plain makes little sense as the content will look like
65 // gibberish.  Here, we could change our minds and download.
66 // => Chrome: Render as GIF
67 //
68 // GIF payload, Content-Type: "application/octet-stream":
69 // * IE 7: Render as GIF
70 // * Firefox 2: Download as application/octet-stream (Note: Firefox will
71 //                              Download as GIF if the URL has an GIF extension)
72 // * Safari 3: Download as Unknown (Note: Safari will Render as GIF if the
73 //                                        URL has an GIF extension)
74 // * Opera 9: Render as GIF
75 //
76 // We used to render as GIF here, but the problem is that some sites want to
77 // trigger downloads by sending application/octet-stream (even though they
78 // should be sending Content-Disposition: attachment).  Although it is safe
79 // to render as GIF from a security perspective, we actually get better
80 // compatibility if we don't sniff from application/octet stream at all.
81 // => Chrome: Download as application/octet-stream
82 //
83 // XHTML payload, Content-Type: "text/xml":
84 // * IE 7: Render as XML
85 // * Firefox 2: Render as HTML
86 // * Safari 3: Render as HTML
87 // * Opera 9: Render as HTML
88 // The layout tests rely on us rendering this as HTML.
89 // But we're conservative in XHTML detection, as this runs afoul of the
90 // "don't detect dangerous mime types" rule.
91 //
92 // Note that our definition of HTML payload is much stricter than IE's
93 // definition and roughly the same as Firefox's definition.
94 
95 #include <string>
96 
97 #include "net/base/mime_sniffer.h"
98 
99 #include "base/basictypes.h"
100 #include "base/histogram.h"
101 #include "base/logging.h"
102 #include "base/string_util.h"
103 #include "googleurl/src/gurl.h"
104 #include "net/base/mime_util.h"
105 
106 namespace net {
107 
108 // We aren't interested in looking at more than 512 bytes of content
109 static const size_t kMaxBytesToSniff = 512;
110 
111 // The number of content bytes we need to use all our magic numbers.  Feel free
112 // to increase this number if you add a longer magic number.
113 static const size_t kBytesRequiredForMagic = 42;
114 
115 struct MagicNumber {
116   const char* mime_type;
117   const char* magic;
118   size_t magic_len;
119   bool is_string;
120 };
121 
122 #define MAGIC_NUMBER(mime_type, magic) \
123   { (mime_type), (magic), sizeof(magic)-1, false },
124 
125 // Magic strings are case insensitive and must not include '\0' characters
126 #define MAGIC_STRING(mime_type, magic) \
127   { (mime_type), (magic), sizeof(magic)-1, true },
128 
129 static const MagicNumber kMagicNumbers[] = {
130   // Source: HTML 5 specification
131   MAGIC_NUMBER("application/pdf", "%PDF-")
132   MAGIC_NUMBER("application/postscript", "%!PS-Adobe-")
133   MAGIC_NUMBER("image/gif", "GIF87a")
134   MAGIC_NUMBER("image/gif", "GIF89a")
135   MAGIC_NUMBER("image/png", "\x89" "PNG\x0D\x0A\x1A\x0A")
136   MAGIC_NUMBER("image/jpeg", "\xFF\xD8\xFF")
137   MAGIC_NUMBER("image/bmp", "BM")
138   // Source: Mozilla
139   MAGIC_NUMBER("text/plain", "#!")  // Script
140   MAGIC_NUMBER("text/plain", "%!")  // Script, similar to PS
141   MAGIC_NUMBER("text/plain", "From")
142   MAGIC_NUMBER("text/plain", ">From")
143   // Chrome specific
144   MAGIC_NUMBER("application/x-gzip", "\x1F\x8B\x08")
145   MAGIC_NUMBER("audio/x-pn-realaudio", "\x2E\x52\x4D\x46")
146   MAGIC_NUMBER("video/x-ms-asf",
147       "\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C")
148   MAGIC_NUMBER("image/tiff", "I I")
149   MAGIC_NUMBER("image/tiff", "II*")
150   MAGIC_NUMBER("image/tiff", "MM\x00*")
151   MAGIC_NUMBER("audio/mpeg", "ID3")
152   // TODO(abarth): we don't handle partial byte matches yet
153   // MAGIC_NUMBER("video/mpeg", "\x00\x00\x01\xB")
154   // MAGIC_NUMBER("audio/mpeg", "\xFF\xE")
155   // MAGIC_NUMBER("audio/mpeg", "\xFF\xF")
156   MAGIC_NUMBER("application/zip", "PK\x03\x04")
157   MAGIC_NUMBER("application/x-rar-compressed", "Rar!\x1A\x07\x00")
158   MAGIC_NUMBER("application/x-msmetafile", "\xD7\xCD\xC6\x9A")
159   MAGIC_NUMBER("application/octet-stream", "MZ")  // EXE
160   // Sniffing for Flash:
161   //
162   //   MAGIC_NUMBER("application/x-shockwave-flash", "CWS")
163   //   MAGIC_NUMBER("application/x-shockwave-flash", "FLV")
164   //   MAGIC_NUMBER("application/x-shockwave-flash", "FWS")
165   //
166   // Including these magic number for Flash is a trade off.
167   //
168   // Pros:
169   //   * Flash is an important and popular file format
170   //
171   // Cons:
172   //   * These patterns are fairly weak
173   //   * If we mistakenly decide something is Flash, we will execute it
174   //     in the origin of an unsuspecting site.  This could be a security
175   //     vulnerability if the site allows users to upload content.
176   //
177   // On balance, we do not include these patterns.
178 };
179 
180 // Our HTML sniffer differs slightly from Mozilla.  For example, Mozilla will
181 // decide that a document that begins "<!DOCTYPE SOAP-ENV:Envelope PUBLIC " is
182 // HTML, but we will not.
183 
184 #define MAGIC_HTML_TAG(tag) \
185   MAGIC_STRING("text/html", "<" tag)
186 
187 static const MagicNumber kSniffableTags[] = {
188   // XML processing directive.  Although this is not an HTML mime type, we sniff
189   // for this in the HTML phase because text/xml is just as powerful as HTML and
190   // we want to leverage our white space skipping technology.
191   MAGIC_NUMBER("text/xml", "<?xml")  // Mozilla
192   // DOCTYPEs
193   MAGIC_HTML_TAG("!DOCTYPE html")  // HTML5 spec
194   // Sniffable tags, ordered by how often they occur in sniffable documents.
195   MAGIC_HTML_TAG("script")  // HTML5 spec, Mozilla
196   MAGIC_HTML_TAG("html")  // HTML5 spec, Mozilla
197   MAGIC_HTML_TAG("!--")
198   MAGIC_HTML_TAG("head")  // HTML5 spec, Mozilla
199   MAGIC_HTML_TAG("iframe")  // Mozilla
200   MAGIC_HTML_TAG("h1")  // Mozilla
201   MAGIC_HTML_TAG("div")  // Mozilla
202   MAGIC_HTML_TAG("font")  // Mozilla
203   MAGIC_HTML_TAG("table")  // Mozilla
204   MAGIC_HTML_TAG("a")  // Mozilla
205   MAGIC_HTML_TAG("style")  // Mozilla
206   MAGIC_HTML_TAG("title")  // Mozilla
207   MAGIC_HTML_TAG("b")  // Mozilla
208   MAGIC_HTML_TAG("body")  // Mozilla
209   MAGIC_HTML_TAG("br")
210   MAGIC_HTML_TAG("p")  // Mozilla
211 };
212 
UMASnifferHistogramGet(const char * name,int array_size)213 static scoped_refptr<Histogram> UMASnifferHistogramGet(const char* name,
214                                                        int array_size) {
215   scoped_refptr<Histogram> counter =
216       LinearHistogram::FactoryGet(name, 1, array_size - 1, array_size,
217       Histogram::kUmaTargetedHistogramFlag);
218   return counter;
219 }
220 
MatchMagicNumber(const char * content,size_t size,const MagicNumber * magic_entry,std::string * result)221 static bool MatchMagicNumber(const char* content, size_t size,
222                              const MagicNumber* magic_entry,
223                              std::string* result) {
224   const size_t len = magic_entry->magic_len;
225 
226   // Keep kBytesRequiredForMagic honest.
227   DCHECK(len <= kBytesRequiredForMagic);
228 
229   // To compare with magic strings, we need to compute strlen(content), but
230   // content might not actually have a null terminator.  In that case, we
231   // pretend the length is content_size.
232   const char* end =
233       static_cast<const char*>(memchr(content, '\0', size));
234   const size_t content_strlen = (end != NULL) ? (end - content) : size;
235 
236   bool match = false;
237   if (magic_entry->is_string) {
238     if (content_strlen >= len) {
239       // String comparisons are case-insensitive
240       match = (base::strncasecmp(magic_entry->magic, content, len) == 0);
241     }
242   } else {
243     if (size >= len)
244       match = (memcmp(magic_entry->magic, content, len) == 0);
245   }
246 
247   if (match) {
248     result->assign(magic_entry->mime_type);
249     return true;
250   }
251   return false;
252 }
253 
CheckForMagicNumbers(const char * content,size_t size,const MagicNumber * magic,size_t magic_len,Histogram * counter,std::string * result)254 static bool CheckForMagicNumbers(const char* content, size_t size,
255                                  const MagicNumber* magic, size_t magic_len,
256                                  Histogram* counter, std::string* result) {
257   for (size_t i = 0; i < magic_len; ++i) {
258     if (MatchMagicNumber(content, size, &(magic[i]), result)) {
259       if (counter) counter->Add(static_cast<int>(i));
260       return true;
261     }
262   }
263   return false;
264 }
265 
SniffForHTML(const char * content,size_t size,std::string * result)266 static bool SniffForHTML(const char* content, size_t size,
267                          std::string* result) {
268   // We adopt a strategy similar to that used by Mozilla to sniff HTML tags,
269   // but with some modifications to better match the HTML5 spec.
270   const char* const end = content + size;
271   const char* pos;
272   for (pos = content; pos < end; ++pos) {
273     if (!IsAsciiWhitespace(*pos))
274       break;
275   }
276   static scoped_refptr<Histogram> counter =
277       UMASnifferHistogramGet("mime_sniffer.kSniffableTags2",
278                              arraysize(kSniffableTags));
279   // |pos| now points to first non-whitespace character (or at end).
280   return CheckForMagicNumbers(pos, end - pos,
281                               kSniffableTags, arraysize(kSniffableTags),
282                               counter.get(), result);
283 }
284 
SniffForMagicNumbers(const char * content,size_t size,std::string * result)285 static bool SniffForMagicNumbers(const char* content, size_t size,
286                                  std::string* result) {
287   // Check our big table of Magic Numbers
288   static scoped_refptr<Histogram> counter =
289       UMASnifferHistogramGet("mime_sniffer.kMagicNumbers2",
290                              arraysize(kMagicNumbers));
291   return CheckForMagicNumbers(content, size,
292                               kMagicNumbers, arraysize(kMagicNumbers),
293                               counter.get(), result);
294 }
295 
296 // Byte order marks
297 static const MagicNumber kMagicXML[] = {
298   // We want to be very conservative in interpreting text/xml content as
299   // XHTML -- we just want to sniff enough to make unit tests pass.
300   // So we match explicitly on this, and don't match other ways of writing
301   // it in semantically-equivalent ways.
302   MAGIC_STRING("application/xhtml+xml",
303                "<html xmlns=\"http://www.w3.org/1999/xhtml\"")
304   MAGIC_STRING("application/atom+xml", "<feed")
305   MAGIC_STRING("application/rss+xml", "<rss")  // UTF-8
306 };
307 
308 // Sniff an XML document to judge whether it contains XHTML or a feed.
309 // Returns true if it has seen enough content to make a definitive decision.
310 // TODO(evanm): this is similar but more conservative than what Safari does,
311 // while HTML5 has a different recommendation -- what should we do?
312 // TODO(evanm): this is incorrect for documents whose encoding isn't a superset
313 // of ASCII -- do we care?
SniffXML(const char * content,size_t size,std::string * result)314 static bool SniffXML(const char* content, size_t size, std::string* result) {
315   // We allow at most kFirstTagBytes bytes of content before we expect the
316   // opening tag.
317   const size_t kFeedAllowedHeaderBytes = 300;
318   const char* const end = content + std::min(size, kFeedAllowedHeaderBytes);
319   const char* pos = content;
320 
321   // This loop iterates through tag-looking offsets in the file.
322   // We want to skip XML processing instructions (of the form "<?xml ...")
323   // and stop at the first "plain" tag, then make a decision on the mime-type
324   // based on the name (or possibly attributes) of that tag.
325   static scoped_refptr<Histogram> counter =
326       UMASnifferHistogramGet("mime_sniffer.kMagicXML2",
327                              arraysize(kMagicXML));
328   const int kMaxTagIterations = 5;
329   for (int i = 0; i < kMaxTagIterations && pos < end; ++i) {
330     pos = reinterpret_cast<const char*>(memchr(pos, '<', end - pos));
331     if (!pos)
332       return false;
333 
334     if (base::strncasecmp(pos, "<?xml", sizeof("<?xml")-1) == 0) {
335       // Skip XML declarations.
336       ++pos;
337       continue;
338     } else if (base::strncasecmp(pos, "<!DOCTYPE",
339                                  sizeof("<!DOCTYPE")-1) == 0) {
340       // Skip DOCTYPE declarations.
341       ++pos;
342       continue;
343     }
344 
345     if (CheckForMagicNumbers(pos, end - pos,
346                              kMagicXML, arraysize(kMagicXML),
347                              counter.get(), result))
348       return true;
349 
350     // TODO(evanm): handle RSS 1.0, which is an RDF format and more difficult
351     // to identify.
352 
353     // If we get here, we've hit an initial tag that hasn't matched one of the
354     // above tests.  Abort.
355     return true;
356   }
357 
358   // We iterated too far without finding a start tag.
359   // If we have more content to look at, we aren't going to change our mind by
360   // seeing more bytes from the network.
361   return pos < end;
362 }
363 
364 // Byte order marks
365 static const MagicNumber kByteOrderMark[] = {
366   MAGIC_NUMBER("text/plain", "\xFE\xFF")  // UTF-16BE
367   MAGIC_NUMBER("text/plain", "\xFF\xFE")  // UTF-16LE
368   MAGIC_NUMBER("text/plain", "\xEF\xBB\xBF")  // UTF-8
369 };
370 
371 // Whether a given byte looks like it might be part of binary content.
372 // Source: HTML5 spec
373 static char kByteLooksBinary[] = {
374   1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1,  // 0x00 - 0x0F
375   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,  // 0x10 - 0x1F
376   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x20 - 0x2F
377   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x30 - 0x3F
378   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x40 - 0x4F
379   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x50 - 0x5F
380   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x60 - 0x6F
381   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x70 - 0x7F
382   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x80 - 0x8F
383   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0x90 - 0x9F
384   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xA0 - 0xAF
385   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xB0 - 0xBF
386   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xC0 - 0xCF
387   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xD0 - 0xDF
388   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xE0 - 0xEF
389   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0xF0 - 0xFF
390 };
391 
LooksBinary(const char * content,size_t size)392 static bool LooksBinary(const char* content, size_t size) {
393   // First, we look for a BOM.
394   static scoped_refptr<Histogram> counter =
395       UMASnifferHistogramGet("mime_sniffer.kByteOrderMark2",
396                              arraysize(kByteOrderMark));
397   std::string unused;
398   if (CheckForMagicNumbers(content, size,
399                            kByteOrderMark, arraysize(kByteOrderMark),
400                            counter.get(), &unused)) {
401     // If there is BOM, we think the buffer is not binary.
402     return false;
403   }
404 
405   // Next we look to see if any of the bytes "look binary."
406   for (size_t i = 0; i < size; ++i) {
407     // If we a see a binary-looking byte, we think the content is binary.
408     if (kByteLooksBinary[static_cast<unsigned char>(content[i])])
409       return true;
410   }
411 
412   // No evidence either way, default to non-binary.
413   return false;
414 }
415 
IsUnknownMimeType(const std::string & mime_type)416 static bool IsUnknownMimeType(const std::string& mime_type) {
417   // TODO(tc): Maybe reuse some code in net/http/http_response_headers.* here.
418   // If we do, please be careful not to alter the semantics at all.
419   static const char* kUnknownMimeTypes[] = {
420     // Empty mime types are as unknown as they get.
421     "",
422     // The unknown/unknown type is popular and uninformative
423     "unknown/unknown",
424     // The second most popular unknown mime type is application/unknown
425     "application/unknown",
426     // Firefox rejects a mime type if it is exactly */*
427     "*/*",
428   };
429   static scoped_refptr<Histogram> counter =
430       UMASnifferHistogramGet("mime_sniffer.kUnknownMimeTypes2",
431                              arraysize(kUnknownMimeTypes) + 1);
432   for (size_t i = 0; i < arraysize(kUnknownMimeTypes); ++i) {
433     if (mime_type == kUnknownMimeTypes[i]) {
434       counter->Add(i);
435       return true;
436     }
437   }
438   if (mime_type.find('/') == std::string::npos) {
439     // Firefox rejects a mime type if it does not contain a slash
440     counter->Add(arraysize(kUnknownMimeTypes));
441     return true;
442   }
443   return false;
444 }
445 
446 // Sniff a crx (chrome extension) file.
SniffCRX(const char * content,size_t content_size,const GURL & url,const std::string & type_hint,std::string * result)447 static bool SniffCRX(const char* content, size_t content_size, const GURL& url,
448                      const std::string& type_hint, std::string* result) {
449   static scoped_refptr<Histogram> counter =
450       UMASnifferHistogramGet("mime_sniffer.kSniffCRX", 3);
451 
452   // Technically, the crx magic number is just Cr24, but the bytes after that
453   // are a version number which changes infrequently. Including it in the
454   // sniffing gives us less room for error. If the version number ever changes,
455   // we can just add an entry to this list.
456   //
457   // TODO(aa): If we ever have another magic number, we'll want to pass a
458   // histogram into CheckForMagicNumbers(), below, to see which one matched.
459   const struct MagicNumber kCRXMagicNumbers[] = {
460     MAGIC_NUMBER("application/x-chrome-extension", "Cr24\x02\x00\x00\x00")
461   };
462 
463   // Only consider files that have the extension ".crx".
464   const char kCRXExtension[] = ".crx";
465   const int kExtensionLength = arraysize(kCRXExtension) - 1;  // ignore null
466   if (url.path().rfind(kCRXExtension, std::string::npos, kExtensionLength) ==
467       url.path().size() - kExtensionLength) {
468     counter->Add(1);
469   } else {
470     return false;
471   }
472 
473   if (CheckForMagicNumbers(content, content_size,
474                            kCRXMagicNumbers, arraysize(kCRXMagicNumbers),
475                            NULL, result)) {
476     counter->Add(2);
477   } else {
478     return false;
479   }
480 
481   return true;
482 }
483 
ShouldSniffMimeType(const GURL & url,const std::string & mime_type)484 bool ShouldSniffMimeType(const GURL& url, const std::string& mime_type) {
485   static scoped_refptr<Histogram> should_sniff_counter =
486       UMASnifferHistogramGet("mime_sniffer.ShouldSniffMimeType2", 3);
487   // We are willing to sniff the mime type for HTTP, HTTPS, and FTP
488   bool sniffable_scheme = url.is_empty() ||
489                           url.SchemeIs("http") ||
490                           url.SchemeIs("https") ||
491                           url.SchemeIs("ftp");
492   if (!sniffable_scheme) {
493     should_sniff_counter->Add(1);
494     return false;
495   }
496 
497   static const char* kSniffableTypes[] = {
498     // Many web servers are misconfigured to send text/plain for many
499     // different types of content.
500     "text/plain",
501     // We want to sniff application/octet-stream for
502     // application/x-chrome-extension, but nothing else.
503     "application/octet-stream",
504     // XHTML and Atom/RSS feeds are often served as plain xml instead of
505     // their more specific mime types.
506     "text/xml",
507     "application/xml",
508   };
509   static scoped_refptr<Histogram> counter =
510       UMASnifferHistogramGet("mime_sniffer.kSniffableTypes2",
511                              arraysize(kSniffableTypes) + 1);
512   for (size_t i = 0; i < arraysize(kSniffableTypes); ++i) {
513     if (mime_type == kSniffableTypes[i]) {
514       counter->Add(i);
515       should_sniff_counter->Add(2);
516       return true;
517     }
518   }
519   if (IsUnknownMimeType(mime_type)) {
520     // The web server didn't specify a content type or specified a mime
521     // type that we ignore.
522     counter->Add(arraysize(kSniffableTypes));
523     should_sniff_counter->Add(2);
524     return true;
525   }
526   should_sniff_counter->Add(1);
527   return false;
528 }
529 
SniffMimeType(const char * content,size_t content_size,const GURL & url,const std::string & type_hint,std::string * result)530 bool SniffMimeType(const char* content, size_t content_size,
531                    const GURL& url, const std::string& type_hint,
532                    std::string* result) {
533   DCHECK_LT(content_size, 1000000U);  // sanity check
534   DCHECK(content);
535   DCHECK(result);
536 
537   // By default, we'll return the type hint.
538   result->assign(type_hint);
539 
540   // Flag for tracking whether our decision was limited by content_size.  We
541   // probably have enough content if we can use all our magic numbers.
542   const bool have_enough_content = content_size >= kBytesRequiredForMagic;
543 
544   // We have an upper limit on the number of bytes we will consider.
545   if (content_size > kMaxBytesToSniff)
546     content_size = kMaxBytesToSniff;
547 
548   // Cache information about the type_hint
549   const bool hint_is_unknown_mime_type = IsUnknownMimeType(type_hint);
550 
551   // First check for HTML
552   if (hint_is_unknown_mime_type) {
553     // We're only willing to sniff HTML if the server has not supplied a mime
554     // type, or if the type it did supply indicates that it doesn't know what
555     // the type should be.
556     if (SniffForHTML(content, content_size, result))
557       return true;  // We succeeded in sniffing HTML.  No more content needed.
558   }
559 
560   // We'll reuse this information later
561   const bool hint_is_text_plain = (type_hint == "text/plain");
562   const bool looks_binary = LooksBinary(content, content_size);
563 
564   if (hint_is_text_plain && !looks_binary) {
565     // The server said the content was text/plain and we don't really have any
566     // evidence otherwise.
567     result->assign("text/plain");
568     return have_enough_content;
569   }
570 
571   // If we have plain XML, sniff XML subtypes.
572   if (type_hint == "text/xml" || type_hint == "application/xml") {
573     // We're not interested in sniffing these types for images and the like.
574     // Instead, we're looking explicitly for a feed.  If we don't find one we're
575     // done and return early.
576     if (SniffXML(content, content_size, result))
577       return true;
578     return content_size >= kMaxBytesToSniff;
579   }
580 
581   // CRX files (chrome extensions) have a special sniffing algorithm. It is
582   // tighter than the others because we don't have to match legacy behavior.
583   if (SniffCRX(content, content_size, url, type_hint, result))
584     return true;
585 
586   // We're not interested in sniffing for magic numbers when the type_hint
587   // is application/octet-stream.  Time to bail out.
588   if (type_hint == "application/octet-stream")
589     return have_enough_content;
590 
591   // Now we look in our large table of magic numbers to see if we can find
592   // anything that matches the content.
593   if (SniffForMagicNumbers(content, content_size, result))
594     return true;  // We've matched a magic number.  No more content needed.
595 
596   // Having failed thus far, we're willing to override unknown mime types and
597   // text/plain.
598   if (hint_is_unknown_mime_type || hint_is_text_plain) {
599     if (looks_binary)
600       result->assign("application/octet-stream");
601     else
602       result->assign("text/plain");
603     // We could change our mind if a binary-looking byte appears later in
604     // the content, so we only have enough content if we have the max.
605     return content_size >= kMaxBytesToSniff;
606   }
607 
608   return have_enough_content;
609 }
610 
611 }  // namespace net
612