diff --git a/cef_paths2.gypi b/cef_paths2.gypi index 1c5663d55..10e5a696a 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -470,6 +470,7 @@ 'tests/ceftests/browser_info_map_unittest.cc', 'tests/ceftests/command_line_unittest.cc', 'tests/ceftests/cookie_unittest.cc', + 'tests/ceftests/cors_unittest.cc', 'tests/ceftests/devtools_message_unittest.cc', 'tests/ceftests/dialog_unittest.cc', 'tests/ceftests/display_unittest.cc', diff --git a/libcef/browser/alloy/alloy_content_browser_client.cc b/libcef/browser/alloy/alloy_content_browser_client.cc index 1c0679cff..6c1664e19 100644 --- a/libcef/browser/alloy/alloy_content_browser_client.cc +++ b/libcef/browser/alloy/alloy_content_browser_client.cc @@ -1255,6 +1255,7 @@ void AlloyContentBrowserClient::RegisterNonNetworkSubresourceURLLoaderFactories( extensions::Manifest::IsComponentLocation(extension->location())) { // Components of chrome that are implemented as extensions or platform apps // are allowed to use chrome://resources/ and chrome://theme/ URLs. + // See also HasCrossOriginWhitelistEntry. allowed_webui_hosts.emplace_back(content::kChromeUIResourcesHost); allowed_webui_hosts.emplace_back(chrome::kChromeUIThemeHost); } diff --git a/libcef/browser/net_service/proxy_url_loader_factory.cc b/libcef/browser/net_service/proxy_url_loader_factory.cc index 131d3031e..bc52039a5 100644 --- a/libcef/browser/net_service/proxy_url_loader_factory.cc +++ b/libcef/browser/net_service/proxy_url_loader_factory.cc @@ -6,7 +6,9 @@ #include "libcef/browser/net_service/proxy_url_loader_factory.h" #include "libcef/browser/context.h" +#include "libcef/browser/origin_whitelist_impl.h" #include "libcef/browser/thread_util.h" +#include "libcef/common/net/scheme_registration.h" #include "libcef/common/net_service/net_service_util.h" #include "base/barrier_closure.h" @@ -19,6 +21,7 @@ #include "mojo/public/cpp/base/big_buffer.h" #include "net/http/http_status_code.h" #include "services/network/public/cpp/cors/cors.h" +#include "services/network/public/cpp/features.h" namespace net_service { @@ -27,6 +30,26 @@ namespace { // User data key for ResourceContextData. const void* const kResourceContextUserDataKey = &kResourceContextUserDataKey; +base::Optional GetHeaderString( + const net::HttpResponseHeaders* headers, + const std::string& header_name) { + std::string header_value; + if (!headers || !headers->GetNormalizedHeader(header_name, &header_value)) { + return base::nullopt; + } + return header_value; +} + +bool IsOutOfBlinkCorsEnabled() { + static int state = -1; + if (state == -1) { + state = base::FeatureList::IsEnabled(network::features::kOutOfBlinkCors) + ? 1 + : 0; + } + return !!state; +} + } // namespace // Owns all of the ProxyURLLoaderFactorys for a given BrowserContext. Since @@ -219,6 +242,8 @@ class InterceptedRequest : public network::mojom::URLLoader, bool wait_for_loader_error); void SendErrorAndCompleteImmediately(int error_code); + void SendErrorStatusAndCompleteImmediately( + const network::URLLoaderCompletionStatus& status); void SendErrorCallback(int error_code, bool safebrowsing_hit); @@ -366,6 +391,27 @@ void InterceptedRequest::Restart() { current_request_uses_header_client_ = !!factory_->url_loader_header_client_receiver_; + if (IsOutOfBlinkCorsEnabled() && request_.request_initiator && + network::cors::ShouldCheckCors(request_.url, request_.request_initiator, + request_.mode)) { + if (scheme::IsCorsEnabledScheme(request_.url.scheme())) { + // Add the Origin header for CORS-enabled scheme requests. + request_.headers.SetHeaderIfMissing( + net::HttpRequestHeaders::kOrigin, + request_.request_initiator->Serialize()); + } else if (!HasCrossOriginWhitelistEntry( + *request_.request_initiator, + url::Origin::Create(request_.url))) { + // Fail requests if a CORS check is required and the scheme is not CORS + // enabled. This matches the error condition that would be generated by + // CorsURLLoader::StartRequest in the network process. + SendErrorStatusAndCompleteImmediately( + network::URLLoaderCompletionStatus(network::CorsErrorStatus( + network::mojom::CorsError::kCorsDisabledScheme))); + return; + } + } + const GURL original_url = request_.url; factory_->request_handler_->OnBeforeRequest( @@ -881,10 +927,12 @@ void InterceptedRequest::ContinueToResponseStarted(int error_code) { override_headers_ = nullptr; redirect_url_ = GURL(); + scoped_refptr headers = + current_response_ ? current_response_->headers : nullptr; + std::string location; - const bool is_redirect = redirect_url.is_valid() || - (current_response_->headers && - current_response_->headers->IsRedirect(&location)); + const bool is_redirect = + redirect_url.is_valid() || (headers && headers->IsRedirect(&location)); if (stream_loader_ && is_redirect) { // Redirecting from OnReceiveResponse generally isn't supported by the // NetworkService, so we can only support it when using a custom loader. @@ -903,6 +951,30 @@ void InterceptedRequest::ContinueToResponseStarted(int error_code) { LOG_IF(WARNING, is_redirect) << "Redirect at this time is not supported by " "the default network loader."; + // CORS check for requests that are handled by the client. Requests handled + // by the network process will be checked there. + if (IsOutOfBlinkCorsEnabled() && stream_loader_ && !is_redirect && + request_.request_initiator && + network::cors::ShouldCheckCors(request_.url, request_.request_initiator, + request_.mode)) { + const auto error_status = network::cors::CheckAccess( + request_.url, + GetHeaderString( + headers.get(), + network::cors::header_names::kAccessControlAllowOrigin), + GetHeaderString( + headers.get(), + network::cors::header_names::kAccessControlAllowCredentials), + request_.credentials_mode, *request_.request_initiator); + if (error_status && + !HasCrossOriginWhitelistEntry(*request_.request_initiator, + url::Origin::Create(request_.url))) { + SendErrorStatusAndCompleteImmediately( + network::URLLoaderCompletionStatus(*error_status)); + return; + } + } + // Resume handling of client messages after continuing from an async // callback. if (proxied_client_binding_) @@ -992,7 +1064,13 @@ void InterceptedRequest::CallOnComplete( } void InterceptedRequest::SendErrorAndCompleteImmediately(int error_code) { - status_ = network::URLLoaderCompletionStatus(error_code); + SendErrorStatusAndCompleteImmediately( + network::URLLoaderCompletionStatus(error_code)); +} + +void InterceptedRequest::SendErrorStatusAndCompleteImmediately( + const network::URLLoaderCompletionStatus& status) { + status_ = status; SendErrorCallback(status_.error_code, false); target_client_->OnComplete(status_); OnDestroy(); diff --git a/libcef/browser/origin_whitelist_impl.cc b/libcef/browser/origin_whitelist_impl.cc index 6bd3bca6a..1a8c74923 100644 --- a/libcef/browser/origin_whitelist_impl.cc +++ b/libcef/browser/origin_whitelist_impl.cc @@ -15,8 +15,12 @@ #include "base/bind.h" #include "base/lazy_instance.h" #include "base/synchronization/lock.h" +#include "chrome/common/webui_url_constants.h" #include "content/public/browser/render_process_host.h" +#include "content/public/common/url_constants.h" +#include "extensions/common/constants.h" #include "url/gurl.h" +#include "url/origin.h" namespace { @@ -157,10 +161,11 @@ CefOriginWhitelistManager* CefOriginWhitelistManager::GetInstance() { return g_manager.Pointer(); } -bool IsMatch(const GURL& source_origin, - const GURL& target_origin, +bool IsMatch(const url::Origin& source_origin, + const url::Origin& target_origin, const Cef_CrossOriginWhiteListEntry_Params& param) { - if (source_origin.GetOrigin() != GURL(param.source_origin)) { + if (!source_origin.IsSameOriginWith( + url::Origin::Create(GURL(param.source_origin)))) { // Source origin does not match. return false; } @@ -271,7 +276,18 @@ void GetCrossOriginWhitelistEntries( entries); } -bool HasCrossOriginWhitelistEntry(const GURL& source, const GURL& target) { +bool HasCrossOriginWhitelistEntry(const url::Origin& source, + const url::Origin& target) { + // Components of chrome that are implemented as extensions or platform apps + // are allowed to use chrome://resources/ and chrome://theme/ URLs. + // See also RegisterNonNetworkSubresourceURLLoaderFactories. + if (source.scheme() == extensions::kExtensionScheme && + target.scheme() == content::kChromeUIScheme && + (target.host() == chrome::kChromeUIThemeHost || + target.host() == content::kChromeUIResourcesHost)) { + return true; + } + std::vector params; CefOriginWhitelistManager::GetInstance()->GetCrossOriginWhitelistEntries( ¶ms); diff --git a/libcef/browser/origin_whitelist_impl.h b/libcef/browser/origin_whitelist_impl.h index bca948c2d..3136cad1c 100644 --- a/libcef/browser/origin_whitelist_impl.h +++ b/libcef/browser/origin_whitelist_impl.h @@ -12,7 +12,9 @@ namespace content { class RenderProcessHost; } -class GURL; +namespace url { +class Origin; +} struct Cef_CrossOriginWhiteListEntry_Params; @@ -23,6 +25,7 @@ void GetCrossOriginWhitelistEntries( // Returns true if |source| can access |target| based on the cross-origin white // list settings. -bool HasCrossOriginWhitelistEntry(const GURL& source, const GURL& target); +bool HasCrossOriginWhitelistEntry(const url::Origin& source, + const url::Origin& target); #endif // CEF_LIBCEF_BROWSER_ORIGIN_WHITELIST_IMPL_H_ diff --git a/libcef/common/alloy/alloy_main_delegate.cc b/libcef/common/alloy/alloy_main_delegate.cc index 6b585a3ac..23e573927 100644 --- a/libcef/common/alloy/alloy_main_delegate.cc +++ b/libcef/common/alloy/alloy_main_delegate.cc @@ -244,12 +244,6 @@ bool AlloyMainDelegate::BasicStartupComplete(int* exit_code) { std::vector disable_features; - if (network::features::kOutOfBlinkCors.default_state == - base::FEATURE_ENABLED_BY_DEFAULT) { - // TODO: Add support for out-of-Blink CORS (see issue #2716) - disable_features.push_back(network::features::kOutOfBlinkCors.name); - } - #if defined(OS_WIN) if (features::kCalculateNativeWinOcclusion.default_state == base::FEATURE_ENABLED_BY_DEFAULT) { diff --git a/libcef/common/net/scheme_registration.cc b/libcef/common/net/scheme_registration.cc index cd67ae05c..b34eb9704 100644 --- a/libcef/common/net/scheme_registration.cc +++ b/libcef/common/net/scheme_registration.cc @@ -8,6 +8,7 @@ #include "libcef/common/net/scheme_info.h" #include "libcef/features/runtime.h" +#include "base/stl_util.h" #include "content/public/common/url_constants.h" #include "extensions/common/constants.h" #include "net/net_buildflags.h" @@ -86,4 +87,10 @@ bool IsStandardScheme(const std::string& scheme) { return url::IsStandard(scheme.c_str(), scheme_comp); } +// Should return the same value as SecurityOrigin::isLocal and +// SchemeRegistry::shouldTreatURLSchemeAsCorsEnabled. +bool IsCorsEnabledScheme(const std::string& scheme) { + return base::Contains(url::GetCorsEnabledSchemes(), scheme); +} + } // namespace scheme diff --git a/libcef/common/net/scheme_registration.h b/libcef/common/net/scheme_registration.h index c8064fd9e..dcbdbb671 100644 --- a/libcef/common/net/scheme_registration.h +++ b/libcef/common/net/scheme_registration.h @@ -22,6 +22,9 @@ bool IsInternalHandledScheme(const std::string& scheme); // Returns true if the specified |scheme| is a registered standard scheme. bool IsStandardScheme(const std::string& scheme); +// Returns true if the specified |scheme| is a registered CORS enabled scheme. +bool IsCorsEnabledScheme(const std::string& scheme); + } // namespace scheme #endif // CEF_LIBCEF_COMMON_NET_SCHEME_REGISTRATION_H_ diff --git a/tests/ceftests/cors_unittest.cc b/tests/ceftests/cors_unittest.cc new file mode 100644 index 000000000..bbd736bc0 --- /dev/null +++ b/tests/ceftests/cors_unittest.cc @@ -0,0 +1,983 @@ +// 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) diff --git a/tests/ceftests/scheme_handler_unittest.cc b/tests/ceftests/scheme_handler_unittest.cc index 4fd4d076c..ef899c34a 100644 --- a/tests/ceftests/scheme_handler_unittest.cc +++ b/tests/ceftests/scheme_handler_unittest.cc @@ -3,6 +3,7 @@ // can be found in the LICENSE file. #include +#include #include "include/base/cef_bind.h" #include "include/cef_callback.h" @@ -32,6 +33,7 @@ class TestResults { sub_allow_origin.clear(); exit_url.clear(); accept_language.clear(); + console_messages.clear(); delay = 0; got_request.reset(); got_read.reset(); @@ -69,6 +71,9 @@ class TestResults { // Used for testing per-browser Accept-Language. std::string accept_language; + // Used for testing received console messages. + std::vector console_messages; + // Delay for returning scheme handler results. int delay; @@ -104,7 +109,13 @@ class TestSchemeHandler : public TestHandler { // Necessary to make the method public in order to destroy the test from // ClientSchemeHandler::ProcessRequest(). - void DestroyTest() override { TestHandler::DestroyTest(); } + void DestroyTest() override { + EXPECT_TRUE(test_results_->console_messages.empty()) + << "Did not receive expected console message: " + << test_results_->console_messages.front(); + + TestHandler::DestroyTest(); + } void DestroyTestIfDone() { if (!test_results_->exit_url.empty() && !test_results_->got_exit_request) { @@ -206,6 +217,31 @@ class TestSchemeHandler : public TestHandler { DestroyTestIfDone(); } + bool OnConsoleMessage(CefRefPtr browser, + cef_log_severity_t level, + const CefString& message, + const CefString& source, + int line) override { + bool expected = false; + if (!test_results_->console_messages.empty()) { + std::vector::iterator it = + test_results_->console_messages.begin(); + for (; it != test_results_->console_messages.end(); ++it) { + const std::string& possible = *it; + const std::string& actual = message.ToString(); + if (actual.find(possible) == 0U) { + expected = true; + test_results_->console_messages.erase(it); + break; + } + } + } + + EXPECT_TRUE(expected) << "Unexpected console message: " + << message.ToString(); + return false; + } + protected: TestResults* test_results_; @@ -635,6 +671,11 @@ void SetUpXHR(const XHRTestSettings& settings) { g_TestResults.sub_allow_origin = settings.sub_allow_origin; g_TestResults.sub_redirect_url = settings.sub_redirect_url; + if (settings.synchronous) { + g_TestResults.console_messages.push_back( + "Synchronous XMLHttpRequest on the main thread is deprecated"); + } + std::string request_url; if (!settings.sub_redirect_url.empty()) request_url = settings.sub_redirect_url; @@ -677,7 +718,6 @@ void SetUpXHR(const XHRTestSettings& settings) { " }" "};" "xhr.onerror = function(e) {" - " console.log('XMLHttpRequest failed with error ' + e);" " onResult('FAILURE');" "};" "xhr.send()"; @@ -728,16 +768,12 @@ void SetUpFetch(const FetchTestSettings& settings) { " response.text().then(function(text) {" " onResult(text);" " }).catch(function(e) {" - " console.log('FetchHttpRequest failed with error ' + e);" " onResult('FAILURE'); " " });" " } else {" - " console.log('XMLHttpRequest failed with status ' + " - " response.status);" " onResult('FAILURE');" " }" "}).catch(function(e) {" - " console.log('FetchHttpRequest failed with error ' + e);" " onResult('FAILURE');" "});" << "}" @@ -1154,6 +1190,11 @@ TEST(SchemeHandlerTest, CustomNonStandardXHRSameOriginSync) { settings.sub_url = "customnonstd:xhr%20value"; SetUpXHR(settings); + g_TestResults.console_messages.push_back( + "Access to XMLHttpRequest at 'customnonstd:xhr%20value' from origin " + "'null' has been blocked by CORS policy: Cross origin requests are only " + "supported for protocol schemes:"); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1179,6 +1220,11 @@ TEST(SchemeHandlerTest, CustomNonStandardXHRSameOriginAsync) { settings.synchronous = false; SetUpXHR(settings); + g_TestResults.console_messages.push_back( + "Access to XMLHttpRequest at 'customnonstd:xhr%20value' from origin " + "'null' has been blocked by CORS policy: Cross origin requests are only " + "supported for protocol schemes:"); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1203,6 +1249,10 @@ TEST(SchemeHandlerTest, CustomStandardFetchSameOrigin) { settings.sub_url = "customstd://test/fetch.html"; SetUpFetch(settings); + g_TestResults.console_messages.push_back( + "Fetch API cannot load customstd://test/fetch.html. URL scheme " + "\"customstd\" is not supported."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1251,6 +1301,10 @@ TEST(SchemeHandlerTest, CustomNonStandardFetchSameOrigin) { settings.sub_url = "customnonstd:xhr%20value"; SetUpFetch(settings); + g_TestResults.console_messages.push_back( + "Fetch API cannot load customnonstd:xhr%20value. URL scheme must be " + "\"http\" or \"https\" for CORS request."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1290,6 +1344,10 @@ TEST(SchemeHandlerTest, CustomNonStandardXSSSameOrigin) { RegisterTestScheme("customnonstd", std::string()); SetUpXSS("customnonstd:some%20value", "customnonstd:xhr%20value"); + g_TestResults.console_messages.push_back( + "Error: Blocked a frame with origin \"null\" from accessing a " + "cross-origin frame."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1315,6 +1373,12 @@ TEST(SchemeHandlerTest, CustomStandardXHRDifferentOriginSync) { settings.sub_url = "customstd://test2/xhr.html"; SetUpXHR(settings); + g_TestResults.console_messages.push_back( + "Access to XMLHttpRequest at 'customstd://test2/xhr.html' from origin " + "'customstd://test1' has been blocked by CORS policy: No " + "'Access-Control-Allow-Origin' header is present on the requested " + "resource."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1323,7 +1387,11 @@ TEST(SchemeHandlerTest, CustomStandardXHRDifferentOriginSync) { EXPECT_TRUE(g_TestResults.got_read); EXPECT_TRUE(g_TestResults.got_output); EXPECT_TRUE(g_TestResults.got_sub_request); - EXPECT_TRUE(g_TestResults.got_sub_read); + if (!IsOutOfBlinkCorsEnabled()) { + EXPECT_TRUE(g_TestResults.got_sub_read); + } else { + EXPECT_FALSE(g_TestResults.got_sub_read); + } EXPECT_FALSE(g_TestResults.got_sub_success); ClearTestSchemes(); @@ -1341,6 +1409,12 @@ TEST(SchemeHandlerTest, CustomStandardXHRDifferentOriginAsync) { settings.synchronous = false; SetUpXHR(settings); + g_TestResults.console_messages.push_back( + "Access to XMLHttpRequest at 'customstd://test2/xhr.html' from origin " + "'customstd://test1' has been blocked by CORS policy: No " + "'Access-Control-Allow-Origin' header is present on the requested " + "resource."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1349,7 +1423,11 @@ TEST(SchemeHandlerTest, CustomStandardXHRDifferentOriginAsync) { EXPECT_TRUE(g_TestResults.got_read); EXPECT_TRUE(g_TestResults.got_output); EXPECT_TRUE(g_TestResults.got_sub_request); - EXPECT_TRUE(g_TestResults.got_sub_read); + if (!IsOutOfBlinkCorsEnabled()) { + EXPECT_TRUE(g_TestResults.got_sub_read); + } else { + EXPECT_FALSE(g_TestResults.got_sub_read); + } EXPECT_FALSE(g_TestResults.got_sub_success); ClearTestSchemes(); @@ -1366,6 +1444,13 @@ TEST(SchemeHandlerTest, CustomStandardFetchDifferentOrigin) { settings.sub_url = "customstdfetch://test2/fetch.html"; SetUpFetch(settings); + g_TestResults.console_messages.push_back( + "Access to fetch at 'customstdfetch://test2/fetch.html' from origin " + "'customstdfetch://test1' 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."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1374,7 +1459,11 @@ TEST(SchemeHandlerTest, CustomStandardFetchDifferentOrigin) { EXPECT_TRUE(g_TestResults.got_read); EXPECT_TRUE(g_TestResults.got_output); EXPECT_TRUE(g_TestResults.got_sub_request); - EXPECT_TRUE(g_TestResults.got_sub_read); + if (!IsOutOfBlinkCorsEnabled()) { + EXPECT_TRUE(g_TestResults.got_sub_read); + } else { + EXPECT_FALSE(g_TestResults.got_sub_read); + } EXPECT_FALSE(g_TestResults.got_sub_success); ClearTestSchemes(); @@ -1387,6 +1476,10 @@ TEST(SchemeHandlerTest, CustomStandardXSSDifferentOrigin) { RegisterTestScheme("customstd", "test2"); SetUpXSS("customstd://test1/run.html", "customstd://test2/iframe.html"); + g_TestResults.console_messages.push_back( + "Error: Blocked a frame with origin \"customstd://test2\" from accessing " + "a cross-origin frame."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1408,6 +1501,10 @@ TEST(SchemeHandlerTest, CustomStandardXSSDifferentProtocolHttp) { RegisterTestScheme("http", "test2"); SetUpXSS("customstd://test1/run.html", "http://test2/iframe.html"); + g_TestResults.console_messages.push_back( + "Error: Blocked a frame with origin \"http://test2\" from accessing a " + "cross-origin frame."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1430,6 +1527,10 @@ TEST(SchemeHandlerTest, CustomStandardXSSDifferentProtocolCustomNonStandard) { RegisterTestScheme("customnonstd", std::string()); SetUpXSS("customstd://test1/run.html", "customnonstd:some%20value"); + g_TestResults.console_messages.push_back( + "Error: Blocked a frame with origin \"null\" from accessing a " + "cross-origin frame."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1451,6 +1552,10 @@ TEST(SchemeHandlerTest, HttpXSSDifferentProtocolCustomStandard) { RegisterTestScheme("customstd", "test2"); SetUpXSS("http://test1/run.html", "customstd://test2/iframe.html"); + g_TestResults.console_messages.push_back( + "Error: Blocked a frame with origin \"customstd://test2\" from accessing " + "a cross-origin frame."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1472,6 +1577,10 @@ TEST(SchemeHandlerTest, HttpXSSDifferentProtocolCustomNonStandard) { RegisterTestScheme("customnonstd", std::string()); SetUpXSS("http://test1/run.html", "customnonstd:some%20value"); + g_TestResults.console_messages.push_back( + "Error: Blocked a frame with origin \"null\" from accessing a " + "cross-origin frame."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1497,6 +1606,12 @@ TEST(SchemeHandlerTest, HttpXHRDifferentOriginSync) { settings.sub_url = "http://test2/xhr.html"; SetUpXHR(settings); + g_TestResults.console_messages.push_back( + "Access to XMLHttpRequest at 'http://test2/xhr.html' from origin " + "'http://test1' has been blocked by CORS policy: No " + "'Access-Control-Allow-Origin' header is present on the requested " + "resource."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1505,7 +1620,11 @@ TEST(SchemeHandlerTest, HttpXHRDifferentOriginSync) { EXPECT_TRUE(g_TestResults.got_read); EXPECT_TRUE(g_TestResults.got_output); EXPECT_TRUE(g_TestResults.got_sub_request); - EXPECT_TRUE(g_TestResults.got_sub_read); + if (!IsOutOfBlinkCorsEnabled()) { + EXPECT_TRUE(g_TestResults.got_sub_read); + } else { + EXPECT_FALSE(g_TestResults.got_sub_read); + } EXPECT_FALSE(g_TestResults.got_sub_success); ClearTestSchemes(); @@ -1523,6 +1642,12 @@ TEST(SchemeHandlerTest, HttpXHRDifferentOriginAsync) { settings.synchronous = false; SetUpXHR(settings); + g_TestResults.console_messages.push_back( + "Access to XMLHttpRequest at 'http://test2/xhr.html' from origin " + "'http://test1' has been blocked by CORS policy: No " + "'Access-Control-Allow-Origin' header is present on the requested " + "resource."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1531,7 +1656,11 @@ TEST(SchemeHandlerTest, HttpXHRDifferentOriginAsync) { EXPECT_TRUE(g_TestResults.got_read); EXPECT_TRUE(g_TestResults.got_output); EXPECT_TRUE(g_TestResults.got_sub_request); - EXPECT_TRUE(g_TestResults.got_sub_read); + if (!IsOutOfBlinkCorsEnabled()) { + EXPECT_TRUE(g_TestResults.got_sub_read); + } else { + EXPECT_FALSE(g_TestResults.got_sub_read); + } EXPECT_FALSE(g_TestResults.got_sub_success); ClearTestSchemes(); @@ -1548,6 +1677,13 @@ TEST(SchemeHandlerTest, HttpFetchDifferentOriginAsync) { settings.sub_url = "http://test2/fetch.html"; SetUpFetch(settings); + g_TestResults.console_messages.push_back( + "Access to fetch at 'http://test2/fetch.html' from origin 'http://test1' " + "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."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -1556,7 +1692,11 @@ TEST(SchemeHandlerTest, HttpFetchDifferentOriginAsync) { EXPECT_TRUE(g_TestResults.got_read); EXPECT_TRUE(g_TestResults.got_output); EXPECT_TRUE(g_TestResults.got_sub_request); - EXPECT_TRUE(g_TestResults.got_sub_read); + if (!IsOutOfBlinkCorsEnabled()) { + EXPECT_TRUE(g_TestResults.got_sub_read); + } else { + EXPECT_FALSE(g_TestResults.got_sub_read); + } EXPECT_FALSE(g_TestResults.got_sub_success); ClearTestSchemes(); @@ -1569,6 +1709,10 @@ TEST(SchemeHandlerTest, HttpXSSDifferentOrigin) { RegisterTestScheme("http", "test2"); SetUpXSS("http://test1/run.html", "http://test2/xss.html"); + g_TestResults.console_messages.push_back( + "Error: Blocked a frame with origin \"http://test2\" from accessing a " + "cross-origin frame."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -2085,6 +2229,12 @@ TEST(SchemeHandlerTest, CustomStandardXHRDifferentOriginRedirectSync) { settings.sub_redirect_url = "customstd://test1/xhr.html"; SetUpXHR(settings); + g_TestResults.console_messages.push_back( + "Access to XMLHttpRequest at 'customstd://test2/xhr.html' (redirected " + "from 'customstd://test1/xhr.html') from origin 'customstd://test1' has " + "been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is " + "present on the requested resource."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -2094,7 +2244,11 @@ TEST(SchemeHandlerTest, CustomStandardXHRDifferentOriginRedirectSync) { EXPECT_TRUE(g_TestResults.got_output); EXPECT_TRUE(g_TestResults.got_sub_redirect); EXPECT_TRUE(g_TestResults.got_sub_request); - EXPECT_TRUE(g_TestResults.got_sub_read); + if (!IsOutOfBlinkCorsEnabled()) { + EXPECT_TRUE(g_TestResults.got_sub_read); + } else { + EXPECT_FALSE(g_TestResults.got_sub_read); + } EXPECT_FALSE(g_TestResults.got_sub_success); ClearTestSchemes(); @@ -2113,6 +2267,12 @@ TEST(SchemeHandlerTest, CustomStandardXHRDifferentOriginRedirectAsync) { settings.synchronous = false; SetUpXHR(settings); + g_TestResults.console_messages.push_back( + "Access to XMLHttpRequest at 'customstd://test2/xhr.html' (redirected " + "from 'customstd://test1/xhr.html') from origin 'customstd://test1' has " + "been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is " + "present on the requested resource."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -2122,7 +2282,11 @@ TEST(SchemeHandlerTest, CustomStandardXHRDifferentOriginRedirectAsync) { EXPECT_TRUE(g_TestResults.got_output); EXPECT_TRUE(g_TestResults.got_sub_redirect); EXPECT_TRUE(g_TestResults.got_sub_request); - EXPECT_TRUE(g_TestResults.got_sub_read); + if (!IsOutOfBlinkCorsEnabled()) { + EXPECT_TRUE(g_TestResults.got_sub_read); + } else { + EXPECT_FALSE(g_TestResults.got_sub_read); + } EXPECT_FALSE(g_TestResults.got_sub_success); ClearTestSchemes(); @@ -2140,6 +2304,14 @@ TEST(SchemeHandlerTest, CustomStandardFetchDifferentOriginRedirect) { settings.sub_redirect_url = "customstdfetch://test1/fetch.html"; SetUpFetch(settings); + g_TestResults.console_messages.push_back( + "Access to fetch at 'customstdfetch://test2/fetch.html' (redirected from " + "'customstdfetch://test1/fetch.html') from origin " + "'customstdfetch://test1' 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."); + CefRefPtr handler = new TestSchemeHandler(&g_TestResults); handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); @@ -2149,7 +2321,11 @@ TEST(SchemeHandlerTest, CustomStandardFetchDifferentOriginRedirect) { EXPECT_TRUE(g_TestResults.got_output); EXPECT_TRUE(g_TestResults.got_sub_redirect); EXPECT_TRUE(g_TestResults.got_sub_request); - EXPECT_TRUE(g_TestResults.got_sub_read); + if (!IsOutOfBlinkCorsEnabled()) { + EXPECT_TRUE(g_TestResults.got_sub_read); + } else { + EXPECT_FALSE(g_TestResults.got_sub_read); + } EXPECT_FALSE(g_TestResults.got_sub_success); ClearTestSchemes(); @@ -2429,6 +2605,7 @@ void RegisterSchemeHandlerCustomSchemes( // Add a custom standard scheme. registrar->AddCustomScheme( "customstd", CEF_SCHEME_OPTION_STANDARD | CEF_SCHEME_OPTION_CORS_ENABLED); + // Also used in cors_unittest.cc. registrar->AddCustomScheme("customstdfetch", CEF_SCHEME_OPTION_STANDARD | CEF_SCHEME_OPTION_CORS_ENABLED | diff --git a/tests/ceftests/test_handler.cc b/tests/ceftests/test_handler.cc index ff064bf72..e20b86e50 100644 --- a/tests/ceftests/test_handler.cc +++ b/tests/ceftests/test_handler.cc @@ -10,6 +10,7 @@ #include "include/cef_stream.h" #include "include/wrapper/cef_closure_task.h" #include "include/wrapper/cef_stream_resource_handler.h" +#include "tests/ceftests/test_request.h" #include "tests/shared/common/client_switches.h" #if defined(USE_AURA) @@ -261,15 +262,8 @@ CefRefPtr TestHandler::GetResourceHandler( EXPECT_IO_THREAD(); if (resource_map_.size() > 0) { - CefString url = request->GetURL(); - - // Ignore the query component, if any. - std::string urlStr = url; - size_t idx = urlStr.find('?'); - if (idx > 0) - urlStr = urlStr.substr(0, idx); - - ResourceMap::const_iterator it = resource_map_.find(urlStr); + const std::string& url = test_request::GetPathURL(request->GetURL()); + ResourceMap::const_iterator it = resource_map_.find(url); if (it != resource_map_.end()) { // Return the previously mapped resource CefRefPtr stream = CefStreamReader::CreateForData( diff --git a/tests/ceftests/test_request.cc b/tests/ceftests/test_request.cc index 8de702c82..216e6290d 100644 --- a/tests/ceftests/test_request.cc +++ b/tests/ceftests/test_request.cc @@ -4,9 +4,13 @@ #include "tests/ceftests/test_request.h" +#include + +#include "include/cef_stream.h" #include "include/cef_urlrequest.h" #include "include/wrapper/cef_closure_task.h" #include "include/wrapper/cef_helpers.h" +#include "include/wrapper/cef_stream_resource_handler.h" namespace test_request { @@ -106,4 +110,39 @@ void Send(const SendConfig& config, const RequestDoneCallback& callback) { } } +std::string GetPathURL(const std::string& url) { + const size_t index1 = url.find('?'); + const size_t index2 = url.find('#'); + size_t index = -1; + if (index1 >= 0 && index2 >= 0) { + index = std::min(index1, index2); + } else if (index1 >= 0) { + index = index1; + } else if (index2 >= 0) { + index = index2; + } + if (index >= 0) { + return url.substr(0, index); + } + return url; +} + +CefRefPtr CreateResourceHandler( + CefRefPtr response, + const std::string& response_data) { + CefRefPtr stream; + if (!response_data.empty()) { + stream = CefStreamReader::CreateForData( + static_cast(const_cast(response_data.c_str())), + response_data.length()); + } + + CefResponse::HeaderMap headerMap; + response->GetHeaderMap(headerMap); + + return new CefStreamResourceHandler( + response->GetStatus(), response->GetStatusText(), response->GetMimeType(), + headerMap, stream); +} + } // namespace test_request diff --git a/tests/ceftests/test_request.h b/tests/ceftests/test_request.h index 7aa6b7475..34ca17fd3 100644 --- a/tests/ceftests/test_request.h +++ b/tests/ceftests/test_request.h @@ -12,6 +12,7 @@ #include "include/cef_frame.h" #include "include/cef_request.h" #include "include/cef_request_context.h" +#include "include/cef_resource_handler.h" #include "include/cef_response.h" namespace test_request { @@ -64,6 +65,14 @@ struct SendConfig { // request completes. void Send(const SendConfig& config, const RequestDoneCallback& callback); +// Removes query and/or fragment components from |url|. +std::string GetPathURL(const std::string& url); + +// Creates a new resource handler that returns the specified response. +CefRefPtr CreateResourceHandler( + CefRefPtr response, + const std::string& response_data); + } // namespace test_request #endif // CEF_TESTS_CEFTESTS_TEST_REQUEST_H_ diff --git a/tests/ceftests/test_server.cc b/tests/ceftests/test_server.cc index 5075a0a24..a907d3b75 100644 --- a/tests/ceftests/test_server.cc +++ b/tests/ceftests/test_server.cc @@ -447,6 +447,27 @@ CefRefPtr AddObserverAndStart( return AddObserver(observer, base::Bind(Start, callback)); } +void SendResponse(CefRefPtr server, + int connection_id, + CefRefPtr response, + const std::string& response_data) { + const int response_code = response->GetStatus(); + const CefString& content_type = response->GetMimeType(); + int64 content_length = static_cast(response_data.size()); + + CefResponse::HeaderMap extra_headers; + response->GetHeaderMap(extra_headers); + + server->SendHttpResponse(connection_id, response_code, content_type, + content_length, extra_headers); + + if (content_length != 0) { + server->SendRawData(connection_id, response_data.data(), + response_data.size()); + server->CloseConnection(connection_id); + } +} + // ObserverHelper ObserverHelper::ObserverHelper() : weak_ptr_factory_(this) { diff --git a/tests/ceftests/test_server.h b/tests/ceftests/test_server.h index 958394de5..00d39e3f2 100644 --- a/tests/ceftests/test_server.h +++ b/tests/ceftests/test_server.h @@ -11,6 +11,7 @@ #include "include/base/cef_bind.h" #include "include/cef_registration.h" #include "include/cef_request.h" +#include "include/cef_response.h" #include "include/cef_server.h" namespace test_server { @@ -76,6 +77,12 @@ CefRefPtr AddObserverAndStart( Observer* observer, const StartDoneCallback& callback); +// Helper for sending a fully qualified response. +void SendResponse(CefRefPtr server, + int connection_id, + CefRefPtr response, + const std::string& response_data); + // Helper for managing Observer registration and callbacks. Only used on the UI // thread. class ObserverHelper : Observer { diff --git a/tests/ceftests/test_util.cc b/tests/ceftests/test_util.cc index 5058b1f60..723c03208 100644 --- a/tests/ceftests/test_util.cc +++ b/tests/ceftests/test_util.cc @@ -281,6 +281,17 @@ bool TestOldResourceAPI() { return state ? true : false; } +bool IsOutOfBlinkCorsEnabled() { + static int state = -1; + if (state == -1) { + CefRefPtr command_line = + CefCommandLine::GetGlobalCommandLine(); + const std::string& value = command_line->GetSwitchValue("disable-features"); + state = value.find("OutOfBlinkCors") == std::string::npos ? 1 : 0; + } + return state ? true : false; +} + CefRefPtr CreateTestRequestContext( TestRequestContextMode mode, const std::string& cache_path) { diff --git a/tests/ceftests/test_util.h b/tests/ceftests/test_util.h index 8bacbc4a8..d45f10e42 100644 --- a/tests/ceftests/test_util.h +++ b/tests/ceftests/test_util.h @@ -81,6 +81,9 @@ inline bool IsTestRequestContextModeCustom(TestRequestContextMode mode) { // Returns true if the old CefResourceHandler API should be tested. bool TestOldResourceAPI(); +// Returns true if OutOfBlinkCors is enabled. +bool IsOutOfBlinkCorsEnabled(); + // Return a RequestContext object matching the specified |mode|. // |cache_path| may be specified for CUSTOM modes. // Use the RC_TEST_GROUP_BASE macro to test all valid combinations.