// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/loader/cross_site_document_resource_handler.h" #include #include #include #include #include "base/command_line.h" #include "base/files/file_path.h" #include "base/location.h" #include "base/logging.h" #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/memory/weak_ptr.h" #include "base/single_thread_task_runner.h" #include "base/test/histogram_tester.h" #include "base/threading/thread_task_runner_handle.h" #include "content/browser/loader/mock_resource_loader.h" #include "content/browser/loader/resource_controller.h" #include "content/browser/loader/test_resource_handler.h" #include "content/public/browser/resource_request_info.h" #include "content/public/common/resource_response.h" #include "content/public/common/resource_type.h" #include "content/public/common/webplugininfo.h" #include "content/public/test/test_browser_thread_bundle.h" #include "content/public/test/test_utils.h" #include "net/base/net_errors.h" #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_status.h" #include "net/url_request/url_request_test_util.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" namespace content { namespace { enum class OriginHeader { kOmit, kInclude }; enum class AccessControlAllowOriginHeader { kOmit, kAllowAny, kAllowNull, kAllowInitiatorOrigin, kAllowExampleDotCom }; enum class Verdict { kAllowWithoutSniffing, kBlockWithoutSniffing, kAllowAfterSniffing, kBlockAfterSniffing }; // This struct is used to describe each test case in this file. It's passed as // a test parameter to each TEST_P test. struct TestScenario { // Attributes to make test failure messages useful. const char* description; int source_line; // Attributes of the HTTP Request. const char* target_url; ResourceType resource_type; const char* initiator_origin; OriginHeader cors_request; // Attributes of the HTTP response. const char* response_mime_type; CrossSiteDocumentMimeType canonical_mime_type; bool include_no_sniff_header; AccessControlAllowOriginHeader cors_response; const char* first_chunk; // Expected result. Verdict verdict; }; // Stream operator to let GetParam() print a useful result if any tests fail. ::std::ostream& operator<<(::std::ostream& os, const TestScenario& scenario) { std::string cors_response; switch (scenario.cors_response) { case AccessControlAllowOriginHeader::kOmit: cors_response = "AccessControlAllowOriginHeader::kOmit"; break; case AccessControlAllowOriginHeader::kAllowAny: cors_response = "AccessControlAllowOriginHeader::kAllowAny"; break; case AccessControlAllowOriginHeader::kAllowNull: cors_response = "AccessControlAllowOriginHeader::kAllowNull"; break; case AccessControlAllowOriginHeader::kAllowInitiatorOrigin: cors_response = "AccessControlAllowOriginHeader::kAllowInitiatorOrigin"; break; case AccessControlAllowOriginHeader::kAllowExampleDotCom: cors_response = "AccessControlAllowOriginHeader::kAllowExampleDotCom"; break; default: NOTREACHED(); } std::string verdict; switch (scenario.verdict) { case Verdict::kAllowWithoutSniffing: verdict = "Verdict::kAllowWithoutSniffing"; break; case Verdict::kBlockWithoutSniffing: verdict = "Verdict::kBlockWithoutSniffing"; break; case Verdict::kAllowAfterSniffing: verdict = "Verdict::kAllowAfterSniffing"; break; case Verdict::kBlockAfterSniffing: verdict = "Verdict::kBlockAfterSniffing"; break; default: NOTREACHED(); } return os << "\n description = " << scenario.description << "\n target_url = " << scenario.target_url << "\n resource_type = " << scenario.resource_type << "\n initiator_origin = " << scenario.initiator_origin << "\n cors_request = " << (scenario.cors_request == OriginHeader::kOmit ? "OriginHeader::kOmit" : "OriginHeader::kInclude") << "\n response_mime_type = " << scenario.response_mime_type << "\n canonical_mime_type = " << scenario.canonical_mime_type << "\n include_no_sniff = " << (scenario.include_no_sniff_header ? "true" : "false") << "\n cors_response = " << cors_response << "\n first_chunk = " << scenario.first_chunk << "\n verdict = " << verdict; } // A set of test cases that verify CrossSiteDocumentResourceHandler correctly // classifies network responses as allowed or blocked. These TestScenarios are // passed to the TEST_P tests below as test parameters. const TestScenario kScenarios[] = { // Allowed responses: { "Allowed: Same-site XHR to HTML", __LINE__, "http://www.a.com/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "text/html", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "this should sniff as HTML", // first_chunk Verdict::kAllowWithoutSniffing, // verdict }, { "Allowed: Cross-site script", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_SCRIPT, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "application/javascript", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_OTHERS, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "var x=3;", // first_chunk Verdict::kAllowWithoutSniffing, // verdict }, { "Allowed: Cross-site XHR to HTML with CORS for origin", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kInclude, // cors_request "text/html", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kAllowInitiatorOrigin, // cors_response "this should sniff as HTML", // first_chunk Verdict::kAllowWithoutSniffing, // verdict }, { "Allowed: Cross-site XHR to XML with CORS for any", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kInclude, // cors_request "application/rss+xml", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_XML, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kAllowAny, // cors_response "", // first_chunk Verdict::kAllowWithoutSniffing, // verdict }, { "Allowed: Cross-site XHR to JSON with CORS for null", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kInclude, // cors_request "text/json", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_JSON, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kAllowNull, // cors_response "{\"x\" : 3}", // first_chunk Verdict::kAllowWithoutSniffing, // verdict }, { "Allowed: Cross-site XHR to HTML over FTP", __LINE__, "ftp://www.b.com/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "text/html", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "this should sniff as HTML", // first_chunk Verdict::kAllowWithoutSniffing, // verdict }, { "Allowed: Cross-site XHR to HTML from file://", __LINE__, "file:///foo/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "text/html", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "this should sniff as HTML", // first_chunk Verdict::kAllowWithoutSniffing, // verdict }, { "Allowed: Cross-site fetch HTML from Flash without CORS", __LINE__, "http://www.b.com/plugin.html", // target_url RESOURCE_TYPE_PLUGIN_RESOURCE, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "text/html", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "this should sniff as HTML", // first_chunk Verdict::kAllowWithoutSniffing, // verdict }, { "Allowed: Cross-site fetch HTML from NaCl with CORS response", __LINE__, "http://www.b.com/plugin.html", // target_url RESOURCE_TYPE_PLUGIN_RESOURCE, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kInclude, // cors_request "text/html", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kAllowInitiatorOrigin, // cors_response "this should sniff as HTML", // first_chunk Verdict::kAllowWithoutSniffing, // verdict }, // Allowed responses due to sniffing: { "Allowed: Cross-site script to JSONP labeled as HTML", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_SCRIPT, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "text/html", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "foo({\"x\" : 3})", // first_chunk Verdict::kAllowAfterSniffing, // verdict }, { "Allowed: Cross-site script to JavaScript labeled as text", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_SCRIPT, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "text/plain", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_PLAIN, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "var x = 3;", // first_chunk Verdict::kAllowAfterSniffing, // verdict }, { "Allowed: Cross-site XHR to nonsense labeled as XML", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "application/xml", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_XML, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "Won't sniff as XML", // first_chunk Verdict::kAllowAfterSniffing, // verdict }, { "Allowed: Cross-site XHR to nonsense labeled as JSON", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "text/x-json", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_JSON, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "Won't sniff as JSON", // first_chunk Verdict::kAllowAfterSniffing, // verdict }, // TODO(creis): We should block the following response since there isn't // enough data to confirm it as HTML by sniffing. { "Allowed for now: Cross-site XHR to HTML with small first read", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "text/html", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "this should sniff as HTML", // first_chunk Verdict::kBlockAfterSniffing, // verdict }, { "Blocked: Cross-site XHR to XML without CORS", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "application/xml", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_XML, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "", // first_chunk Verdict::kBlockAfterSniffing, // verdict }, { "Blocked: Cross-site XHR to JSON without CORS", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "application/json", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_JSON, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "{\"x\" : 3}", // first_chunk Verdict::kBlockAfterSniffing, // verdict }, { "Blocked: Cross-site XHR to HTML labeled as text without CORS", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "text/plain", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_PLAIN, // canonical_mime_type false, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "this should sniff as HTML", // first_chunk Verdict::kBlockAfterSniffing, // verdict }, { "Blocked: Cross-site XHR to nosniff HTML without CORS", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "text/html", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type true, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "this should sniff as HTML", // first_chunk Verdict::kBlockWithoutSniffing, // verdict }, { "Blocked: Cross-site XHR to nosniff response without CORS", __LINE__, "http://www.b.com/resource.html", // target_url RESOURCE_TYPE_XHR, // resource_type "http://www.a.com/", // initiator_origin OriginHeader::kOmit, // cors_request "text/html", // response_mime_type CROSS_SITE_DOCUMENT_MIME_TYPE_HTML, // canonical_mime_type true, // include_no_sniff_header AccessControlAllowOriginHeader::kOmit, // cors_response "Wouldn't sniff as HTML", // first_chunk Verdict::kBlockWithoutSniffing, // verdict }, { "Blocked: Cross-site