mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-01-30 11:04:52 +01:00
dca0435d2f
Split the Alloy runtime into bootstrap and style components. Support creation of Alloy style browsers and windows with the Chrome runtime. Chrome runtime (`--enable-chrome-runtime`) + Alloy style (`--use-alloy-style`) supports Views (`--use-views`), native parent (`--use-native`) and windowless rendering (`--off-screen-rendering-enabled`). Print preview is supported in all cases except with windowless rendering on all platforms and native parent on MacOS. It is disabled by default with Alloy style for legacy compatibility. Where supported it can be enabled or disabled globally using `--[enable|disable]-print-preview` or configured on a per-RequestContext basis using the `printing.print_preview_disabled` preference. It also behaves as expected when triggered via the PDF viewer print button. Chrome runtime + Alloy style behavior differs from Alloy runtime in the following significant ways: - Supports Chrome error pages by default. - DevTools popups are Chrome style only (cannot be windowless). - The Alloy extension API will not supported. Chrome runtime + Alloy style passes all expected Alloy ceftests except the following: - `DisplayTest.AutoResize` (Alloy extension API not supported) - `DownloadTest.*` (Download API not yet supported) - `ExtensionTest.*` (Alloy extension API not supported) This change also adds Chrome runtime support for CefContextMenuHandler::RunContextMenu (see #3293). This change also explicitly blocks (and doesn't retry) FrameAttached requests from PDF viewer and print preview excluded frames (see #3664). Known issues specific to Chrome runtime + Alloy style: - DevTools popup with windowless rendering doesn't load successfully. Use windowed rendering or remote debugging as a workaround. - Chrome style Window with Alloy style BrowserView (`--use-alloy-style --use-chrome-style-window`) does not show Chrome theme changes. To test: - Run `ceftests --enable-chrome-runtime --use-alloy-style [--use-chrome-style-window] [--use-views|--use-native] --gtest_filter=...` - Run `cefclient --enable-chrome-runtime --use-alloy-style [--use-chrome-style-window] [--use-views|--use-native|--off-screen-rendering-enabled]` - Run `cefsimple --enable-chrome-runtime --use-alloy-style [--use-views]`
1865 lines
78 KiB
C++
1865 lines
78 KiB
C++
// 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 <set>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include "include/base/cef_callback.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_observer.h"
|
|
#include "tests/ceftests/test_util.h"
|
|
#include "tests/shared/browser/client_app_browser.h"
|
|
|
|
namespace {
|
|
|
|
// Browser-side app delegate.
|
|
class CorsBrowserTest : public client::ClientAppBrowser::Delegate {
|
|
public:
|
|
CorsBrowserTest() = default;
|
|
|
|
void OnContextInitialized(CefRefPtr<client::ClientAppBrowser> app) override {
|
|
if (IsChromeBootstrap()) {
|
|
// Disable InsecureFormNavigationThrottle which blocks 307 redirect of
|
|
// POST requests from HTTPS to custom non-standard scheme causing the
|
|
// CorsTest.RedirectPost307HttpSchemeToCustomNonStandardScheme test to
|
|
// fail.
|
|
CefRefPtr<CefValue> value = CefValue::Create();
|
|
value->SetBool(false);
|
|
CefString error;
|
|
bool result = CefRequestContext::GetGlobalContext()->SetPreference(
|
|
"profile.mixed_forms_warnings", value, error);
|
|
CHECK(result) << error.ToString();
|
|
}
|
|
}
|
|
|
|
private:
|
|
IMPLEMENT_REFCOUNTING(CorsBrowserTest);
|
|
};
|
|
|
|
constexpr bool kUseHttpsServerScheme = false;
|
|
|
|
const char kMimeTypeHtml[] = "text/html";
|
|
const char kMimeTypeText[] = "text/plain";
|
|
|
|
const char kDefaultHtml[] = "<html><body>TEST</body></html>";
|
|
const char kDefaultText[] = "TEST";
|
|
const char kDefaultCookie[] = "testCookie=testVal";
|
|
|
|
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,
|
|
CUSTOM_UNREGISTERED_SCHEME,
|
|
};
|
|
|
|
std::string GetOrigin(HandlerType handler) {
|
|
switch (handler) {
|
|
case HandlerType::SERVER:
|
|
// TODO: Only call test_server::GetOrigin() after test server
|
|
// initialization.
|
|
if (!kUseHttpsServerScheme) {
|
|
std::stringstream ss;
|
|
ss << "http://" << test_server::kHttpServerAddress << ":"
|
|
<< test_server::kHttpServerPort;
|
|
return ss.str();
|
|
}
|
|
return test_server::GetOrigin(kUseHttpsServerScheme);
|
|
case HandlerType::HTTP_SCHEME:
|
|
// Use HTTPS because requests from HTTP to the loopback address will be
|
|
// blocked by https://chromestatus.com/feature/5436853517811712.
|
|
return "https://corstest.com";
|
|
case HandlerType::CUSTOM_STANDARD_SCHEME:
|
|
// Standard scheme that's registered as CORS and fetch enabled.
|
|
// Registered in scheme_handler_unittest.cc.
|
|
return "customstdfetch://corstest";
|
|
case HandlerType::CUSTOM_NONSTANDARD_SCHEME:
|
|
// Non-standard schemes are not CORS or fetch enabled.
|
|
// Registered in scheme_handler_unittest.cc.
|
|
return "customnonstd:corstest";
|
|
case HandlerType::CUSTOM_UNREGISTERED_SCHEME:
|
|
// A scheme that isn't registered anywhere is treated as a non-standard
|
|
// scheme.
|
|
return "customstdunregistered://corstest";
|
|
}
|
|
NOTREACHED();
|
|
return std::string();
|
|
}
|
|
|
|
std::string GetScheme(HandlerType handler) {
|
|
switch (handler) {
|
|
case HandlerType::SERVER:
|
|
return test_server::GetScheme(kUseHttpsServerScheme);
|
|
case HandlerType::HTTP_SCHEME:
|
|
return "https";
|
|
case HandlerType::CUSTOM_STANDARD_SCHEME:
|
|
return "customstdfetch";
|
|
case HandlerType::CUSTOM_NONSTANDARD_SCHEME:
|
|
return "customnonstd";
|
|
case HandlerType::CUSTOM_UNREGISTERED_SCHEME:
|
|
return "customstdunregistered";
|
|
}
|
|
NOTREACHED();
|
|
return std::string();
|
|
}
|
|
|
|
bool IsNonStandardType(HandlerType handler) {
|
|
return handler == HandlerType::CUSTOM_NONSTANDARD_SCHEME ||
|
|
handler == HandlerType::CUSTOM_UNREGISTERED_SCHEME;
|
|
}
|
|
|
|
bool IsStandardType(HandlerType handler) {
|
|
return !IsNonStandardType(handler);
|
|
}
|
|
|
|
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;
|
|
// If non-empty the method value must match.
|
|
std::string method;
|
|
|
|
// 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() = default;
|
|
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;
|
|
|
|
struct ConsoleMessage {
|
|
std::string message;
|
|
|
|
// Number of times the message was received. All registered messages are
|
|
// expected at least one time.
|
|
size_t count = 0;
|
|
};
|
|
|
|
// Used for testing received console messages.
|
|
std::vector<ConsoleMessage> console_messages;
|
|
|
|
// If true cookies will be cleared after every test run.
|
|
bool clear_cookies = false;
|
|
|
|
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, 0U});
|
|
}
|
|
|
|
Resource* GetResource(const std::string& url,
|
|
const std::string& method = std::string()) const {
|
|
if (resources.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
std::set<std::string> matching_methods;
|
|
if (method.empty()) {
|
|
// Match standard HTTP methods.
|
|
matching_methods.insert("GET");
|
|
matching_methods.insert("POST");
|
|
} else {
|
|
matching_methods.insert(method);
|
|
}
|
|
|
|
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 &&
|
|
(resource->method.empty() ||
|
|
matching_methods.find(resource->method) != matching_methods.end())) {
|
|
return resource;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Resource* GetResource(CefRefPtr<CefRequest> request) const {
|
|
return GetResource(request->GetURL(), request->GetMethod());
|
|
}
|
|
|
|
// Optional initialization after the test server is started.
|
|
virtual void Initialize() {}
|
|
|
|
// 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.
|
|
virtual 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();
|
|
}
|
|
}
|
|
|
|
// Optionally override to verify cleared cookie contents.
|
|
virtual bool VerifyClearedCookies(
|
|
const test_request::CookieVector& cookies) const {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class TestServerObserver : public test_server::ObserverHelper {
|
|
public:
|
|
TestServerObserver(TestSetup* setup,
|
|
base::OnceClosure ready_callback,
|
|
base::OnceClosure done_callback)
|
|
: setup_(setup),
|
|
ready_callback_(std::move(ready_callback)),
|
|
done_callback_(std::move(done_callback)) {
|
|
DCHECK(setup);
|
|
Initialize(kUseHttpsServerScheme);
|
|
}
|
|
|
|
~TestServerObserver() override { std::move(done_callback_).Run(); }
|
|
|
|
void OnInitialized(const std::string& server_origin) override {
|
|
CEF_REQUIRE_UI_THREAD();
|
|
std::move(ready_callback_).Run();
|
|
}
|
|
|
|
bool OnTestServerRequest(CefRefPtr<CefRequest> request,
|
|
const ResponseCallback& response_callback) 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();
|
|
response_callback.Run(resource->response, resource->response_data);
|
|
|
|
// Stop propagating the callback.
|
|
return true;
|
|
}
|
|
|
|
void OnShutdown() override {
|
|
CEF_REQUIRE_UI_THREAD();
|
|
delete this;
|
|
}
|
|
|
|
private:
|
|
TestSetup* const setup_;
|
|
base::OnceClosure ready_callback_;
|
|
base::OnceClosure done_callback_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(TestServerObserver);
|
|
};
|
|
|
|
class CorsTestHandler : public RoutingTestHandler {
|
|
public:
|
|
explicit CorsTestHandler(TestSetup* setup) : setup_(setup) {}
|
|
|
|
void RunTest() override {
|
|
StartServer(base::BindOnce(&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_);
|
|
|
|
if (setup_->NeedsServer()) {
|
|
EXPECT_TRUE(got_stopped_server_);
|
|
} else {
|
|
EXPECT_FALSE(got_stopped_server_);
|
|
}
|
|
|
|
if (setup_->clear_cookies) {
|
|
EXPECT_TRUE(got_cleared_cookies_);
|
|
} else {
|
|
EXPECT_FALSE(got_cleared_cookies_);
|
|
}
|
|
|
|
setup_->AssertDone();
|
|
for (const auto& cm : setup_->console_messages) {
|
|
EXPECT_GT(cm.count, 0U)
|
|
<< "Did not receive expected console message: " << cm.message;
|
|
}
|
|
|
|
RoutingTestHandler::DestroyTest();
|
|
}
|
|
|
|
CefRefPtr<CefResourceHandler> GetResourceHandler(
|
|
CefRefPtr<CefBrowser> browser,
|
|
CefRefPtr<CefFrame> frame,
|
|
CefRefPtr<CefRequest> request) override {
|
|
CEF_REQUIRE_IO_THREAD();
|
|
const std::string& url = request->GetURL();
|
|
const std::string& method = request->GetMethod();
|
|
if (method == "OPTIONS") {
|
|
// We should never see the CORS preflight request.
|
|
ADD_FAILURE() << "Unexpected CORS preflight for " << url;
|
|
}
|
|
|
|
Resource* resource = setup_->GetResource(request);
|
|
if (resource && resource->handler != HandlerType::SERVER) {
|
|
resource->response_ct++;
|
|
EXPECT_TRUE(resource->VerifyRequest(request)) << url;
|
|
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_t 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()) {
|
|
const std::string& actual = message.ToString();
|
|
for (auto& cm : setup_->console_messages) {
|
|
if (actual.find(cm.message) == 0U) {
|
|
expected = true;
|
|
cm.count++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EXPECT_TRUE(expected) << "Unexpected console message: "
|
|
<< message.ToString();
|
|
return false;
|
|
}
|
|
|
|
protected:
|
|
void TriggerCreateBrowser() {
|
|
setup_->Initialize();
|
|
setup_->Validate();
|
|
|
|
main_url_ = setup_->GetMainURL();
|
|
CreateBrowser(main_url_);
|
|
}
|
|
|
|
void TriggerDestroyTestIfDone() {
|
|
CefPostTask(TID_UI,
|
|
base::BindOnce(&CorsTestHandler::DestroyTestIfDone, this));
|
|
}
|
|
|
|
void DestroyTestIfDone() {
|
|
CEF_REQUIRE_UI_THREAD();
|
|
if (shutting_down_) {
|
|
return;
|
|
}
|
|
|
|
if (setup_->IsDone()) {
|
|
shutting_down_ = true;
|
|
StopServer();
|
|
}
|
|
}
|
|
|
|
void StartServer(base::OnceClosure next_step) {
|
|
if (!CefCurrentlyOn(TID_UI)) {
|
|
CefPostTask(TID_UI, base::BindOnce(&CorsTestHandler::StartServer, this,
|
|
std::move(next_step)));
|
|
return;
|
|
}
|
|
|
|
if (!setup_->NeedsServer()) {
|
|
std::move(next_step).Run();
|
|
return;
|
|
}
|
|
|
|
// Will delete itself after the server stops.
|
|
server_ = new TestServerObserver(
|
|
setup_, std::move(next_step),
|
|
base::BindOnce(&CorsTestHandler::StoppedServer, this));
|
|
}
|
|
|
|
void StopServer() {
|
|
CEF_REQUIRE_UI_THREAD();
|
|
if (!server_) {
|
|
DCHECK(!setup_->NeedsServer());
|
|
AfterStoppedServer();
|
|
return;
|
|
}
|
|
|
|
// Results in a call to StoppedServer().
|
|
server_->Shutdown();
|
|
}
|
|
|
|
void StoppedServer() {
|
|
CEF_REQUIRE_UI_THREAD();
|
|
got_stopped_server_.yes();
|
|
server_ = nullptr;
|
|
AfterStoppedServer();
|
|
}
|
|
|
|
void AfterStoppedServer() {
|
|
CEF_REQUIRE_UI_THREAD();
|
|
if (setup_->clear_cookies) {
|
|
ClearCookies();
|
|
} else {
|
|
DestroyTest();
|
|
}
|
|
}
|
|
|
|
void ClearCookies() {
|
|
CEF_REQUIRE_UI_THREAD();
|
|
DCHECK(setup_->clear_cookies);
|
|
test_request::GetAllCookies(
|
|
CefCookieManager::GetGlobalManager(nullptr), /*deleteCookies=*/true,
|
|
base::BindOnce(&CorsTestHandler::ClearedCookies, this));
|
|
}
|
|
|
|
void ClearedCookies(const test_request::CookieVector& cookies) {
|
|
CEF_REQUIRE_UI_THREAD();
|
|
got_cleared_cookies_.yes();
|
|
EXPECT_TRUE(setup_->VerifyClearedCookies(cookies));
|
|
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;
|
|
|
|
TrackCallback got_stopped_server_;
|
|
TrackCallback got_cleared_cookies_;
|
|
|
|
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 {
|
|
|
|
struct CookieTestSetup : TestSetup {
|
|
CookieTestSetup() = default;
|
|
|
|
bool expect_cookie = false;
|
|
|
|
bool VerifyClearedCookies(
|
|
const test_request::CookieVector& cookies) const override {
|
|
if (!expect_cookie) {
|
|
EXPECT_TRUE(cookies.empty());
|
|
return cookies.empty();
|
|
}
|
|
|
|
EXPECT_EQ(1U, cookies.size());
|
|
const std::string& cookie = CefString(&cookies[0].name).ToString() + "=" +
|
|
CefString(&cookies[0].value).ToString();
|
|
EXPECT_STREQ(kDefaultCookie, cookie.c_str());
|
|
return cookie == kDefaultCookie;
|
|
}
|
|
};
|
|
|
|
struct CookieResource : Resource {
|
|
CookieResource() = default;
|
|
|
|
bool expect_cookie = false;
|
|
|
|
void InitSetCookie() {
|
|
response->SetHeaderByName("Set-Cookie", kDefaultCookie,
|
|
/*overwrite=*/true);
|
|
}
|
|
|
|
bool VerifyRequest(CefRefPtr<CefRequest> request) const override {
|
|
const std::string& cookie = request->GetHeaderByName("Cookie");
|
|
const std::string& expected_cookie =
|
|
expect_cookie ? kDefaultCookie : std::string();
|
|
EXPECT_STREQ(expected_cookie.c_str(), cookie.c_str()) << GetPathURL();
|
|
return expected_cookie == cookie;
|
|
}
|
|
};
|
|
|
|
void SetupCookieExpectations(CookieTestSetup* setup,
|
|
CookieResource* main_resource,
|
|
CookieResource* sub_resource) {
|
|
// All schemes except custom non-standard support cookies.
|
|
const bool supports_cookies = IsStandardType(main_resource->handler);
|
|
|
|
// The main resource may set the cookie (if cookies are supported), but should
|
|
// not receive one.
|
|
main_resource->InitSetCookie();
|
|
main_resource->expect_cookie = false;
|
|
|
|
// A cookie will be set only for schemes that support cookies.
|
|
setup->expect_cookie = supports_cookies;
|
|
// Always clear cookies so we can verify that one wasn't set unexpectedly.
|
|
setup->clear_cookies = true;
|
|
|
|
// Expect the sub-resource to receive the cookie for same-origin requests
|
|
// only.
|
|
sub_resource->expect_cookie =
|
|
supports_cookies && main_resource->handler == sub_resource->handler;
|
|
}
|
|
|
|
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(CookieTestSetup* setup,
|
|
const std::string& test_name,
|
|
HandlerType main_handler,
|
|
CookieResource* main_resource,
|
|
HandlerType iframe_handler,
|
|
CookieResource* 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));
|
|
|
|
SetupCookieExpectations(setup, main_resource, iframe_resource);
|
|
|
|
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 &&
|
|
(IsNonStandardType(main_handler) || 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 || IsNonStandardType(iframe_handler)
|
|
? "null"
|
|
: GetOrigin(iframe_handler);
|
|
setup->AddConsoleMessage(
|
|
"SecurityError: Failed to read a named property 'document' from "
|
|
"'Window': Blocked a frame with origin \"" +
|
|
origin + "\" from accessing a cross-origin frame.");
|
|
}
|
|
|
|
if (has_same_origin && main_handler == iframe_handler &&
|
|
IsStandardType(main_handler)) {
|
|
setup->AddConsoleMessage(
|
|
"An iframe which has both allow-scripts and allow-same-origin for "
|
|
"its sandbox attribute can escape its sandboxing.");
|
|
}
|
|
} 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);
|
|
}
|
|
|
|
struct IframeTestSetup : CookieTestSetup {
|
|
IframeTestSetup(const std::string& test_name,
|
|
HandlerType main_handler,
|
|
HandlerType iframe_handler,
|
|
const std::string& sandbox_attribs)
|
|
: test_name_(test_name),
|
|
main_handler_(main_handler),
|
|
iframe_handler_(iframe_handler),
|
|
sandbox_attribs_(sandbox_attribs) {}
|
|
|
|
bool NeedsServer() const override {
|
|
return main_handler_ == HandlerType::SERVER ||
|
|
iframe_handler_ == HandlerType::SERVER;
|
|
}
|
|
|
|
void Initialize() override {
|
|
SetupIframeRequest(this, test_name_, main_handler_, &resource_main_,
|
|
iframe_handler_, &resource_iframe_, sandbox_attribs_);
|
|
}
|
|
|
|
private:
|
|
const std::string test_name_;
|
|
const HandlerType main_handler_;
|
|
const HandlerType iframe_handler_;
|
|
const std::string sandbox_attribs_;
|
|
|
|
CookieResource resource_main_;
|
|
CookieResource resource_iframe_;
|
|
};
|
|
|
|
} // 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) { \
|
|
IframeTestSetup setup("CorsTest.Iframe" #test_name, \
|
|
HandlerType::handler_main, \
|
|
HandlerType::handler_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##ServerToCustomUnregisteredScheme, SERVER, \
|
|
CUSTOM_UNREGISTERED_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##HttpSchemeToCustomUnregisteredScheme, HTTP_SCHEME, \
|
|
CUSTOM_UNREGISTERED_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##CustomStandardSchemeToCustomUnregisteredScheme, \
|
|
CUSTOM_STANDARD_SCHEME, CUSTOM_UNREGISTERED_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) \
|
|
CORS_TEST_IFRAME(name##CustomNonStandardSchemeToCustomUnregisteredScheme, \
|
|
CUSTOM_NONSTANDARD_SCHEME, CUSTOM_UNREGISTERED_SCHEME, \
|
|
sandbox_attribs) \
|
|
CORS_TEST_IFRAME(name##CustomUnregisteredSchemeToServer, \
|
|
CUSTOM_UNREGISTERED_SCHEME, SERVER, sandbox_attribs) \
|
|
CORS_TEST_IFRAME(name##CustomUnregisteredSchemeToHttpScheme, \
|
|
CUSTOM_UNREGISTERED_SCHEME, HTTP_SCHEME, sandbox_attribs) \
|
|
CORS_TEST_IFRAME(name##CustomUnregisteredSchemeToCustomStandardScheme, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_STANDARD_SCHEME, \
|
|
sandbox_attribs) \
|
|
CORS_TEST_IFRAME(name##CustomUnregisteredSchemeToCustomNonStandardScheme, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_NONSTANDARD_SCHEME, \
|
|
sandbox_attribs) \
|
|
CORS_TEST_IFRAME(name##CustomUnregisteredSchemeToCustomUnregisteredScheme, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_UNREGISTERED_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 {
|
|
|
|
const char kSubRequestMethod[] = "GET";
|
|
const char kSubUnsafeHeaderName[] = "x-unsafe-header";
|
|
const char kSubUnsafeHeaderValue[] = "not-safe";
|
|
|
|
struct SubResource : CookieResource {
|
|
SubResource() = default;
|
|
|
|
std::string main_origin;
|
|
bool supports_cors = false;
|
|
bool is_cross_origin = false;
|
|
|
|
void InitCors(HandlerType main_handler, bool add_header) {
|
|
// Must specify the method to differentiate from the preflight request.
|
|
method = kSubRequestMethod;
|
|
|
|
// Origin is always "null" for non-standard schemes.
|
|
main_origin =
|
|
IsNonStandardType(main_handler) ? "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 = IsStandardType(handler);
|
|
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 ||
|
|
(IsNonStandardType(main_handler) && 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 {
|
|
if (!CookieResource::VerifyRequest(request)) {
|
|
return false;
|
|
}
|
|
|
|
const std::string& request_method = request->GetMethod();
|
|
EXPECT_STREQ(method.c_str(), request_method.c_str()) << GetPathURL();
|
|
if (request_method != method) {
|
|
return false;
|
|
}
|
|
|
|
// Verify that the "Origin" header contains the expected value.
|
|
const std::string& origin = request->GetHeaderByName("Origin");
|
|
const std::string& expected_origin =
|
|
is_cross_origin ? main_origin : std::string();
|
|
EXPECT_STREQ(expected_origin.c_str(), origin.c_str()) << GetPathURL();
|
|
if (expected_origin != origin) {
|
|
return false;
|
|
}
|
|
|
|
// Verify that the "X-Unsafe-Header" header contains the expected value.
|
|
const std::string& unsafe_header =
|
|
request->GetHeaderByName(kSubUnsafeHeaderName);
|
|
EXPECT_STREQ(kSubUnsafeHeaderValue, unsafe_header.c_str()) << GetPathURL();
|
|
return unsafe_header == kSubUnsafeHeaderValue;
|
|
}
|
|
};
|
|
|
|
// See https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
|
|
// for details of CORS preflight behavior.
|
|
struct PreflightResource : Resource {
|
|
std::string main_origin;
|
|
|
|
void InitPreflight(HandlerType main_handler) {
|
|
// CORS preflight requests originate from PreflightController in the network
|
|
// process, so we only expect them for server requests.
|
|
EXPECT_EQ(HandlerType::SERVER, handler);
|
|
|
|
// Origin is always "null" for non-standard schemes.
|
|
main_origin =
|
|
IsNonStandardType(main_handler) ? "null" : GetOrigin(main_handler);
|
|
|
|
method = "OPTIONS";
|
|
response->SetHeaderByName("Access-Control-Allow-Methods",
|
|
"GET,HEAD,OPTIONS,POST", false);
|
|
response->SetHeaderByName("Access-Control-Allow-Headers",
|
|
kSubUnsafeHeaderName, false);
|
|
response->SetHeaderByName("Access-Control-Allow-Origin", main_origin,
|
|
false);
|
|
}
|
|
|
|
bool VerifyRequest(CefRefPtr<CefRequest> request) const override {
|
|
const std::string& request_method = request->GetMethod();
|
|
EXPECT_STREQ(method.c_str(), request_method.c_str()) << GetPathURL();
|
|
if (request_method != method) {
|
|
return false;
|
|
}
|
|
|
|
const std::string& origin = request->GetHeaderByName("Origin");
|
|
EXPECT_STREQ(main_origin.c_str(), origin.c_str()) << GetPathURL();
|
|
if (main_origin != origin) {
|
|
return false;
|
|
}
|
|
|
|
const std::string& ac_request_method =
|
|
request->GetHeaderByName("Access-Control-Request-Method");
|
|
EXPECT_STREQ(kSubRequestMethod, ac_request_method.c_str()) << GetPathURL();
|
|
if (ac_request_method != kSubRequestMethod) {
|
|
return false;
|
|
}
|
|
|
|
const std::string& ac_request_headers =
|
|
request->GetHeaderByName("Access-Control-Request-Headers");
|
|
EXPECT_STREQ(kSubUnsafeHeaderName, ac_request_headers.c_str())
|
|
<< GetPathURL();
|
|
if (ac_request_headers != kSubUnsafeHeaderName) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
enum class ExecMode {
|
|
XHR,
|
|
FETCH,
|
|
};
|
|
|
|
std::string GetXhrExecJS(const std::string& sub_url) {
|
|
// Inclusion of an unsafe header triggers CORS preflight for cross-origin
|
|
// requests to the server.
|
|
return "xhr = new XMLHttpRequest();\n"
|
|
"xhr.open(\"GET\", \"" +
|
|
sub_url +
|
|
"\", true)\n;"
|
|
"xhr.setRequestHeader('" +
|
|
kSubUnsafeHeaderName + "', '" + kSubUnsafeHeaderValue +
|
|
"');\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) {
|
|
// Inclusion of an unsafe header triggers CORS preflight for cross-origin
|
|
// requests to the server.
|
|
return std::string() +
|
|
"let h = new Headers();\n"
|
|
"h.append('" +
|
|
kSubUnsafeHeaderName + "', '" + kSubUnsafeHeaderValue +
|
|
"');\n"
|
|
"fetch('" +
|
|
sub_url +
|
|
"', {headers: h})\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.
|
|
// In addition to basic CORS header behaviors and request blocking, this test
|
|
// verifies that CORS preflight requests are sent and received when expected.
|
|
// Since preflight behavior is implemented in the network process we expect it
|
|
// to already have substantial test coverage in Chromium.
|
|
void SetupExecRequest(ExecMode mode,
|
|
CookieTestSetup* setup,
|
|
const std::string& test_name,
|
|
HandlerType main_handler,
|
|
CookieResource* main_resource,
|
|
HandlerType sub_handler,
|
|
SubResource* sub_resource,
|
|
PreflightResource* preflight_resource,
|
|
bool add_header) {
|
|
const std::string& base_path = "/" + test_name;
|
|
|
|
// Expect a single xhr request.
|
|
const std::string& sub_path = base_path + ".sub.txt";
|
|
sub_resource->Init(sub_handler, sub_path, 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));
|
|
|
|
SetupCookieExpectations(setup, main_resource, sub_resource);
|
|
|
|
// Cross-origin requests to a server sub-resource will receive a CORS
|
|
// preflight request because we add an unsafe header.
|
|
const bool expect_cors_preflight =
|
|
sub_resource->is_cross_origin && sub_handler == HandlerType::SERVER;
|
|
|
|
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
|
|
// "Access-Control-Allow-Origin" header.
|
|
if (!expect_cors_preflight || preflight_resource != nullptr) {
|
|
// This is the error message when not expecting a CORS preflight
|
|
// request, or when the preflight request is handled by the server.
|
|
// Unhandled preflight requests will output a different error message
|
|
// (see below).
|
|
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 \"" + GetScheme(sub_handler) +
|
|
"\" is not supported.");
|
|
}
|
|
} 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);
|
|
|
|
if (expect_cors_preflight) {
|
|
// Expect a CORS preflight request.
|
|
if (preflight_resource) {
|
|
// The server will handle the preflight request. The cross-origin XHR may
|
|
// still be blocked if the "Access-Control-Allow-Origin" header is missing
|
|
// (see above).
|
|
preflight_resource->Init(sub_handler, sub_path, kMimeTypeText,
|
|
std::string());
|
|
preflight_resource->InitPreflight(main_handler);
|
|
setup->AddResource(preflight_resource);
|
|
} else {
|
|
// The server will not handle the preflight request. Expect the
|
|
// cross-origin XHR to be blocked.
|
|
main_resource->expected_failure_query_ct = 1;
|
|
main_resource->expected_success_query_ct = 0;
|
|
sub_resource->expected_response_ct = 0;
|
|
|
|
if (mode == ExecMode::XHR) {
|
|
setup->AddConsoleMessage(
|
|
"Access to XMLHttpRequest at '" + sub_url + "' from origin '" +
|
|
sub_resource->main_origin +
|
|
"' has been blocked by CORS policy: Response to preflight request "
|
|
"doesn't pass access control check: 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: Response to preflight request "
|
|
"doesn't pass access control check: 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.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // 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) { \
|
|
CookieTestSetup setup; \
|
|
CookieResource resource_main; \
|
|
SubResource resource_sub; \
|
|
PreflightResource resource_preflight; \
|
|
SetupExecRequest(ExecMode::XHR, &setup, "CorsTest.Xhr" #test_name, \
|
|
HandlerType::handler_main, &resource_main, \
|
|
HandlerType::handler_sub, &resource_sub, \
|
|
&resource_preflight, 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##ServerToCustomUnregisteredScheme, SERVER, \
|
|
CUSTOM_UNREGISTERED_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##HttpSchemeToCustomUnregisteredScheme, HTTP_SCHEME, \
|
|
CUSTOM_UNREGISTERED_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##CustomStandardSchemeToCustomUnregisteredScheme, \
|
|
CUSTOM_STANDARD_SCHEME, CUSTOM_UNREGISTERED_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) \
|
|
CORS_TEST_XHR(name##CustomNonStandardSchemeToCustomUnregisteredScheme, \
|
|
CUSTOM_NONSTANDARD_SCHEME, CUSTOM_UNREGISTERED_SCHEME, \
|
|
add_header) \
|
|
CORS_TEST_XHR(name##CustomUnregisteredSchemeToServer, \
|
|
CUSTOM_UNREGISTERED_SCHEME, SERVER, add_header) \
|
|
CORS_TEST_XHR(name##CustomUnregisteredSchemeToHttpScheme, \
|
|
CUSTOM_UNREGISTERED_SCHEME, HTTP_SCHEME, add_header) \
|
|
CORS_TEST_XHR(name##CustomUnregisteredSchemeToCustomStandardScheme, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_STANDARD_SCHEME, \
|
|
add_header) \
|
|
CORS_TEST_XHR(name##CustomUnregisteredSchemeToCustomNonStandardScheme, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_NONSTANDARD_SCHEME, \
|
|
add_header) \
|
|
CORS_TEST_XHR(name##CustomUnregisteredSchemeToCustomUnregisteredScheme, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_UNREGISTERED_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)
|
|
|
|
// Like above, but without handling CORS preflight requests.
|
|
#define CORS_TEST_XHR_NO_PREFLIGHT(test_name, handler_main, handler_sub, \
|
|
add_header) \
|
|
TEST(CorsTest, Xhr##test_name) { \
|
|
CookieTestSetup setup; \
|
|
CookieResource resource_main; \
|
|
SubResource resource_sub; \
|
|
SetupExecRequest(ExecMode::XHR, &setup, "CorsTest.Xhr" #test_name, \
|
|
HandlerType::handler_main, &resource_main, \
|
|
HandlerType::handler_sub, &resource_sub, nullptr, \
|
|
add_header); \
|
|
CefRefPtr<CorsTestHandler> handler = new CorsTestHandler(&setup); \
|
|
handler->ExecuteTest(); \
|
|
ReleaseAndWaitForDestructor(handler); \
|
|
}
|
|
|
|
#define CORS_TEST_XHR_NO_PREFLIGHT_SERVER(name, add_header) \
|
|
CORS_TEST_XHR_NO_PREFLIGHT(name##ServerToServer, SERVER, SERVER, add_header) \
|
|
CORS_TEST_XHR_NO_PREFLIGHT(name##HttpSchemeToServer, HTTP_SCHEME, SERVER, \
|
|
add_header) \
|
|
CORS_TEST_XHR_NO_PREFLIGHT(name##CustomStandardSchemeToServer, \
|
|
CUSTOM_STANDARD_SCHEME, SERVER, add_header) \
|
|
CORS_TEST_XHR_NO_PREFLIGHT(name##CustomNonStandardSchemeToServer, \
|
|
CUSTOM_NONSTANDARD_SCHEME, SERVER, add_header)
|
|
|
|
// XHR requests without the "Access-Control-Allow-Origin" header.
|
|
CORS_TEST_XHR_NO_PREFLIGHT_SERVER(NoHeaderNoPreflight, false)
|
|
|
|
// XHR requests with the "Access-Control-Allow-Origin" header.
|
|
CORS_TEST_XHR_NO_PREFLIGHT_SERVER(WithHeaderNoPreflight, 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) { \
|
|
CookieTestSetup setup; \
|
|
CookieResource resource_main; \
|
|
SubResource resource_sub; \
|
|
PreflightResource resource_preflight; \
|
|
SetupExecRequest(ExecMode::FETCH, &setup, "CorsTest.Fetch" #test_name, \
|
|
HandlerType::handler_main, &resource_main, \
|
|
HandlerType::handler_sub, &resource_sub, \
|
|
&resource_preflight, 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##ServerToCustomUnregisteredScheme, SERVER, \
|
|
CUSTOM_UNREGISTERED_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##HttpSchemeToCustomUnregisteredScheme, HTTP_SCHEME, \
|
|
CUSTOM_UNREGISTERED_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##CustomStandardSchemeToCustomUnregisteredScheme, \
|
|
CUSTOM_STANDARD_SCHEME, CUSTOM_UNREGISTERED_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) \
|
|
CORS_TEST_FETCH(name##CustomNonStandardSchemeToCustomUnregisteredScheme, \
|
|
CUSTOM_NONSTANDARD_SCHEME, CUSTOM_UNREGISTERED_SCHEME, \
|
|
add_header) \
|
|
CORS_TEST_FETCH(name##CustomUnregisteredSchemeToServer, \
|
|
CUSTOM_UNREGISTERED_SCHEME, SERVER, add_header) \
|
|
CORS_TEST_FETCH(name##CustomUnregisteredSchemeToHttpScheme, \
|
|
CUSTOM_UNREGISTERED_SCHEME, HTTP_SCHEME, add_header) \
|
|
CORS_TEST_FETCH(name##CustomUnregisteredSchemeToCustomStandardScheme, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_STANDARD_SCHEME, \
|
|
add_header) \
|
|
CORS_TEST_FETCH(name##CustomUnregisteredSchemeToCustomNonStandardScheme, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_NONSTANDARD_SCHEME, \
|
|
add_header) \
|
|
CORS_TEST_FETCH(name##CustomUnregisteredSchemeToCustomUnregisteredScheme, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_UNREGISTERED_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)
|
|
|
|
// Like above, but without handling CORS preflight requests.
|
|
#define CORS_TEST_FETCH_NO_PREFLIGHT(test_name, handler_main, handler_sub, \
|
|
add_header) \
|
|
TEST(CorsTest, Fetch##test_name) { \
|
|
CookieTestSetup setup; \
|
|
CookieResource resource_main; \
|
|
SubResource resource_sub; \
|
|
SetupExecRequest(ExecMode::FETCH, &setup, "CorsTest.Fetch" #test_name, \
|
|
HandlerType::handler_main, &resource_main, \
|
|
HandlerType::handler_sub, &resource_sub, nullptr, \
|
|
add_header); \
|
|
CefRefPtr<CorsTestHandler> handler = new CorsTestHandler(&setup); \
|
|
handler->ExecuteTest(); \
|
|
ReleaseAndWaitForDestructor(handler); \
|
|
}
|
|
|
|
#define CORS_TEST_FETCH_NO_PREFLIGHT_SERVER(name, add_header) \
|
|
CORS_TEST_FETCH_NO_PREFLIGHT(name##ServerToServer, SERVER, SERVER, \
|
|
add_header) \
|
|
CORS_TEST_FETCH_NO_PREFLIGHT(name##HttpSchemeToServer, HTTP_SCHEME, SERVER, \
|
|
add_header) \
|
|
CORS_TEST_FETCH_NO_PREFLIGHT(name##CustomStandardSchemeToServer, \
|
|
CUSTOM_STANDARD_SCHEME, SERVER, add_header) \
|
|
CORS_TEST_FETCH_NO_PREFLIGHT(name##CustomNonStandardSchemeToServer, \
|
|
CUSTOM_NONSTANDARD_SCHEME, SERVER, add_header)
|
|
|
|
// Fetch requests without the "Access-Control-Allow-Origin" header.
|
|
CORS_TEST_FETCH_NO_PREFLIGHT_SERVER(NoHeaderNoPreflight, false)
|
|
|
|
// Fetch requests with the "Access-Control-Allow-Origin" header.
|
|
CORS_TEST_FETCH_NO_PREFLIGHT_SERVER(WithHeaderNoPreflight, true)
|
|
|
|
namespace {
|
|
|
|
enum class RedirectMode {
|
|
MODE_302,
|
|
MODE_307,
|
|
};
|
|
|
|
struct RedirectGetResource : CookieResource {
|
|
RedirectGetResource() = default;
|
|
|
|
bool VerifyRequest(CefRefPtr<CefRequest> request) const override {
|
|
if (!CookieResource::VerifyRequest(request)) {
|
|
return false;
|
|
}
|
|
|
|
// The "Origin" header should never be present for a redirect.
|
|
const std::string& origin = request->GetHeaderByName("Origin");
|
|
EXPECT_TRUE(origin.empty()) << GetPathURL();
|
|
return origin.empty();
|
|
}
|
|
};
|
|
|
|
void SetupRedirectResponse(RedirectMode mode,
|
|
const std::string& redirect_url,
|
|
CefRefPtr<CefResponse> response) {
|
|
if (mode == RedirectMode::MODE_302) {
|
|
response->SetStatus(302);
|
|
} else if (mode == RedirectMode::MODE_307) {
|
|
response->SetStatus(307);
|
|
} else {
|
|
NOTREACHED();
|
|
}
|
|
|
|
response->SetHeaderByName("Location", redirect_url,
|
|
/*overwrite=*/false);
|
|
}
|
|
|
|
// Test redirect requests.
|
|
void SetupRedirectGetRequest(RedirectMode mode,
|
|
CookieTestSetup* setup,
|
|
const std::string& test_name,
|
|
HandlerType main_handler,
|
|
CookieResource* main_resource,
|
|
HandlerType redirect_handler,
|
|
RedirectGetResource* redirect_resource) {
|
|
const std::string& base_path = "/" + test_name;
|
|
|
|
// Expect a single redirect request that sends SuccessMsg.
|
|
redirect_resource->Init(redirect_handler, base_path + ".redirect.html",
|
|
kMimeTypeHtml, GetDefaultSuccessMsgHtml());
|
|
redirect_resource->expected_success_query_ct = 1;
|
|
|
|
// Expect a single main request that results in a redirect.
|
|
const std::string& redirect_url = redirect_resource->GetPathURL();
|
|
main_resource->Init(main_handler, base_path, kMimeTypeHtml, std::string());
|
|
SetupRedirectResponse(mode, redirect_url, main_resource->response);
|
|
|
|
SetupCookieExpectations(setup, main_resource, redirect_resource);
|
|
|
|
setup->AddResource(main_resource);
|
|
setup->AddResource(redirect_resource);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Test redirect GET requests with different origin combinations.
|
|
#define CORS_TEST_REDIRECT_GET(test_name, mode, handler_main, \
|
|
handler_redirect) \
|
|
TEST(CorsTest, RedirectGet##test_name) { \
|
|
CookieTestSetup setup; \
|
|
CookieResource resource_main; \
|
|
RedirectGetResource resource_redirect; \
|
|
SetupRedirectGetRequest( \
|
|
RedirectMode::mode, &setup, "CorsTest.RedirectGet" #test_name, \
|
|
HandlerType::handler_main, &resource_main, \
|
|
HandlerType::handler_redirect, &resource_redirect); \
|
|
CefRefPtr<CorsTestHandler> handler = new CorsTestHandler(&setup); \
|
|
handler->ExecuteTest(); \
|
|
ReleaseAndWaitForDestructor(handler); \
|
|
}
|
|
|
|
// Test all redirect GET combinations (same and cross-origin).
|
|
#define CORS_TEST_REDIRECT_GET_ALL(name, mode) \
|
|
CORS_TEST_REDIRECT_GET(name##ServerToServer, mode, SERVER, SERVER) \
|
|
CORS_TEST_REDIRECT_GET(name##ServerToHttpScheme, mode, SERVER, HTTP_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##ServerToCustomStandardScheme, mode, SERVER, \
|
|
CUSTOM_STANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##ServerToCustomNonStandardScheme, mode, SERVER, \
|
|
CUSTOM_NONSTANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##ServerToCustomUnregisteredScheme, mode, SERVER, \
|
|
CUSTOM_UNREGISTERED_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##HttpSchemeToServer, mode, HTTP_SCHEME, SERVER) \
|
|
CORS_TEST_REDIRECT_GET(name##HttpSchemeToHttpScheme, mode, HTTP_SCHEME, \
|
|
HTTP_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##HttpSchemeToCustomStandardScheme, mode, \
|
|
HTTP_SCHEME, CUSTOM_STANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##HttpSchemeToCustomNonStandardScheme, mode, \
|
|
HTTP_SCHEME, CUSTOM_NONSTANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##HttpSchemeToCustomUnregisteredScheme, mode, \
|
|
HTTP_SCHEME, CUSTOM_UNREGISTERED_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##CustomStandardSchemeToServer, mode, \
|
|
CUSTOM_STANDARD_SCHEME, SERVER) \
|
|
CORS_TEST_REDIRECT_GET(name##CustomStandardSchemeToHttpScheme, mode, \
|
|
CUSTOM_STANDARD_SCHEME, HTTP_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##CustomStandardSchemeToCustomStandardScheme, \
|
|
mode, CUSTOM_STANDARD_SCHEME, CUSTOM_STANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##CustomStandardSchemeToCustomNonStandardScheme, \
|
|
mode, CUSTOM_STANDARD_SCHEME, \
|
|
CUSTOM_NONSTANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##CustomStandardSchemeToCustomUnregisteredScheme, \
|
|
mode, CUSTOM_STANDARD_SCHEME, \
|
|
CUSTOM_UNREGISTERED_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##CustomNonStandardSchemeToServer, mode, \
|
|
CUSTOM_NONSTANDARD_SCHEME, SERVER) \
|
|
CORS_TEST_REDIRECT_GET(name##CustomNonStandardSchemeToHttpScheme, mode, \
|
|
CUSTOM_NONSTANDARD_SCHEME, HTTP_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##CustomNonStandardSchemeToCustomStandardScheme, \
|
|
mode, CUSTOM_NONSTANDARD_SCHEME, \
|
|
CUSTOM_STANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET( \
|
|
name##CustomNonStandardSchemeToCustomNonStandardScheme, mode, \
|
|
CUSTOM_NONSTANDARD_SCHEME, CUSTOM_NONSTANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET( \
|
|
name##CustomNonStandardSchemeToCustomUnregisteredScheme, mode, \
|
|
CUSTOM_NONSTANDARD_SCHEME, CUSTOM_UNREGISTERED_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##CustomUnregisteredSchemeToServer, mode, \
|
|
CUSTOM_UNREGISTERED_SCHEME, SERVER) \
|
|
CORS_TEST_REDIRECT_GET(name##CustomUnregisteredSchemeToHttpScheme, mode, \
|
|
CUSTOM_UNREGISTERED_SCHEME, HTTP_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET(name##CustomUnregisteredSchemeToCustomStandardScheme, \
|
|
mode, CUSTOM_UNREGISTERED_SCHEME, \
|
|
CUSTOM_STANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET( \
|
|
name##CustomUnregisteredSchemeToCustomNonStandardScheme, mode, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_NONSTANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_GET( \
|
|
name##CustomUnregisteredSchemeToCustomUnregisteredScheme, mode, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_UNREGISTERED_SCHEME)
|
|
|
|
// Redirect GET requests.
|
|
CORS_TEST_REDIRECT_GET_ALL(302, MODE_302)
|
|
CORS_TEST_REDIRECT_GET_ALL(307, MODE_307)
|
|
|
|
namespace {
|
|
|
|
struct PostResource : CookieResource {
|
|
PostResource() = default;
|
|
|
|
bool expect_downgrade_to_get = false;
|
|
bool was_redirected = false;
|
|
|
|
std::string main_origin;
|
|
bool is_cross_origin;
|
|
|
|
void InitOrigin(HandlerType main_handler) {
|
|
// Origin is always "null" for non-HTTP(S) schemes.
|
|
// This should only be "null" for non-standard schemes, but Blink is likely
|
|
// using SchemeIsHTTPOrHTTPS() when submitting the form request.
|
|
main_origin = IsNonStandardType(main_handler) ||
|
|
main_handler == HandlerType::CUSTOM_STANDARD_SCHEME
|
|
? "null"
|
|
: GetOrigin(main_handler);
|
|
|
|
// 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 ||
|
|
(IsNonStandardType(main_handler) && handler == main_handler);
|
|
}
|
|
|
|
bool VerifyRequest(CefRefPtr<CefRequest> request) const override {
|
|
if (!CookieResource::VerifyRequest(request)) {
|
|
return false;
|
|
}
|
|
|
|
// The "Origin" header should be present if the request is POST, and was not
|
|
// redirected cross-origin.
|
|
std::string expected_origin;
|
|
if (!expect_downgrade_to_get) {
|
|
if (was_redirected && is_cross_origin) {
|
|
// Always "null" for cross-origin redirects.
|
|
expected_origin = "null";
|
|
} else {
|
|
expected_origin = main_origin;
|
|
}
|
|
}
|
|
|
|
const std::string& origin = request->GetHeaderByName("Origin");
|
|
EXPECT_STREQ(expected_origin.c_str(), origin.c_str()) << GetPathURL();
|
|
if (expected_origin != origin) {
|
|
return false;
|
|
}
|
|
|
|
const std::string& req_method = request->GetMethod();
|
|
const bool has_post_data = request->GetPostData() != nullptr;
|
|
if (expect_downgrade_to_get) {
|
|
EXPECT_FALSE(has_post_data) << GetPathURL();
|
|
EXPECT_STREQ("GET", req_method.c_str()) << GetPathURL();
|
|
return !has_post_data && req_method == "GET";
|
|
} else {
|
|
EXPECT_TRUE(has_post_data) << GetPathURL();
|
|
EXPECT_STREQ("POST", req_method.c_str()) << GetPathURL();
|
|
return has_post_data && req_method == "POST";
|
|
}
|
|
}
|
|
};
|
|
|
|
std::string GetPostFormHtml(const std::string& submit_url) {
|
|
return "<html><body>"
|
|
"<form id=\"f\" action=\"" +
|
|
submit_url +
|
|
"\" method=\"post\">"
|
|
"<input type=\"hidden\" name=\"n\" value=\"v\"></form>"
|
|
"<script>document.getElementById('f').submit();</script>"
|
|
"</body></html>";
|
|
}
|
|
|
|
// Test redirect requests.
|
|
void SetupRedirectPostRequest(RedirectMode mode,
|
|
CookieTestSetup* setup,
|
|
const std::string& test_name,
|
|
HandlerType main_handler,
|
|
CookieResource* main_resource,
|
|
PostResource* submit_resource,
|
|
HandlerType redirect_handler,
|
|
PostResource* redirect_resource) {
|
|
const std::string& base_path = "/" + test_name;
|
|
|
|
// Expect a single redirect request that sends SuccessMsg.
|
|
redirect_resource->Init(redirect_handler, base_path + ".redirect.html",
|
|
kMimeTypeHtml, GetDefaultSuccessMsgHtml());
|
|
redirect_resource->InitOrigin(main_handler);
|
|
redirect_resource->expected_success_query_ct = 1;
|
|
|
|
// 302 redirects will downgrade POST requests to GET.
|
|
redirect_resource->expect_downgrade_to_get = mode == RedirectMode::MODE_302;
|
|
redirect_resource->was_redirected = true;
|
|
|
|
// Expect a single submit request that redirects the response.
|
|
const std::string& redirect_url = redirect_resource->GetPathURL();
|
|
submit_resource->Init(main_handler, base_path + ".submit.html", kMimeTypeHtml,
|
|
std::string());
|
|
submit_resource->InitOrigin(main_handler);
|
|
SetupRedirectResponse(mode, redirect_url, submit_resource->response);
|
|
|
|
// Expect a single main request that submits the form.
|
|
const std::string& submit_url = submit_resource->GetPathURL();
|
|
main_resource->Init(main_handler, base_path, kMimeTypeHtml,
|
|
GetPostFormHtml(submit_url));
|
|
|
|
SetupCookieExpectations(setup, main_resource, submit_resource);
|
|
SetupCookieExpectations(setup, main_resource, redirect_resource);
|
|
|
|
setup->AddResource(main_resource);
|
|
setup->AddResource(submit_resource);
|
|
setup->AddResource(redirect_resource);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Test redirect GET requests with different origin combinations.
|
|
#define CORS_TEST_REDIRECT_POST(test_name, mode, handler_main, \
|
|
handler_redirect) \
|
|
TEST(CorsTest, RedirectPost##test_name) { \
|
|
CookieTestSetup setup; \
|
|
CookieResource resource_main; \
|
|
PostResource resource_submit, resource_redirect; \
|
|
SetupRedirectPostRequest( \
|
|
RedirectMode::mode, &setup, "CorsTest.RedirectPost" #test_name, \
|
|
HandlerType::handler_main, &resource_main, &resource_submit, \
|
|
HandlerType::handler_redirect, &resource_redirect); \
|
|
CefRefPtr<CorsTestHandler> handler = new CorsTestHandler(&setup); \
|
|
handler->ExecuteTest(); \
|
|
ReleaseAndWaitForDestructor(handler); \
|
|
}
|
|
|
|
// Test all redirect GET combinations (same and cross-origin).
|
|
#define CORS_TEST_REDIRECT_POST_ALL(name, mode) \
|
|
CORS_TEST_REDIRECT_POST(name##ServerToServer, mode, SERVER, SERVER) \
|
|
CORS_TEST_REDIRECT_POST(name##ServerToHttpScheme, mode, SERVER, HTTP_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST(name##ServerToCustomStandardScheme, mode, SERVER, \
|
|
CUSTOM_STANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST(name##ServerToCustomNonStandardScheme, mode, SERVER, \
|
|
CUSTOM_NONSTANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST(name##ServerToCustomUnregisteredScheme, mode, \
|
|
SERVER, CUSTOM_UNREGISTERED_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST(name##HttpSchemeToServer, mode, HTTP_SCHEME, SERVER) \
|
|
CORS_TEST_REDIRECT_POST(name##HttpSchemeToHttpScheme, mode, HTTP_SCHEME, \
|
|
HTTP_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST(name##HttpSchemeToCustomStandardScheme, mode, \
|
|
HTTP_SCHEME, CUSTOM_STANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST(name##HttpSchemeToCustomNonStandardScheme, mode, \
|
|
HTTP_SCHEME, CUSTOM_NONSTANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST(name##HttpSchemeToCustomUnregisteredScheme, mode, \
|
|
HTTP_SCHEME, CUSTOM_UNREGISTERED_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST(name##CustomStandardSchemeToServer, mode, \
|
|
CUSTOM_STANDARD_SCHEME, SERVER) \
|
|
CORS_TEST_REDIRECT_POST(name##CustomStandardSchemeToHttpScheme, mode, \
|
|
CUSTOM_STANDARD_SCHEME, HTTP_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST(name##CustomStandardSchemeToCustomStandardScheme, \
|
|
mode, CUSTOM_STANDARD_SCHEME, \
|
|
CUSTOM_STANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST(name##CustomStandardSchemeToCustomNonStandardScheme, \
|
|
mode, CUSTOM_STANDARD_SCHEME, \
|
|
CUSTOM_NONSTANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST( \
|
|
name##CustomStandardSchemeToCustomUnregisteredScheme, mode, \
|
|
CUSTOM_STANDARD_SCHEME, CUSTOM_UNREGISTERED_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST(name##CustomNonStandardSchemeToServer, mode, \
|
|
CUSTOM_NONSTANDARD_SCHEME, SERVER) \
|
|
CORS_TEST_REDIRECT_POST(name##CustomNonStandardSchemeToHttpScheme, mode, \
|
|
CUSTOM_NONSTANDARD_SCHEME, HTTP_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST(name##CustomNonStandardSchemeToCustomStandardScheme, \
|
|
mode, CUSTOM_NONSTANDARD_SCHEME, \
|
|
CUSTOM_STANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST( \
|
|
name##CustomNonStandardSchemeToCustomNonStandardScheme, mode, \
|
|
CUSTOM_NONSTANDARD_SCHEME, CUSTOM_NONSTANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST( \
|
|
name##CustomNonStandardSchemeToCustomUnregisteredScheme, mode, \
|
|
CUSTOM_NONSTANDARD_SCHEME, CUSTOM_UNREGISTERED_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST(name##CustomUnregisteredSchemeToServer, mode, \
|
|
CUSTOM_UNREGISTERED_SCHEME, SERVER) \
|
|
CORS_TEST_REDIRECT_POST(name##CustomUnregisteredSchemeToHttpScheme, mode, \
|
|
CUSTOM_UNREGISTERED_SCHEME, HTTP_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST( \
|
|
name##CustomUnregisteredSchemeToCustomStandardScheme, mode, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_STANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST( \
|
|
name##CustomUnregisteredSchemeToCustomNonStandardScheme, mode, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_NONSTANDARD_SCHEME) \
|
|
CORS_TEST_REDIRECT_POST( \
|
|
name##CustomUnregisteredSchemeToCustomUnregisteredScheme, mode, \
|
|
CUSTOM_UNREGISTERED_SCHEME, CUSTOM_UNREGISTERED_SCHEME)
|
|
|
|
// Redirect GET requests.
|
|
CORS_TEST_REDIRECT_POST_ALL(302, MODE_302)
|
|
CORS_TEST_REDIRECT_POST_ALL(307, MODE_307)
|
|
|
|
// Entry point for creating CORS browser test objects.
|
|
// Called from client_app_delegates.cc.
|
|
void CreateCorsBrowserTests(client::ClientAppBrowser::DelegateSet& delegates) {
|
|
delegates.insert(new CorsBrowserTest);
|
|
}
|