// Copyright 2011 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/base/mime_sniffer.h" #include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" #include "url/url_constants.h" namespace net { namespace { using ::testing::Range; using ::testing::Values; using ::net::SniffMimeType; // It is shadowed by SniffMimeType(), below. // Turn |str|, a constant string with one or more embedded NULs, along with // a NUL terminator, into an std::string() containing just that data. // Turn |str|, a string with one or more embedded NULs, into an std::string() template std::string MakeConstantString(const char (&str)[N]) { return std::string(str, N - 1); } static std::string SniffMimeType(base::StringPiece content, const std::string& url, const std::string& mime_type_hint) { std::string mime_type; SniffMimeType(content, GURL(url), mime_type_hint, ForceSniffFileUrlsForHtml::kDisabled, &mime_type); return mime_type; } TEST(MimeSnifferTest, SniffableSchemes) { struct { const char* scheme; bool sniffable; } kTestCases[] = { {url::kAboutScheme, false}, {url::kBlobScheme, false}, #if BUILDFLAG(IS_ANDROID) {url::kContentScheme, true}, #else {url::kContentScheme, false}, #endif {url::kContentIDScheme, false}, {url::kDataScheme, false}, {url::kFileScheme, true}, {url::kFileSystemScheme, true}, {url::kFtpScheme, false}, {url::kHttpScheme, true}, {url::kHttpsScheme, true}, {url::kJavaScriptScheme, false}, {url::kMailToScheme, false}, {url::kWsScheme, false}, {url::kWssScheme, false} }; for (const auto test_case : kTestCases) { GURL url(std::string(test_case.scheme) + "://host/path/whatever"); EXPECT_EQ(test_case.sniffable, ShouldSniffMimeType(url, "")); } } TEST(MimeSnifferTest, BoundaryConditionsTest) { std::string mime_type; std::string type_hint; char buf[] = { 'd', '\x1f', '\xFF' }; GURL url; SniffMimeType(base::StringPiece(), url, type_hint, ForceSniffFileUrlsForHtml::kDisabled, &mime_type); EXPECT_EQ("text/plain", mime_type); SniffMimeType(base::StringPiece(buf, 1), url, type_hint, ForceSniffFileUrlsForHtml::kDisabled, &mime_type); EXPECT_EQ("text/plain", mime_type); SniffMimeType(base::StringPiece(buf, 2), url, type_hint, ForceSniffFileUrlsForHtml::kDisabled, &mime_type); EXPECT_EQ("application/octet-stream", mime_type); } TEST(MimeSnifferTest, BasicSniffingTest) { EXPECT_EQ("text/html", SniffMimeType(MakeConstantString(""), "http://www.example.com/foo.gif", "application/octet-stream")); EXPECT_EQ("image/gif", SniffMimeType(MakeConstantString("GIF89a\x1F\x83\x94"), "http://www.example.com/foo", "text/plain")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("Gif87a\x1F\x83\x94"), "http://www.example.com/foo?param=tt.gif", "")); EXPECT_EQ("text/plain", SniffMimeType(MakeConstantString("%!PS-Adobe-3.0"), "http://www.example.com/foo", "text/plain")); EXPECT_EQ( "application/octet-stream", SniffMimeType(MakeConstantString("\x89" "PNG\x0D\x0A\x1A\x0A"), "http://www.example.com/foo", "application/octet-stream")); EXPECT_EQ("image/jpeg", SniffMimeType(MakeConstantString("\xFF\xD8\xFF\x23\x49\xAF"), "http://www.example.com/foo", "")); } TEST(MimeSnifferTest, ChromeExtensionsTest) { // schemes EXPECT_EQ("application/x-chrome-extension", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "http://www.example.com/foo.crx", "")); EXPECT_EQ("application/x-chrome-extension", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "https://www.example.com/foo.crx", "")); EXPECT_EQ("application/x-chrome-extension", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "ftp://www.example.com/foo.crx", "")); // some other mimetypes that should get converted EXPECT_EQ("application/x-chrome-extension", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "http://www.example.com/foo.crx", "text/plain")); EXPECT_EQ("application/x-chrome-extension", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "http://www.example.com/foo.crx", "application/octet-stream")); // success edge cases EXPECT_EQ("application/x-chrome-extension", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "http://www.example.com/foo.crx?query=string", "")); EXPECT_EQ("application/x-chrome-extension", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "http://www.example.com/foo..crx", "")); EXPECT_EQ("application/x-chrome-extension", SniffMimeType(MakeConstantString("Cr24\x03\x00\x00\x00"), "http://www.example.com/foo..crx", "")); // wrong file extension EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "http://www.example.com/foo.bin", "")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "http://www.example.com/foo.bin?monkey", "")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "invalid-url", "")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "http://www.example.com", "")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "http://www.example.com/", "")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "http://www.example.com/foo", "")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "http://www.example.com/foocrx", "")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x00"), "http://www.example.com/foo.crx.blech", "")); // wrong magic EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("Cr24\x02\x00\x00\x01"), "http://www.example.com/foo.crx?monkey", "")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("PADDING_Cr24\x02\x00\x00\x00"), "http://www.example.com/foo.crx?monkey", "")); } TEST(MimeSnifferTest, MozillaCompatibleTest) { EXPECT_EQ("text/html", SniffMimeType(MakeConstantString(" \n \n \n \n"), "http://www.example.com/foo", "")); EXPECT_EQ( "application/octet-stream", SniffMimeType( MakeConstantString("\n"), "http://www.example.com/foo", "application/octet-stream")); } TEST(MimeSnifferTest, DontAllowPrivilegeEscalationTest) { EXPECT_EQ( "image/gif", SniffMimeType(MakeConstantString("GIF87a\n\n" "" "\n"), "http://www.example.com/foo", "")); EXPECT_EQ( "image/gif", SniffMimeType(MakeConstantString("GIF87a\n\n" "" "\n"), "http://www.example.com/foo?q=ttt.html", "")); EXPECT_EQ( "image/gif", SniffMimeType(MakeConstantString("GIF87a\n\n" "" "\n"), "http://www.example.com/foo#ttt.html", "")); EXPECT_EQ( "text/plain", SniffMimeType(MakeConstantString("a\n\n" "" "\n"), "http://www.example.com/foo", "")); EXPECT_EQ( "text/plain", SniffMimeType(MakeConstantString("a\n\n" "" "\n"), "http://www.example.com/foo?q=ttt.html", "")); EXPECT_EQ( "text/plain", SniffMimeType(MakeConstantString("a\n\n" "" "\n"), "http://www.example.com/foo#ttt.html", "")); EXPECT_EQ( "text/plain", SniffMimeType(MakeConstantString("a\n\n" "" "\n"), "http://www.example.com/foo.html", "")); } TEST(MimeSnifferTest, SniffFilesAsHtml) { const std::string kContent = "text"; const GURL kUrl("file:///C/test.unusualextension"); std::string mime_type; SniffMimeType(kContent, kUrl, "" /* type_hint */, ForceSniffFileUrlsForHtml::kDisabled, &mime_type); EXPECT_EQ("text/plain", mime_type); SniffMimeType(kContent, kUrl, "" /* type_hint */, ForceSniffFileUrlsForHtml::kEnabled, &mime_type); EXPECT_EQ("text/html", mime_type); } TEST(MimeSnifferTest, UnicodeTest) { EXPECT_EQ("text/plain", SniffMimeType(MakeConstantString("\xEF\xBB\xBF" "Hi there"), "http://www.example.com/foo", "")); EXPECT_EQ( "text/plain", SniffMimeType(MakeConstantString("\xEF\xBB\xBF\xED\x7A\xAD\x7A\x0D\x79"), "http://www.example.com/foo", "")); EXPECT_EQ( "text/plain", SniffMimeType(MakeConstantString( "\xFE\xFF\xD0\xA5\xD0\xBE\xD0\xBB\xD1\x83\xD0\xB9"), "http://www.example.com/foo", "")); EXPECT_EQ("text/plain", SniffMimeType( MakeConstantString( "\xFE\xFF\x00\x41\x00\x20\xD8\x00\xDC\x00\xD8\x00\xDC\x01"), "http://www.example.com/foo", "")); } TEST(MimeSnifferTest, FlashTest) { EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("CWSdd\x00\xB3"), "http://www.example.com/foo", "")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("FLVjdkl*(#)0sdj\x00"), "http://www.example.com/foo?q=ttt.swf", "")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("FWS3$9\r\b\x00"), "http://www.example.com/foo#ttt.swf", "")); EXPECT_EQ("text/plain", SniffMimeType(MakeConstantString("FLVjdkl*(#)0sdj"), "http://www.example.com/foo.swf", "")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("FLVjdkl*(#)0s\x01dj"), "http://www.example.com/foo/bar.swf", "")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("FWS3$9\r\b\x1A"), "http://www.example.com/foo.swf?clickTAG=http://" "www.adnetwork.com/bar", "")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("FWS3$9\r\x1C\b"), "http://www.example.com/foo.swf?clickTAG=http://" "www.adnetwork.com/bar", "text/plain")); } TEST(MimeSnifferTest, XMLTest) { // An easy feed to identify. EXPECT_EQ("application/atom+xml", SniffMimeType("\r\n" "" "" ""; // CNN's RSS EXPECT_EQ("application/rss+xml", SniffMimeType(kCNNRSS, "", "text/xml")); EXPECT_EQ("text/plain", SniffMimeType(kCNNRSS, "", "text/plain")); // Don't sniff random XML as something different. EXPECT_EQ("text/xml", SniffMimeType("", "", "text/xml")); EXPECT_EQ("application/xml", SniffMimeType("", "", "application/xml")); EXPECT_EQ("text/plain", SniffMimeType("", "", "text/plain")); EXPECT_EQ("application/rss+xml", SniffMimeType("", "", "application/rss+xml")); EXPECT_EQ("text/xml", SniffMimeType("", "", "text/xml")); EXPECT_EQ("text/xml", SniffMimeType("", "", "text/xml")); } // Test content which is >= 1024 bytes, and includes no open angle bracket. // http://code.google.com/p/chromium/issues/detail?id=3521 TEST(MimeSnifferTest, XMLTestLargeNoAngledBracket) { // Make a large input, with 1024 bytes of "x". std::string content; content.resize(1024); std::fill(content.begin(), content.end(), 'x'); // content.size() >= 1024 so the sniff is unambiguous. std::string mime_type; EXPECT_TRUE(SniffMimeType(content, GURL(), "text/xml", ForceSniffFileUrlsForHtml::kDisabled, &mime_type)); EXPECT_EQ("text/xml", mime_type); } // Test content which is >= 1024 bytes, and includes a binary looking byte. // http://code.google.com/p/chromium/issues/detail?id=15314 TEST(MimeSnifferTest, LooksBinary) { // Make a large input, with 1024 bytes of "x" and 1 byte of 0x01. std::string content; content.resize(1024); std::fill(content.begin(), content.end(), 'x'); content[1000] = 0x01; // content.size() >= 1024 so the sniff is unambiguous. std::string mime_type; EXPECT_TRUE(SniffMimeType(content, GURL(), "text/plain", ForceSniffFileUrlsForHtml::kDisabled, &mime_type)); EXPECT_EQ("application/octet-stream", mime_type); } TEST(MimeSnifferTest, OfficeTest) { // Check for URLs incorrectly reported as Microsoft Office files. EXPECT_EQ( "application/octet-stream", SniffMimeType(MakeConstantString("Hi there"), "http://www.example.com/foo.doc", "application/msword")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("Hi there"), "http://www.example.com/foo.xls", "application/vnd.ms-excel")); EXPECT_EQ("application/octet-stream", SniffMimeType(MakeConstantString("Hi there"), "http://www.example.com/foo.ppt", "application/vnd.ms-powerpoint")); // Check for Microsoft Office files incorrectly reported as text. EXPECT_EQ( "application/msword", SniffMimeType(MakeConstantString("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" "Hi there"), "http://www.example.com/foo.doc", "text/plain")); EXPECT_EQ( "application/vnd.openxmlformats-officedocument." "wordprocessingml.document", SniffMimeType(MakeConstantString( "PK\x03\x04" "Hi there"), "http://www.example.com/foo.doc", "text/plain")); EXPECT_EQ( "application/vnd.ms-excel", SniffMimeType(MakeConstantString("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" "Hi there"), "http://www.example.com/foo.xls", "text/plain")); EXPECT_EQ( "application/vnd.openxmlformats-officedocument." "spreadsheetml.sheet", SniffMimeType(MakeConstantString("PK\x03\x04" "Hi there"), "http://www.example.com/foo.xls", "text/plain")); EXPECT_EQ( "application/vnd.ms-powerpoint", SniffMimeType(MakeConstantString("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" "Hi there"), "http://www.example.com/foo.ppt", "text/plain")); EXPECT_EQ( "application/vnd.openxmlformats-officedocument." "presentationml.presentation", SniffMimeType(MakeConstantString("PK\x03\x04" "Hi there"), "http://www.example.com/foo.ppt", "text/plain")); } TEST(MimeSnifferTest, AudioVideoTest) { std::string mime_type; const char kOggTestData[] = "OggS\x00"; EXPECT_TRUE(SniffMimeTypeFromLocalData( base::StringPiece(kOggTestData, sizeof(kOggTestData) - 1), &mime_type)); EXPECT_EQ("audio/ogg", mime_type); mime_type.clear(); // Check ogg header requires the terminal '\0' to be sniffed. EXPECT_FALSE(SniffMimeTypeFromLocalData( base::StringPiece(kOggTestData, sizeof(kOggTestData) - 2), &mime_type)); EXPECT_EQ("", mime_type); mime_type.clear(); const char kFlacTestData[] = "fLaC\x00\x00\x00\x22\x12\x00\x12\x00\x00\x00\x00\x00"; EXPECT_TRUE(SniffMimeTypeFromLocalData( base::StringPiece(kFlacTestData, sizeof(kFlacTestData) - 1), &mime_type)); EXPECT_EQ("audio/x-flac", mime_type); mime_type.clear(); const char kWMATestData[] = "\x30\x26\xb2\x75\x8e\x66\xcf\x11\xa6\xd9\x00\xaa\x00\x62\xce\x6c"; EXPECT_TRUE(SniffMimeTypeFromLocalData( base::StringPiece(kWMATestData, sizeof(kWMATestData) - 1), &mime_type)); EXPECT_EQ("video/x-ms-asf", mime_type); mime_type.clear(); // mp4a, m4b, m4p, and alac extension files which share the same container // format. const char kMP4TestData[] = "\x00\x00\x00\x20\x66\x74\x79\x70\x4d\x34\x41\x20\x00\x00\x00\x00"; EXPECT_TRUE(SniffMimeTypeFromLocalData( base::StringPiece(kMP4TestData, sizeof(kMP4TestData) - 1), &mime_type)); EXPECT_EQ("video/mp4", mime_type); mime_type.clear(); const char kAACTestData[] = "\xff\xf1\x50\x80\x02\x20\xb0\x23\x0a\x83\x20\x7d\x61\x90\x3e\xb1"; EXPECT_TRUE(SniffMimeTypeFromLocalData( base::StringPiece(kAACTestData, sizeof(kAACTestData) - 1), &mime_type)); EXPECT_EQ("audio/mpeg", mime_type); mime_type.clear(); const char kAMRTestData[] = "\x23\x21\x41\x4d\x52\x0a\x3c\x53\x0a\x7c\xe8\xb8\x41\xa5\x80\xca"; EXPECT_TRUE(SniffMimeTypeFromLocalData( base::StringPiece(kAMRTestData, sizeof(kAMRTestData) - 1), &mime_type)); EXPECT_EQ("audio/amr", mime_type); mime_type.clear(); } TEST(MimeSnifferTest, ImageTest) { std::string mime_type; const char kWebPSimpleFormat[] = "RIFF\xee\x81\x00\x00WEBPVP8 "; EXPECT_TRUE(SniffMimeTypeFromLocalData( base::StringPiece(kWebPSimpleFormat, sizeof(kWebPSimpleFormat) - 1), &mime_type)); EXPECT_EQ("image/webp", mime_type); mime_type.clear(); const char kWebPLosslessFormat[] = "RIFF\xee\x81\x00\x00WEBPVP8L"; EXPECT_TRUE(SniffMimeTypeFromLocalData( base::StringPiece(kWebPLosslessFormat, sizeof(kWebPLosslessFormat) - 1), &mime_type)); EXPECT_EQ("image/webp", mime_type); mime_type.clear(); const char kWebPExtendedFormat[] = "RIFF\xee\x81\x00\x00WEBPVP8X"; EXPECT_TRUE(SniffMimeTypeFromLocalData( base::StringPiece(kWebPExtendedFormat, sizeof(kWebPExtendedFormat) - 1), &mime_type)); EXPECT_EQ("image/webp", mime_type); mime_type.clear(); } // The tests need char parameters, but the ranges to test include 0xFF, and some // platforms have signed chars and are noisy about it. Using an int parameter // and casting it to char inside the test case solves both these problems. class MimeSnifferBinaryTest : public ::testing::TestWithParam {}; // From https://mimesniff.spec.whatwg.org/#binary-data-byte : // A binary data byte is a byte in the range 0x00 to 0x08 (NUL to BS), the byte // 0x0B (VT), a byte in the range 0x0E to 0x1A (SO to SUB), or a byte in the // range 0x1C to 0x1F (FS to US). TEST_P(MimeSnifferBinaryTest, IsBinaryControlCode) { std::string param(1, static_cast(GetParam())); EXPECT_TRUE(LooksLikeBinary(param)); } // ::testing::Range(a, b) tests an open-ended range, ie. "b" is not included. INSTANTIATE_TEST_SUITE_P(MimeSnifferBinaryTestRange1, MimeSnifferBinaryTest, Range(0x00, 0x09)); INSTANTIATE_TEST_SUITE_P(MimeSnifferBinaryTestByte0x0B, MimeSnifferBinaryTest, Values(0x0B)); INSTANTIATE_TEST_SUITE_P(MimeSnifferBinaryTestRange2, MimeSnifferBinaryTest, Range(0x0E, 0x1B)); INSTANTIATE_TEST_SUITE_P(MimeSnifferBinaryTestRange3, MimeSnifferBinaryTest, Range(0x1C, 0x20)); class MimeSnifferPlainTextTest : public ::testing::TestWithParam {}; TEST_P(MimeSnifferPlainTextTest, NotBinaryControlCode) { std::string param(1, static_cast(GetParam())); EXPECT_FALSE(LooksLikeBinary(param)); } INSTANTIATE_TEST_SUITE_P(MimeSnifferPlainTextTestPlainTextControlCodes, MimeSnifferPlainTextTest, Values(0x09, 0x0A, 0x0C, 0x0D, 0x1B)); INSTANTIATE_TEST_SUITE_P(MimeSnifferPlainTextTestNotControlCodeRange, MimeSnifferPlainTextTest, Range(0x20, 0x100)); class MimeSnifferControlCodesEdgeCaseTest : public ::testing::TestWithParam {}; TEST_P(MimeSnifferControlCodesEdgeCaseTest, EdgeCase) { EXPECT_TRUE(LooksLikeBinary(GetParam())); } INSTANTIATE_TEST_SUITE_P(MimeSnifferControlCodesEdgeCaseTest, MimeSnifferControlCodesEdgeCaseTest, Values("\x01__", // first byte is binary "__\x03", // last byte is binary "_\x02_" // a byte in the middle is binary )); } // namespace } // namespace net