// Copyright (c) 2020 The Chromium Embedded Framework 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 #include #include "include/base/cef_bind.h" #include "include/cef_callback.h" #include "include/cef_origin_whitelist.h" #include "include/cef_scheme.h" #include "include/wrapper/cef_closure_task.h" #include "tests/ceftests/routing_test_handler.h" #include "tests/ceftests/test_request.h" #include "tests/ceftests/test_server.h" #include "tests/ceftests/test_util.h" namespace { const char kMimeTypeHtml[] = "text/html"; const char kMimeTypeText[] = "text/plain"; const char kDefaultHtml[] = "TEST"; const char kDefaultText[] = "TEST"; const char kSuccessMsg[] = "CorsTestHandler.Success"; const char kFailureMsg[] = "CorsTestHandler.Failure"; // Source that will handle the request. enum class HandlerType { SERVER, HTTP_SCHEME, CUSTOM_STANDARD_SCHEME, CUSTOM_NONSTANDARD_SCHEME, }; std::string GetOrigin(HandlerType handler) { switch (handler) { case HandlerType::SERVER: return test_server::kServerOrigin; case HandlerType::HTTP_SCHEME: return "http://corstest.com"; case HandlerType::CUSTOM_STANDARD_SCHEME: // Standard scheme that is CORS and fetch enabled. // Registered in scheme_handler_unittest.cc. return "customstdfetch://corstest"; case HandlerType::CUSTOM_NONSTANDARD_SCHEME: // Non-sandard scheme that is not CORS or fetch enabled. // Registered in scheme_handler_unittest.cc. return "customnonstd:corstest"; } NOTREACHED(); return std::string(); } std::string GetPathURL(HandlerType handler, const std::string& path) { return GetOrigin(handler) + path; } struct Resource { // Uniquely identifies the resource. HandlerType handler = HandlerType::SERVER; std::string path; // Response information that will be returned. CefRefPtr response; std::string response_data; // Expected error code in OnLoadError. cef_errorcode_t expected_error_code = ERR_NONE; // Expected number of responses. int expected_response_ct = 1; // Expected number of OnQuery calls. int expected_success_query_ct = 0; int expected_failure_query_ct = 0; // Actual number of responses. int response_ct = 0; // Actual number of OnQuery calls. int success_query_ct = 0; int failure_query_ct = 0; Resource() {} Resource(HandlerType request_handler, const std::string& request_path, const std::string& mime_type = kMimeTypeHtml, const std::string& data = kDefaultHtml, int status = 200) { Init(request_handler, request_path, mime_type, data, status); } // Perform basic initialization. void Init(HandlerType request_handler, const std::string& request_path, const std::string& mime_type = kMimeTypeHtml, const std::string& data = kDefaultHtml, int status = 200) { handler = request_handler; path = request_path; response_data = data; response = CefResponse::Create(); response->SetMimeType(mime_type); response->SetStatus(status); } // Validate expected initial state. void Validate() const { DCHECK(!path.empty()); DCHECK(response); DCHECK(!response->GetMimeType().empty()); DCHECK_EQ(0, response_ct); DCHECK_GE(expected_response_ct, 0); } std::string GetPathURL() const { return ::GetPathURL(handler, path); } // Returns true if all expectations have been met. bool IsDone() const { return response_ct == expected_response_ct && success_query_ct == expected_success_query_ct && failure_query_ct == expected_failure_query_ct; } void AssertDone() const { EXPECT_EQ(expected_response_ct, response_ct) << GetPathURL(); EXPECT_EQ(expected_success_query_ct, success_query_ct) << GetPathURL(); EXPECT_EQ(expected_failure_query_ct, failure_query_ct) << GetPathURL(); } // Optionally override to verify request contents. virtual bool VerifyRequest(CefRefPtr request) const { return true; } }; struct TestSetup { // Available resources. typedef std::vector ResourceList; ResourceList resources; // Used for testing received console messages. std::vector console_messages; void AddResource(Resource* resource) { DCHECK(resource); resource->Validate(); resources.push_back(resource); } void AddConsoleMessage(const std::string& message) { DCHECK(!message.empty()); console_messages.push_back(message); } Resource* GetResource(const std::string& url) const { if (resources.empty()) return nullptr; const std::string& path_url = test_request::GetPathURL(url); ResourceList::const_iterator it = resources.begin(); for (; it != resources.end(); ++it) { Resource* resource = *it; if (resource->GetPathURL() == path_url) return resource; } return nullptr; } Resource* GetResource(CefRefPtr request) const { return GetResource(request->GetURL()); } // Validate expected initial state. void Validate() const { DCHECK(!resources.empty()); } std::string GetMainURL() const { return resources.front()->GetPathURL(); } // Returns true if the server will be used. bool NeedsServer() const { ResourceList::const_iterator it = resources.begin(); for (; it != resources.end(); ++it) { Resource* resource = *it; if (resource->handler == HandlerType::SERVER) return true; } return false; } // Returns true if all expectations have been met. bool IsDone() const { ResourceList::const_iterator it = resources.begin(); for (; it != resources.end(); ++it) { Resource* resource = *it; if (!resource->IsDone()) return false; } return true; } void AssertDone() const { ResourceList::const_iterator it = resources.begin(); for (; it != resources.end(); ++it) { (*it)->AssertDone(); } } }; class TestServerObserver : public test_server::ObserverHelper { public: typedef base::Callback CheckDoneCallback; TestServerObserver(TestSetup* setup, const base::Closure& ready_callback, const base::Closure& done_callback) : setup_(setup), ready_callback_(ready_callback), done_callback_(done_callback), weak_ptr_factory_(this) { DCHECK(setup); Initialize(); } ~TestServerObserver() override { done_callback_.Run(); } void OnInitialized(const std::string& server_origin) override { CEF_REQUIRE_UI_THREAD(); ready_callback_.Run(); } bool OnHttpRequest(CefRefPtr server, int connection_id, const CefString& client_address, CefRefPtr request) override { CEF_REQUIRE_UI_THREAD(); Resource* resource = setup_->GetResource(request); if (!resource) { // Not a request we handle. return false; } resource->response_ct++; EXPECT_TRUE(resource->VerifyRequest(request)) << request->GetURL().ToString(); test_server::SendResponse(server, connection_id, resource->response, resource->response_data); // Stop propagating the callback. return true; } void OnShutdown() override { CEF_REQUIRE_UI_THREAD(); delete this; } private: TestSetup* const setup_; const base::Closure ready_callback_; const base::Closure done_callback_; base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(TestServerObserver); }; class CorsTestHandler : public RoutingTestHandler { public: explicit CorsTestHandler(TestSetup* setup) : setup_(setup) { setup_->Validate(); } void RunTest() override { StartServer(base::Bind(&CorsTestHandler::TriggerCreateBrowser, this)); // Time out the test after a reasonable period of time. SetTestTimeout(); } // Necessary to make the method public in order to destroy the test from // ClientSchemeHandlerType::ProcessRequest(). void DestroyTest() override { EXPECT_TRUE(shutting_down_); setup_->AssertDone(); EXPECT_TRUE(setup_->console_messages.empty()) << "Did not receive expected console message: " << setup_->console_messages.front(); RoutingTestHandler::DestroyTest(); } void DestroyTestIfDone() { CEF_REQUIRE_UI_THREAD(); if (shutting_down_) return; if (setup_->IsDone()) { shutting_down_ = true; StopServer(); } } CefRefPtr GetResourceHandler( CefRefPtr browser, CefRefPtr frame, CefRefPtr request) override { CEF_REQUIRE_IO_THREAD(); Resource* resource = setup_->GetResource(request); if (resource && resource->handler != HandlerType::SERVER) { resource->response_ct++; EXPECT_TRUE(resource->VerifyRequest(request)) << request->GetURL().ToString(); return test_request::CreateResourceHandler(resource->response, resource->response_data); } return RoutingTestHandler::GetResourceHandler(browser, frame, request); } void OnLoadEnd(CefRefPtr browser, CefRefPtr frame, int httpStatusCode) override { const std::string& url = frame->GetURL(); Resource* resource = GetResource(url); if (!resource) return; const int expected_status = resource->response->GetStatus(); if (url == main_url_ || expected_status != 200) { // Test that the status code is correct. EXPECT_EQ(expected_status, httpStatusCode) << url; } TriggerDestroyTestIfDone(); } void OnLoadError(CefRefPtr browser, CefRefPtr frame, ErrorCode errorCode, const CefString& errorText, const CefString& failedUrl) override { Resource* resource = GetResource(failedUrl); if (!resource) return; const cef_errorcode_t expected_error = resource->response->GetError(); // Tests sometimes also fail with ERR_ABORTED. if (!(expected_error == ERR_NONE && errorCode == ERR_ABORTED)) { EXPECT_EQ(expected_error, errorCode) << failedUrl.ToString(); } TriggerDestroyTestIfDone(); } bool OnQuery(CefRefPtr browser, CefRefPtr frame, int64 query_id, const CefString& request, bool persistent, CefRefPtr callback) override { Resource* resource = GetResource(frame->GetURL()); if (!resource) return false; if (request.ToString() == kSuccessMsg || request.ToString() == kFailureMsg) { callback->Success(""); if (request.ToString() == kSuccessMsg) resource->success_query_ct++; else resource->failure_query_ct++; TriggerDestroyTestIfDone(); return true; } return false; } bool OnConsoleMessage(CefRefPtr browser, cef_log_severity_t level, const CefString& message, const CefString& source, int line) override { bool expected = false; if (!setup_->console_messages.empty()) { std::vector::iterator it = setup_->console_messages.begin(); for (; it != setup_->console_messages.end(); ++it) { const std::string& possible = *it; const std::string& actual = message.ToString(); if (actual.find(possible) == 0U) { expected = true; setup_->console_messages.erase(it); break; } } } EXPECT_TRUE(expected) << "Unexpected console message: " << message.ToString(); return false; } protected: void TriggerCreateBrowser() { main_url_ = setup_->GetMainURL(); CreateBrowser(main_url_); } void TriggerDestroyTestIfDone() { CefPostTask(TID_UI, base::Bind(&CorsTestHandler::DestroyTestIfDone, this)); } void StartServer(const base::Closure& next_step) { if (!CefCurrentlyOn(TID_UI)) { CefPostTask(TID_UI, base::Bind(&CorsTestHandler::StartServer, this, next_step)); return; } if (!setup_->NeedsServer()) { next_step.Run(); return; } // Will delete itself after the server stops. server_ = new TestServerObserver( setup_, next_step, base::Bind(&CorsTestHandler::StoppedServer, this)); } void StopServer() { CEF_REQUIRE_UI_THREAD(); if (!server_) { DCHECK(!setup_->NeedsServer()); DestroyTest(); return; } // Results in a call to StoppedServer(). server_->Shutdown(); } void StoppedServer() { CEF_REQUIRE_UI_THREAD(); server_ = nullptr; DestroyTest(); } Resource* GetResource(const std::string& url) const { Resource* resource = setup_->GetResource(url); EXPECT_TRUE(resource) << url; return resource; } TestSetup* setup_; std::string main_url_; TestServerObserver* server_ = nullptr; bool shutting_down_ = false; IMPLEMENT_REFCOUNTING(CorsTestHandler); DISALLOW_COPY_AND_ASSIGN(CorsTestHandler); }; // JS that results in a call to CorsTestHandler::OnQuery. std::string GetMsgJS(const std::string& msg) { return "window.testQuery({request:'" + msg + "'});"; } std::string GetSuccessMsgJS() { return GetMsgJS(kSuccessMsg); } std::string GetFailureMsgJS() { return GetMsgJS(kFailureMsg); } std::string GetDefaultSuccessMsgHtml() { return "TEST"; } } // namespace // Verify the test harness for server requests. TEST(CorsTest, BasicServer) { TestSetup setup; Resource resource(HandlerType::SERVER, "/CorsTest.BasicServer"); setup.AddResource(&resource); CefRefPtr handler = new CorsTestHandler(&setup); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Like above, but also send a query JS message. TEST(CorsTest, BasicServerWithQuery) { TestSetup setup; Resource resource(HandlerType::SERVER, "/CorsTest.BasicServerWithQuery", kMimeTypeHtml, GetDefaultSuccessMsgHtml()); resource.expected_success_query_ct = 1; setup.AddResource(&resource); CefRefPtr handler = new CorsTestHandler(&setup); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Verify the test harness for http scheme requests. TEST(CorsTest, BasicHttpScheme) { TestSetup setup; Resource resource(HandlerType::HTTP_SCHEME, "/CorsTest.BasicHttpScheme"); setup.AddResource(&resource); CefRefPtr handler = new CorsTestHandler(&setup); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Like above, but also send a query JS message. TEST(CorsTest, BasicHttpSchemeWithQuery) { TestSetup setup; Resource resource(HandlerType::HTTP_SCHEME, "/CorsTest.BasicHttpSchemeWithQuery", kMimeTypeHtml, GetDefaultSuccessMsgHtml()); resource.expected_success_query_ct = 1; setup.AddResource(&resource); CefRefPtr handler = new CorsTestHandler(&setup); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Verify the test harness for custom standard scheme requests. TEST(CorsTest, BasicCustomStandardScheme) { TestSetup setup; Resource resource(HandlerType::CUSTOM_STANDARD_SCHEME, "/CorsTest.BasicCustomStandardScheme"); setup.AddResource(&resource); CefRefPtr handler = new CorsTestHandler(&setup); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } // Like above, but also send a query JS message. TEST(CorsTest, BasicCustomStandardSchemeWithQuery) { TestSetup setup; Resource resource(HandlerType::CUSTOM_STANDARD_SCHEME, "/CorsTest.BasicCustomStandardSchemeWithQuery", kMimeTypeHtml, GetDefaultSuccessMsgHtml()); resource.expected_success_query_ct = 1; setup.AddResource(&resource); CefRefPtr handler = new CorsTestHandler(&setup); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); } namespace { std::string GetIframeMainHtml(const std::string& iframe_url, const std::string& sandbox_attribs) { return "TEST"; } std::string GetIframeSubHtml() { // Try to script the parent frame, then send the SuccessMsg. return "TEST"; } bool HasSandboxAttrib(const std::string& sandbox_attribs, const std::string& attrib) { return sandbox_attribs.find(attrib) != std::string::npos; } void SetupIframeRequest(TestSetup* setup, const std::string& test_name, HandlerType main_handler, Resource* main_resource, HandlerType iframe_handler, Resource* iframe_resource, const std::string& sandbox_attribs) { const std::string& base_path = "/" + test_name; // Expect a single iframe request. iframe_resource->Init(iframe_handler, base_path + ".iframe.html", kMimeTypeHtml, GetIframeSubHtml()); // Expect a single main frame request. const std::string& iframe_url = iframe_resource->GetPathURL(); main_resource->Init(main_handler, base_path, kMimeTypeHtml, GetIframeMainHtml(iframe_url, sandbox_attribs)); if (HasSandboxAttrib(sandbox_attribs, "allow-scripts")) { // Expect the iframe to load successfully and send the SuccessMsg. iframe_resource->expected_success_query_ct = 1; const bool has_same_origin = HasSandboxAttrib(sandbox_attribs, "allow-same-origin"); if (!has_same_origin || (has_same_origin && (main_handler == HandlerType::CUSTOM_NONSTANDARD_SCHEME || main_handler != iframe_handler))) { // Expect parent frame scripting to fail if: // - "allow-same-origin" is not specified; // - the main frame is a non-standard scheme (e.g. CORS disabled); // - the main frame and iframe origins don't match. // The reported origin will be "null" if "allow-same-origin" is not // specified, or if the iframe is hosted on a non-standard scheme. const std::string& origin = !has_same_origin || iframe_handler == HandlerType::CUSTOM_NONSTANDARD_SCHEME ? "null" : GetOrigin(iframe_handler); setup->AddConsoleMessage("SecurityError: Blocked a frame with origin \"" + origin + "\" from accessing a cross-origin frame."); } } else { // Expect JavaScript execution to fail. setup->AddConsoleMessage("Blocked script execution in '" + iframe_url + "' because the document's frame is sandboxed and " "the 'allow-scripts' permission is not set."); } setup->AddResource(main_resource); setup->AddResource(iframe_resource); } } // namespace // Test iframe sandbox attributes with different origin combinations. #define CORS_TEST_IFRAME(test_name, handler_main, handler_iframe, \ sandbox_attribs) \ TEST(CorsTest, Iframe##test_name) { \ TestSetup setup; \ Resource resource_main, resource_iframe; \ SetupIframeRequest(&setup, "CorsTest.Iframe" #test_name, \ HandlerType::handler_main, &resource_main, \ HandlerType::handler_iframe, &resource_iframe, \ sandbox_attribs); \ CefRefPtr handler = new CorsTestHandler(&setup); \ handler->ExecuteTest(); \ ReleaseAndWaitForDestructor(handler); \ } // Test all origin combinations (same and cross-origin). #define CORS_TEST_IFRAME_ALL(name, sandbox_attribs) \ CORS_TEST_IFRAME(name##ServerToServer, SERVER, SERVER, sandbox_attribs) \ CORS_TEST_IFRAME(name##ServerToHttpScheme, SERVER, HTTP_SCHEME, \ sandbox_attribs) \ CORS_TEST_IFRAME(name##ServerToCustomStandardScheme, SERVER, \ CUSTOM_STANDARD_SCHEME, sandbox_attribs) \ CORS_TEST_IFRAME(name##ServerToCustomNonStandardScheme, SERVER, \ CUSTOM_NONSTANDARD_SCHEME, sandbox_attribs) \ CORS_TEST_IFRAME(name##HttpSchemeToServer, HTTP_SCHEME, SERVER, \ sandbox_attribs) \ CORS_TEST_IFRAME(name##HttpSchemeToHttpScheme, HTTP_SCHEME, HTTP_SCHEME, \ sandbox_attribs) \ CORS_TEST_IFRAME(name##HttpSchemeToCustomStandardScheme, HTTP_SCHEME, \ CUSTOM_STANDARD_SCHEME, sandbox_attribs) \ CORS_TEST_IFRAME(name##HttpSchemeToCustomNonStandardScheme, HTTP_SCHEME, \ CUSTOM_NONSTANDARD_SCHEME, sandbox_attribs) \ CORS_TEST_IFRAME(name##CustomStandardSchemeToServer, CUSTOM_STANDARD_SCHEME, \ SERVER, sandbox_attribs) \ CORS_TEST_IFRAME(name##CustomStandardSchemeToHttpScheme, \ CUSTOM_STANDARD_SCHEME, HTTP_SCHEME, sandbox_attribs) \ CORS_TEST_IFRAME(name##CustomStandardSchemeToCustomStandardScheme, \ CUSTOM_STANDARD_SCHEME, CUSTOM_STANDARD_SCHEME, \ sandbox_attribs) \ CORS_TEST_IFRAME(name##CustomStandardSchemeToCustomNonStandardScheme, \ CUSTOM_STANDARD_SCHEME, CUSTOM_NONSTANDARD_SCHEME, \ sandbox_attribs) \ CORS_TEST_IFRAME(name##CustomNonStandardSchemeToServer, \ CUSTOM_NONSTANDARD_SCHEME, SERVER, sandbox_attribs) \ CORS_TEST_IFRAME(name##CustomNonStandardSchemeToHttpScheme, \ CUSTOM_NONSTANDARD_SCHEME, HTTP_SCHEME, sandbox_attribs) \ CORS_TEST_IFRAME(name##CustomNonStandardSchemeToCustomStandardScheme, \ CUSTOM_NONSTANDARD_SCHEME, CUSTOM_STANDARD_SCHEME, \ sandbox_attribs) \ CORS_TEST_IFRAME(name##CustomNonStandardSchemeToCustomNonStandardScheme, \ CUSTOM_NONSTANDARD_SCHEME, CUSTOM_NONSTANDARD_SCHEME, \ sandbox_attribs) // Everything is blocked. CORS_TEST_IFRAME_ALL(None, "") // JavaScript execution is allowed. CORS_TEST_IFRAME_ALL(AllowScripts, "allow-scripts") // JavaScript execution is allowed and scripting the parent is allowed for // same-origin only. CORS_TEST_IFRAME_ALL(AllowScriptsAndSameOrigin, "allow-scripts allow-same-origin") namespace { struct SubResource : Resource { SubResource() {} std::string main_origin; bool supports_cors = false; bool is_cross_origin = false; void InitCors(HandlerType main_handler, bool add_header) { // Origin is always "null" for non-standard schemes. main_origin = main_handler == HandlerType::CUSTOM_NONSTANDARD_SCHEME ? "null" : GetOrigin(main_handler); // True if cross-origin requests are allowed. XHR requests to non-standard // schemes are not allowed (due to the "null" origin). supports_cors = handler != HandlerType::CUSTOM_NONSTANDARD_SCHEME; if (!supports_cors) { // Don't expect the xhr request. expected_response_ct = 0; } // True if the request is considered cross-origin. Any requests between // non-standard schemes are considered cross-origin (due to the "null" // origin). is_cross_origin = main_handler != handler || (main_handler == HandlerType::CUSTOM_NONSTANDARD_SCHEME && handler == main_handler); if (is_cross_origin && add_header) { response->SetHeaderByName("Access-Control-Allow-Origin", main_origin, false); } } bool VerifyRequest(CefRefPtr request) const override { // Verify that the "Origin" header contains the expected value. const std::string& origin = request->GetHeaderByName("Origin"); if (is_cross_origin) { EXPECT_STREQ(main_origin.c_str(), origin.c_str()); return main_origin == origin; } EXPECT_TRUE(origin.empty()); return origin.empty(); } }; enum class ExecMode { XHR, FETCH, }; std::string GetXhrExecJS(const std::string& sub_url) { return "xhr = new XMLHttpRequest();\n" "xhr.open(\"GET\", \"" + sub_url + "\", true)\n;" "xhr.onload = function(e) {\n" " if (xhr.readyState === 4) {\n" " if (xhr.status === 200) {\n" " onResult(xhr.responseText);\n" " } else {\n" " console.log('XMLHttpRequest failed with status ' + " "xhr.status);\n" " onResult('FAILURE');\n" " }\n" " }\n" "};\n" "xhr.onerror = function(e) {\n" " onResult('FAILURE');\n" "};\n" "xhr.send();\n"; } std::string GetFetchExecJS(const std::string& sub_url) { return "fetch('" + sub_url + "')\n" ".then(function(response) {\n" " if (response.status === 200) {\n" " response.text().then(function(text) {\n" " onResult(text);\n" " }).catch(function(e) {\n" " onResult('FAILURE')\n; " " })\n;" " } else {\n" " onResult('FAILURE');\n" " }\n" "}).catch(function(e) {\n" " onResult('FAILURE');\n" "});\n"; } std::string GetExecMainHtml(ExecMode mode, const std::string& sub_url) { return std::string() + "\n" "\n" "" "Running execRequest..." ""; } // XHR and fetch requests behave the same, except for console message contents. void SetupExecRequest(ExecMode mode, TestSetup* setup, const std::string& test_name, HandlerType main_handler, Resource* main_resource, HandlerType sub_handler, SubResource* sub_resource, bool add_header) { const std::string& base_path = "/" + test_name; // Expect a single xhr request. sub_resource->Init(sub_handler, base_path + ".sub.txt", kMimeTypeText, kDefaultText); sub_resource->InitCors(main_handler, add_header); // Expect a single main frame request. const std::string& sub_url = sub_resource->GetPathURL(); main_resource->Init(main_handler, base_path, kMimeTypeHtml, GetExecMainHtml(mode, sub_url)); if (sub_resource->is_cross_origin && (!sub_resource->supports_cors || !add_header)) { // Expect the cross-origin XHR to be blocked. main_resource->expected_failure_query_ct = 1; if (sub_resource->supports_cors && !add_header) { // The request supports CORS, but we didn't add the header. if (mode == ExecMode::XHR) { setup->AddConsoleMessage( "Access to XMLHttpRequest at '" + sub_url + "' from origin '" + sub_resource->main_origin + "' has been blocked by CORS policy: No " "'Access-Control-Allow-Origin' " "header is present on the requested resource."); } else { setup->AddConsoleMessage( "Access to fetch at '" + sub_url + "' from origin '" + sub_resource->main_origin + "' has been blocked by CORS policy: No " "'Access-Control-Allow-Origin' header is present on the requested " "resource. If an opaque response serves your needs, set the " "request's mode to 'no-cors' to fetch the resource with CORS " "disabled."); } } else if (mode == ExecMode::XHR) { setup->AddConsoleMessage( "Access to XMLHttpRequest at '" + sub_url + "' from origin '" + sub_resource->main_origin + "' has been blocked by CORS policy: Cross origin requests are only " "supported for protocol schemes:"); } else { setup->AddConsoleMessage( "Fetch API cannot load " + sub_url + ". URL scheme must be \"http\" or \"https\" for CORS request."); } } else { // Expect the (possibly cross-origin) XHR to be allowed. main_resource->expected_success_query_ct = 1; } setup->AddResource(main_resource); setup->AddResource(sub_resource); } } // namespace // Test XHR requests with different origin combinations. #define CORS_TEST_XHR(test_name, handler_main, handler_sub, add_header) \ TEST(CorsTest, Xhr##test_name) { \ TestSetup setup; \ Resource resource_main; \ SubResource resource_sub; \ SetupExecRequest(ExecMode::XHR, &setup, "CorsTest.Xhr" #test_name, \ HandlerType::handler_main, &resource_main, \ HandlerType::handler_sub, &resource_sub, add_header); \ CefRefPtr handler = new CorsTestHandler(&setup); \ handler->ExecuteTest(); \ ReleaseAndWaitForDestructor(handler); \ } // Test all origin combinations (same and cross-origin). #define CORS_TEST_XHR_ALL(name, add_header) \ CORS_TEST_XHR(name##ServerToServer, SERVER, SERVER, add_header) \ CORS_TEST_XHR(name##ServerToHttpScheme, SERVER, HTTP_SCHEME, add_header) \ CORS_TEST_XHR(name##ServerToCustomStandardScheme, SERVER, \ CUSTOM_STANDARD_SCHEME, add_header) \ CORS_TEST_XHR(name##ServerToCustomNonStandardScheme, SERVER, \ CUSTOM_NONSTANDARD_SCHEME, add_header) \ CORS_TEST_XHR(name##HttpSchemeToServer, HTTP_SCHEME, SERVER, add_header) \ CORS_TEST_XHR(name##HttpSchemeToHttpScheme, HTTP_SCHEME, HTTP_SCHEME, \ add_header) \ CORS_TEST_XHR(name##HttpSchemeToCustomStandardScheme, HTTP_SCHEME, \ CUSTOM_STANDARD_SCHEME, add_header) \ CORS_TEST_XHR(name##HttpSchemeToCustomNonStandardScheme, HTTP_SCHEME, \ CUSTOM_NONSTANDARD_SCHEME, add_header) \ CORS_TEST_XHR(name##CustomStandardSchemeToServer, CUSTOM_STANDARD_SCHEME, \ SERVER, add_header) \ CORS_TEST_XHR(name##CustomStandardSchemeToHttpScheme, \ CUSTOM_STANDARD_SCHEME, HTTP_SCHEME, add_header) \ CORS_TEST_XHR(name##CustomStandardSchemeToCustomStandardScheme, \ CUSTOM_STANDARD_SCHEME, CUSTOM_STANDARD_SCHEME, add_header) \ CORS_TEST_XHR(name##CustomStandardSchemeToCustomNonStandardScheme, \ CUSTOM_STANDARD_SCHEME, CUSTOM_NONSTANDARD_SCHEME, add_header) \ CORS_TEST_XHR(name##CustomNonStandardSchemeToServer, \ CUSTOM_NONSTANDARD_SCHEME, SERVER, add_header) \ CORS_TEST_XHR(name##CustomNonStandardSchemeToHttpScheme, \ CUSTOM_NONSTANDARD_SCHEME, HTTP_SCHEME, add_header) \ CORS_TEST_XHR(name##CustomNonStandardSchemeToCustomStandardScheme, \ CUSTOM_NONSTANDARD_SCHEME, CUSTOM_STANDARD_SCHEME, add_header) \ CORS_TEST_XHR(name##CustomNonStandardSchemeToCustomNonStandardScheme, \ CUSTOM_NONSTANDARD_SCHEME, CUSTOM_NONSTANDARD_SCHEME, \ add_header) // XHR requests without the "Access-Control-Allow-Origin" header. CORS_TEST_XHR_ALL(NoHeader, false) // XHR requests with the "Access-Control-Allow-Origin" header. CORS_TEST_XHR_ALL(WithHeader, true) // Test fetch requests with different origin combinations. #define CORS_TEST_FETCH(test_name, handler_main, handler_sub, add_header) \ TEST(CorsTest, Fetch##test_name) { \ TestSetup setup; \ Resource resource_main; \ SubResource resource_sub; \ SetupExecRequest(ExecMode::FETCH, &setup, "CorsTest.Fetch" #test_name, \ HandlerType::handler_main, &resource_main, \ HandlerType::handler_sub, &resource_sub, add_header); \ CefRefPtr handler = new CorsTestHandler(&setup); \ handler->ExecuteTest(); \ ReleaseAndWaitForDestructor(handler); \ } // Test all origin combinations (same and cross-origin). #define CORS_TEST_FETCH_ALL(name, add_header) \ CORS_TEST_FETCH(name##ServerToServer, SERVER, SERVER, add_header) \ CORS_TEST_FETCH(name##ServerToHttpScheme, SERVER, HTTP_SCHEME, add_header) \ CORS_TEST_FETCH(name##ServerToCustomStandardScheme, SERVER, \ CUSTOM_STANDARD_SCHEME, add_header) \ CORS_TEST_FETCH(name##ServerToCustomNonStandardScheme, SERVER, \ CUSTOM_NONSTANDARD_SCHEME, add_header) \ CORS_TEST_FETCH(name##HttpSchemeToServer, HTTP_SCHEME, SERVER, add_header) \ CORS_TEST_FETCH(name##HttpSchemeToHttpScheme, HTTP_SCHEME, HTTP_SCHEME, \ add_header) \ CORS_TEST_FETCH(name##HttpSchemeToCustomStandardScheme, HTTP_SCHEME, \ CUSTOM_STANDARD_SCHEME, add_header) \ CORS_TEST_FETCH(name##HttpSchemeToCustomNonStandardScheme, HTTP_SCHEME, \ CUSTOM_NONSTANDARD_SCHEME, add_header) \ CORS_TEST_FETCH(name##CustomStandardSchemeToServer, CUSTOM_STANDARD_SCHEME, \ SERVER, add_header) \ CORS_TEST_FETCH(name##CustomStandardSchemeToHttpScheme, \ CUSTOM_STANDARD_SCHEME, HTTP_SCHEME, add_header) \ CORS_TEST_FETCH(name##CustomStandardSchemeToCustomStandardScheme, \ CUSTOM_STANDARD_SCHEME, CUSTOM_STANDARD_SCHEME, add_header) \ CORS_TEST_FETCH(name##CustomStandardSchemeToCustomNonStandardScheme, \ CUSTOM_STANDARD_SCHEME, CUSTOM_NONSTANDARD_SCHEME, \ add_header) \ CORS_TEST_FETCH(name##CustomNonStandardSchemeToServer, \ CUSTOM_NONSTANDARD_SCHEME, SERVER, add_header) \ CORS_TEST_FETCH(name##CustomNonStandardSchemeToHttpScheme, \ CUSTOM_NONSTANDARD_SCHEME, HTTP_SCHEME, add_header) \ CORS_TEST_FETCH(name##CustomNonStandardSchemeToCustomStandardScheme, \ CUSTOM_NONSTANDARD_SCHEME, CUSTOM_STANDARD_SCHEME, \ add_header) \ CORS_TEST_FETCH(name##CustomNonStandardSchemeToCustomNonStandardScheme, \ CUSTOM_NONSTANDARD_SCHEME, CUSTOM_NONSTANDARD_SCHEME, \ add_header) // Fetch requests without the "Access-Control-Allow-Origin" header. CORS_TEST_FETCH_ALL(NoHeader, false) // Fetch requests with the "Access-Control-Allow-Origin" header. CORS_TEST_FETCH_ALL(WithHeader, true)