Add support and enable out-of-Blink CORS (fixes issue #2716)

It can still be disabled for a short time by passing
`--disable-features=OutOfBlinkCors` on the command-line.
This commit is contained in:
Marshall Greenblatt 2020-08-14 15:28:23 -04:00
parent 2e94bfafea
commit 6b1e5335bc
17 changed files with 1387 additions and 40 deletions

View File

@ -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',

View File

@ -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);
}

View File

@ -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<std::string> 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<net::HttpResponseHeaders> 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();

View File

@ -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<Cef_CrossOriginWhiteListEntry_Params> params;
CefOriginWhitelistManager::GetInstance()->GetCrossOriginWhitelistEntries(
&params);

View File

@ -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_

View File

@ -244,12 +244,6 @@ bool AlloyMainDelegate::BasicStartupComplete(int* exit_code) {
std::vector<std::string> 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) {

View File

@ -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

View File

@ -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_

View File

@ -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 <algorithm>
#include <vector>
#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[] = "<html><body>TEST</body></html>";
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<CefResponse> 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<CefRequest> request) const {
return true;
}
};
struct TestSetup {
// Available resources.
typedef std::vector<Resource*> ResourceList;
ResourceList resources;
// Used for testing received console messages.
std::vector<std::string> 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<CefRequest> 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<bool()> 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<CefServer> server,
int connection_id,
const CefString& client_address,
CefRefPtr<CefRequest> 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<TestServerObserver> 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<CefResourceHandler> GetResourceHandler(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> 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<CefBrowser> browser,
CefRefPtr<CefFrame> 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<CefBrowser> browser,
CefRefPtr<CefFrame> 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<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64 query_id,
const CefString& request,
bool persistent,
CefRefPtr<Callback> 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<CefBrowser> 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<std::string>::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 "<html><body>TEST<script>" + GetSuccessMsgJS() +
"</script></body></html>";
}
} // namespace
// Verify the test harness for server requests.
TEST(CorsTest, BasicServer) {
TestSetup setup;
Resource resource(HandlerType::SERVER, "/CorsTest.BasicServer");
setup.AddResource(&resource);
CefRefPtr<CorsTestHandler> 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<CorsTestHandler> 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<CorsTestHandler> 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<CorsTestHandler> 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<CorsTestHandler> 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<CorsTestHandler> handler = new CorsTestHandler(&setup);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
namespace {
std::string GetIframeMainHtml(const std::string& iframe_url,
const std::string& sandbox_attribs) {
return "<html><body>TEST<iframe src=\"" + iframe_url + "\" sandbox=\"" +
sandbox_attribs + "\"></iframe></body></html>";
}
std::string GetIframeSubHtml() {
// Try to script the parent frame, then send the SuccessMsg.
return "<html><body>TEST<script>try { parent.document.body; } catch "
"(exception) { console.log(exception.toString()); }" +
GetSuccessMsgJS() + "</script></body></html>";
}
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<CorsTestHandler> 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<CefRequest> 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() +
"<html><head>\n"
"<script language=\"JavaScript\">\n" +
"function onResult(val) {\n"
" if (val === '" +
kDefaultText + "') {" + GetSuccessMsgJS() + "} else {" +
GetFailureMsgJS() +
"}\n}\n"
"function execRequest() {\n" +
(mode == ExecMode::XHR ? GetXhrExecJS(sub_url)
: GetFetchExecJS(sub_url)) +
"}\n</script>\n"
"</head><body onload=\"execRequest();\">"
"Running execRequest..."
"</body></html>";
}
// 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<CorsTestHandler> 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<CorsTestHandler> 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)

View File

@ -3,6 +3,7 @@
// can be found in the LICENSE file.
#include <algorithm>
#include <vector>
#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<std::string> 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<CefBrowser> 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<std::string>::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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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<TestSchemeHandler> 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 |

View File

@ -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<CefResourceHandler> 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<CefStreamReader> stream = CefStreamReader::CreateForData(

View File

@ -4,9 +4,13 @@
#include "tests/ceftests/test_request.h"
#include <algorithm>
#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<CefResourceHandler> CreateResourceHandler(
CefRefPtr<CefResponse> response,
const std::string& response_data) {
CefRefPtr<CefStreamReader> stream;
if (!response_data.empty()) {
stream = CefStreamReader::CreateForData(
static_cast<void*>(const_cast<char*>(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

View File

@ -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<CefResourceHandler> CreateResourceHandler(
CefRefPtr<CefResponse> response,
const std::string& response_data);
} // namespace test_request
#endif // CEF_TESTS_CEFTESTS_TEST_REQUEST_H_

View File

@ -447,6 +447,27 @@ CefRefPtr<CefRegistration> AddObserverAndStart(
return AddObserver(observer, base::Bind(Start, callback));
}
void SendResponse(CefRefPtr<CefServer> server,
int connection_id,
CefRefPtr<CefResponse> response,
const std::string& response_data) {
const int response_code = response->GetStatus();
const CefString& content_type = response->GetMimeType();
int64 content_length = static_cast<int64>(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) {

View File

@ -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<CefRegistration> AddObserverAndStart(
Observer* observer,
const StartDoneCallback& callback);
// Helper for sending a fully qualified response.
void SendResponse(CefRefPtr<CefServer> server,
int connection_id,
CefRefPtr<CefResponse> response,
const std::string& response_data);
// Helper for managing Observer registration and callbacks. Only used on the UI
// thread.
class ObserverHelper : Observer {

View File

@ -281,6 +281,17 @@ bool TestOldResourceAPI() {
return state ? true : false;
}
bool IsOutOfBlinkCorsEnabled() {
static int state = -1;
if (state == -1) {
CefRefPtr<CefCommandLine> 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<CefRequestContext> CreateTestRequestContext(
TestRequestContextMode mode,
const std::string& cache_path) {

View File

@ -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.