// Copyright (c) 2011 The Chromium Embedded Framework Authors. All rights // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. #include #include #include "include/base/cef_callback.h" #include "include/cef_callback.h" #include "include/cef_origin_whitelist.h" #include "include/cef_request_context.h" #include "include/cef_request_context_handler.h" #include "include/cef_scheme.h" #include "include/test/cef_test_helpers.h" #include "include/wrapper/cef_closure_task.h" #include "tests/ceftests/test_handler.h" #include "tests/ceftests/test_suite.h" #include "tests/ceftests/test_util.h" namespace { class TestResults { public: TestResults() = default; // Used for running tests in a custom request context. CefRefPtr request_context; std::string url; std::string html; int status_code = 200; // Error code set on the response. cef_errorcode_t response_error_code = ERR_NONE; // Error code expected in OnLoadError. cef_errorcode_t expected_error_code = ERR_NONE; // Used for testing redirects std::string redirect_url; // Used for testing XHR requests std::string sub_url; std::string sub_html; int sub_status_code = 200; std::string sub_allow_origin; std::string sub_redirect_url; std::string exit_url; // Used for testing XSS requests bool needs_same_origin_policy_relaxation = false; // Used for testing Accept-Language. std::string accept_language; // Used for testing received console messages. std::vector console_messages; // Delay for returning scheme handler results. int delay = 0; TrackCallback got_request, got_read, got_output, got_sub_output, got_redirect, got_error, got_sub_error, got_sub_redirect, got_sub_request, got_sub_read, git_exit_success, got_exit_request; }; // Current scheme handler object. Used when destroying the test from // ClientSchemeHandler::ProcessRequest(). class TestSchemeHandler; TestSchemeHandler* g_current_handler = nullptr; class TestSchemeHandler : public TestHandler { public: explicit TestSchemeHandler(TestResults* tr) : test_results_(tr) { g_current_handler = this; } void RunTest() override { CreateBrowser(test_results_->url, test_results_->request_context); // Time out the test after a reasonable period of time. SetTestTimeout(); } // Necessary to make the method public in order to destroy the test from // ClientSchemeHandler::ProcessRequest(). 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) { return; } if (!test_results_->sub_url.empty() && !(test_results_->got_sub_output || test_results_->got_sub_error || test_results_->got_exit_request)) { return; } if (!(test_results_->got_output || test_results_->got_error)) { return; } DestroyTest(); } bool IsExitURL(const std::string& url) const { return !test_results_->exit_url.empty() && url.find(test_results_->exit_url) != std::string::npos; } cef_return_value_t OnBeforeResourceLoad( CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr callback) override { if (request->GetResourceType() == RT_FAVICON) { // Ignore favicon requests. return RV_CANCEL; } const std::string& newUrl = request->GetURL(); if (IsExitURL(newUrl)) { test_results_->got_exit_request.yes(); // XHR tests use an exit URL to destroy the test. if (newUrl.find("SUCCESS") != std::string::npos) { test_results_->git_exit_success.yes(); } DestroyTestIfDone(); return RV_CANCEL; } if (!test_results_->sub_redirect_url.empty() && newUrl == test_results_->sub_redirect_url) { test_results_->got_sub_redirect.yes(); // Redirect to the sub URL. request->SetURL(test_results_->sub_url); } else if (newUrl == test_results_->redirect_url) { test_results_->got_redirect.yes(); // No read should have occurred for the redirect. EXPECT_TRUE(test_results_->got_request); EXPECT_FALSE(test_results_->got_read); // Now loading the redirect URL. test_results_->url = test_results_->redirect_url; test_results_->redirect_url.clear(); } return RV_CONTINUE; } void OnLoadEnd(CefRefPtr browser, CefRefPtr frame, int httpStatusCode) override { const std::string& url = frame->GetURL(); if (url == test_results_->url) { test_results_->got_output.yes(); } else if (url == test_results_->sub_url) { test_results_->got_sub_output.yes(); } else if (IsExitURL(url)) { return; } if (url == test_results_->url || test_results_->status_code != 200) { // Test that the status code is correct. EXPECT_EQ(httpStatusCode, test_results_->status_code); } DestroyTestIfDone(); } void OnLoadError(CefRefPtr browser, CefRefPtr frame, ErrorCode errorCode, const CefString& errorText, const CefString& failedUrl) override { const std::string& url = failedUrl; if (url == test_results_->url) { test_results_->got_error.yes(); } else if (url == test_results_->sub_url) { test_results_->got_sub_error.yes(); } else if (IsExitURL(url)) { return; } // Tests sometimes also fail with ERR_ABORTED or ERR_UNKNOWN_URL_SCHEME. if (!(test_results_->expected_error_code == 0 && (errorCode == ERR_ABORTED || errorCode == ERR_UNKNOWN_URL_SCHEME))) { EXPECT_EQ(test_results_->expected_error_code, errorCode) << failedUrl.ToString(); } DestroyTestIfDone(); } bool OnConsoleMessage(CefRefPtr browser, cef_log_severity_t level, const CefString& message, const CefString& source, int line) override { bool expected = false; if (!test_results_->console_messages.empty()) { std::vector::iterator it = test_results_->console_messages.begin(); for (; it != test_results_->console_messages.end(); ++it) { const std::string& possible = *it; const std::string& actual = message.ToString(); if (actual.find(possible) == 0U) { expected = true; test_results_->console_messages.erase(it); break; } } } EXPECT_TRUE(expected) << "Unexpected console message: " << message.ToString(); return false; } protected: TestResults* const test_results_; IMPLEMENT_REFCOUNTING(TestSchemeHandler); }; class ClientSchemeHandlerOld : public CefResourceHandler { public: explicit ClientSchemeHandlerOld(TestResults* tr) : test_results_(tr) {} bool ProcessRequest(CefRefPtr request, CefRefPtr callback) override { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); bool handled = false; std::string url = request->GetURL(); is_sub_ = (!test_results_->sub_url.empty() && test_results_->sub_url == url); if (is_sub_) { test_results_->got_sub_request.yes(); if (!test_results_->sub_html.empty()) { handled = true; } } else { EXPECT_EQ(url, test_results_->url); test_results_->got_request.yes(); if (!test_results_->html.empty()) { handled = true; } } std::string accept_language; CefRequest::HeaderMap headerMap; CefRequest::HeaderMap::iterator headerIter; request->GetHeaderMap(headerMap); headerIter = headerMap.find("Accept-Language"); if (headerIter != headerMap.end()) { accept_language = headerIter->second; } EXPECT_TRUE(!accept_language.empty()); if (!test_results_->accept_language.empty()) { // Value from CefRequestContextSettings.accept_language_list. EXPECT_STREQ(test_results_->accept_language.data(), accept_language.data()); } else { // CEF_SETTINGS_ACCEPT_LANGUAGE value from // CefSettings.accept_language_list set in CefTestSuite::GetSettings() // and expanded internally by ComputeAcceptLanguageFromPref. EXPECT_STREQ("en-GB,en;q=0.9", accept_language.data()); } if (handled) { if (test_results_->delay > 0) { // Continue after the delay. CefPostDelayedTask( TID_IO, base::BindOnce(&CefCallback::Continue, callback.get()), test_results_->delay); } else { // Continue immediately. callback->Continue(); } return true; } else if (test_results_->response_error_code != ERR_NONE) { // Propagate the error code. callback->Continue(); return true; } // Response was canceled. if (g_current_handler) { g_current_handler->DestroyTest(); } return false; } void GetResponseHeaders(CefRefPtr response, int64_t& response_length, CefString& redirectUrl) override { CefResponse::HeaderMap headers; if (is_sub_) { response->SetStatus(test_results_->sub_status_code); if (!test_results_->sub_allow_origin.empty()) { // Set the Access-Control-Allow-Origin header to allow cross-domain // scripting. headers.insert(std::make_pair("Access-Control-Allow-Origin", test_results_->sub_allow_origin)); } if (!test_results_->sub_html.empty()) { response->SetMimeType("text/html"); response_length = test_results_->sub_html.size(); } } else if (!test_results_->redirect_url.empty()) { redirectUrl = test_results_->redirect_url; } else if (test_results_->response_error_code != ERR_NONE) { response->SetError(test_results_->response_error_code); } else { response->SetStatus(test_results_->status_code); if (!test_results_->html.empty()) { response->SetMimeType("text/html"); response_length = test_results_->html.size(); } } if (test_results_->needs_same_origin_policy_relaxation) { // Apply same-origin policy relaxation for document.domain. headers.insert(std::make_pair("Origin-Agent-Cluster", "?0")); } if (!headers.empty()) { response->SetHeaderMap(headers); } } void Cancel() override { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); } bool ReadResponse(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr callback) override { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); if (test_results_->delay > 0) { if (!has_delayed_) { // Continue after a delay. CefPostDelayedTask( TID_IO, base::BindOnce(&ClientSchemeHandlerOld::ContinueAfterDelay, this, callback), test_results_->delay); bytes_read = 0; return true; } has_delayed_ = false; } std::string* data; if (is_sub_) { test_results_->got_sub_read.yes(); data = &test_results_->sub_html; } else { test_results_->got_read.yes(); data = &test_results_->html; } bool has_data = false; bytes_read = 0; size_t size = data->size(); if (offset_ < size) { int transfer_size = std::min(bytes_to_read, static_cast(size - offset_)); memcpy(data_out, data->c_str() + offset_, transfer_size); offset_ += transfer_size; bytes_read = transfer_size; has_data = true; } return has_data; } private: void ContinueAfterDelay(CefRefPtr callback) { has_delayed_ = true; callback->Continue(); } TestResults* const test_results_; size_t offset_ = 0; bool is_sub_ = false; bool has_delayed_ = false; IMPLEMENT_REFCOUNTING(ClientSchemeHandlerOld); DISALLOW_COPY_AND_ASSIGN(ClientSchemeHandlerOld); }; class ClientSchemeHandler : public CefResourceHandler { public: explicit ClientSchemeHandler(TestResults* tr) : test_results_(tr) {} bool Open(CefRefPtr request, bool& handle_request, CefRefPtr callback) override { EXPECT_FALSE(CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO)); if (request->GetResourceType() == RT_FAVICON) { // Ignore favicon requests. return false; } bool handled = false; std::string url = request->GetURL(); is_sub_ = (!test_results_->sub_url.empty() && test_results_->sub_url == url); if (is_sub_) { test_results_->got_sub_request.yes(); if (!test_results_->sub_html.empty()) { handled = true; } } else { EXPECT_EQ(url, test_results_->url); test_results_->got_request.yes(); if (!test_results_->html.empty()) { handled = true; } } std::string accept_language; CefRequest::HeaderMap headerMap; CefRequest::HeaderMap::iterator headerIter; request->GetHeaderMap(headerMap); headerIter = headerMap.find("Accept-Language"); if (headerIter != headerMap.end()) { accept_language = headerIter->second; } EXPECT_TRUE(!accept_language.empty()); if (!test_results_->accept_language.empty()) { // Value from CefRequestContextSettings.accept_language_list. EXPECT_STREQ(test_results_->accept_language.data(), accept_language.data()); } else { // CEF_SETTINGS_ACCEPT_LANGUAGE value from // CefSettings.accept_language_list set in CefTestSuite::GetSettings() // and expanded internally by ComputeAcceptLanguageFromPref. if (CefIsFeatureEnabledForTests("ReduceAcceptLanguage")) { EXPECT_TRUE(accept_language == "en-GB" || accept_language == "en-GB,en;q=0.9") << accept_language; } else { EXPECT_STREQ("en-GB,en;q=0.9", accept_language.data()); } } // Continue or cancel the request immediately based on the return value. handle_request = true; if (handled) { if (test_results_->delay > 0) { // Continue after the delay. handle_request = false; CefPostDelayedTask( TID_FILE_USER_BLOCKING, base::BindOnce(&CefCallback::Continue, callback.get()), test_results_->delay); } return true; } else if (test_results_->response_error_code != ERR_NONE) { // Propagate the error code. return true; } // Response was canceled. if (g_current_handler) { g_current_handler->DestroyTest(); } return false; } bool ProcessRequest(CefRefPtr request, CefRefPtr callback) override { if (request->GetResourceType() == RT_FAVICON) { // Ignore favicon requests. return false; } EXPECT_TRUE(false); // Not reached. return false; } void GetResponseHeaders(CefRefPtr response, int64_t& response_length, CefString& redirectUrl) override { CefResponse::HeaderMap headers; if (is_sub_) { response->SetStatus(test_results_->sub_status_code); if (!test_results_->sub_allow_origin.empty()) { // Set the Access-Control-Allow-Origin header to allow cross-domain // scripting. headers.insert(std::make_pair("Access-Control-Allow-Origin", test_results_->sub_allow_origin)); } if (!test_results_->sub_html.empty()) { response->SetMimeType("text/html"); response_length = test_results_->sub_html.size(); } } else if (!test_results_->redirect_url.empty()) { redirectUrl = test_results_->redirect_url; } else if (test_results_->response_error_code != ERR_NONE) { response->SetError(test_results_->response_error_code); } else { response->SetStatus(test_results_->status_code); if (!test_results_->html.empty()) { response->SetMimeType("text/html"); response_length = test_results_->html.size(); } } if (test_results_->needs_same_origin_policy_relaxation) { // Apply same-origin policy relaxation for document.domain. headers.insert(std::make_pair("Origin-Agent-Cluster", "?0")); } if (!headers.empty()) { response->SetHeaderMap(headers); } } void Cancel() override { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); } bool Read(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr callback) override { EXPECT_FALSE(CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO)); if (test_results_->delay > 0) { if (!has_delayed_) { // Continue after a delay. CefPostDelayedTask( TID_FILE_USER_BLOCKING, base::BindOnce(&ClientSchemeHandler::ContinueAfterDelay, this, data_out, bytes_to_read, callback), test_results_->delay); bytes_read = 0; return true; } has_delayed_ = false; } return GetData(data_out, bytes_to_read, bytes_read); } bool ReadResponse(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr callback) override { EXPECT_TRUE(false); // Not reached. bytes_read = -2; return false; } private: void ContinueAfterDelay(void* data_out, int bytes_to_read, CefRefPtr callback) { EXPECT_FALSE(CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO)); has_delayed_ = true; int bytes_read = 0; GetData(data_out, bytes_to_read, bytes_read); callback->Continue(bytes_read); } bool GetData(void* data_out, int bytes_to_read, int& bytes_read) { std::string* data; if (is_sub_) { test_results_->got_sub_read.yes(); data = &test_results_->sub_html; } else { test_results_->got_read.yes(); data = &test_results_->html; } // Default to response complete. bool has_data = false; bytes_read = 0; size_t size = data->size(); if (offset_ < size) { int transfer_size = std::min(bytes_to_read, static_cast(size - offset_)); memcpy(data_out, data->c_str() + offset_, transfer_size); offset_ += transfer_size; bytes_read = transfer_size; has_data = true; } return has_data; } TestResults* const test_results_; size_t offset_ = 0; bool is_sub_ = false; bool has_delayed_ = false; IMPLEMENT_REFCOUNTING(ClientSchemeHandler); DISALLOW_COPY_AND_ASSIGN(ClientSchemeHandler); }; class ClientSchemeHandlerFactory : public CefSchemeHandlerFactory { public: explicit ClientSchemeHandlerFactory(TestResults* tr) : test_results_(tr) {} CefRefPtr Create(CefRefPtr browser, CefRefPtr frame, const CefString& scheme_name, CefRefPtr request) override { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); if (TestOldResourceAPI()) { return new ClientSchemeHandlerOld(test_results_); } return new ClientSchemeHandler(test_results_); } TestResults* const test_results_; IMPLEMENT_REFCOUNTING(ClientSchemeHandlerFactory); DISALLOW_COPY_AND_ASSIGN(ClientSchemeHandlerFactory); }; // If |domain| is empty the scheme will be registered as non-standard. void RegisterTestScheme(TestResults* test_results, const std::string& scheme, const std::string& domain) { if (test_results->request_context) { EXPECT_TRUE(test_results->request_context->RegisterSchemeHandlerFactory( scheme, domain, new ClientSchemeHandlerFactory(test_results))); } else { EXPECT_TRUE(CefRegisterSchemeHandlerFactory( scheme, domain, new ClientSchemeHandlerFactory(test_results))); } WaitForIOThread(); } void ClearTestSchemes(TestResults* test_results) { if (test_results->request_context) { EXPECT_TRUE(test_results->request_context->ClearSchemeHandlerFactories()); } else { EXPECT_TRUE(CefClearSchemeHandlerFactories()); } WaitForIOThread(); } struct XHRTestSettings { XHRTestSettings() = default; std::string url; std::string sub_url; std::string sub_allow_origin; std::string sub_redirect_url; bool synchronous = true; }; void SetUpXHR(TestResults* test_results, const XHRTestSettings& settings) { test_results->sub_url = settings.sub_url; test_results->sub_html = "SUCCESS"; test_results->sub_allow_origin = settings.sub_allow_origin; test_results->sub_redirect_url = settings.sub_redirect_url; std::string request_url; if (!settings.sub_redirect_url.empty()) { request_url = settings.sub_redirect_url; } else { request_url = settings.sub_url; } test_results->url = settings.url; std::stringstream ss; ss << "" "" "" "Running execXMLHttpRequest..." ""; test_results->html = ss.str(); test_results->exit_url = "https://tests/exit"; } struct FetchTestSettings { FetchTestSettings() = default; std::string url; std::string sub_url; std::string sub_allow_origin; std::string sub_redirect_url; }; void SetUpFetch(TestResults* test_results, const FetchTestSettings& settings) { test_results->sub_url = settings.sub_url; test_results->sub_html = "SUCCESS"; test_results->sub_allow_origin = settings.sub_allow_origin; test_results->sub_redirect_url = settings.sub_redirect_url; std::string request_url; if (!settings.sub_redirect_url.empty()) { request_url = settings.sub_redirect_url; } else { request_url = settings.sub_url; } test_results->url = settings.url; std::stringstream ss; ss << "" "" "" "Running execFetchHttpRequest..." ""; test_results->html = ss.str(); test_results->exit_url = "https://tests/exit"; } // namespace void SetUpXSS(TestResults* test_results, const std::string& url, const std::string& sub_url, const std::string& domain = std::string()) { // 1. Load |url| which contains an iframe. // 2. The iframe loads |sub_url|. // 3. |sub_url| tries to call a JS function in |url|. // 4. |url| tries to call a JS function in |sub_url|. std::stringstream ss; std::string domain_line; if (!domain.empty()) { domain_line = "document.domain = '" + domain + "';"; if (url.find("http") == 0 && sub_url.find("http") == 0) { test_results->needs_same_origin_policy_relaxation = true; } } test_results->sub_url = sub_url; ss << "" "" "" "Running execXSSRequest..." ""; test_results->sub_html = ss.str(); test_results->url = url; ss.str(""); ss << "" "" "" "