// 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/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/test_handler.h" #include "tests/ceftests/test_suite.h" #include "tests/ceftests/test_util.h" namespace { class TestResults { public: TestResults() : status_code(200), sub_status_code(200), delay(0) {} void reset() { url.clear(); html.clear(); status_code = 200; response_error_code = ERR_NONE; expected_error_code = ERR_NONE; redirect_url.clear(); sub_url.clear(); sub_html.clear(); sub_status_code = 200; sub_allow_origin.clear(); exit_url.clear(); accept_language.clear(); delay = 0; got_request.reset(); got_read.reset(); got_output.reset(); got_sub_output.reset(); got_redirect.reset(); got_error.reset(); got_sub_error.reset(); got_sub_request.reset(); got_sub_read.reset(); got_sub_success.reset(); got_exit_request.reset(); } std::string url; std::string html; int status_code; // Error code set on the response. cef_errorcode_t response_error_code; // Error code expected in OnLoadError. cef_errorcode_t expected_error_code; // 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; std::string sub_allow_origin; std::string sub_redirect_url; std::string exit_url; // Used for testing per-browser Accept-Language. std::string accept_language; // Delay for returning scheme handler results. int delay; 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, got_sub_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 PopulateBrowserSettings(CefBrowserSettings* settings) override { if (!test_results_->accept_language.empty()) { CefString(&settings->accept_language_list) = test_results_->accept_language; } } void RunTest() override { CreateBrowser(test_results_->url); // 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 { 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 { 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_->got_sub_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. if (!(test_results_->expected_error_code == 0 && errorCode == ERR_ABORTED)) { EXPECT_EQ(test_results_->expected_error_code, errorCode) << failedUrl.ToString(); } DestroyTestIfDone(); } protected: TestResults* test_results_; IMPLEMENT_REFCOUNTING(TestSchemeHandler); }; class ClientSchemeHandlerOld : public CefResourceHandler { public: explicit ClientSchemeHandlerOld(TestResults* tr) : test_results_(tr), offset_(0), is_sub_(false), has_delayed_(false) {} 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 CefBrowserSettings.accept_language set in // PopulateBrowserSettings(). 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::Bind(&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& response_length, CefString& redirectUrl) override { 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. CefResponse::HeaderMap headers; headers.insert(std::make_pair("Access-Control-Allow-Origin", test_results_->sub_allow_origin)); response->SetHeaderMap(headers); } 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(); } } } 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::Bind(&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* test_results_; size_t offset_; bool is_sub_; bool has_delayed_; IMPLEMENT_REFCOUNTING(ClientSchemeHandlerOld); DISALLOW_COPY_AND_ASSIGN(ClientSchemeHandlerOld); }; class ClientSchemeHandler : public CefResourceHandler { public: explicit ClientSchemeHandler(TestResults* tr) : test_results_(tr), offset_(0), is_sub_(false), has_delayed_(false) {} bool Open(CefRefPtr request, bool& handle_request, CefRefPtr callback) override { EXPECT_FALSE(CefCurrentlyOn(TID_UI) || 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 CefBrowserSettings.accept_language set in // PopulateBrowserSettings(). 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()); } // 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::Bind(&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 { EXPECT_TRUE(false); // Not reached. return false; } void GetResponseHeaders(CefRefPtr response, int64& response_length, CefString& redirectUrl) override { 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. CefResponse::HeaderMap headers; headers.insert(std::make_pair("Access-Control-Allow-Origin", test_results_->sub_allow_origin)); response->SetHeaderMap(headers); } 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(); } } } 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::Bind(&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* test_results_; size_t offset_; bool is_sub_; bool has_delayed_; 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* test_results_; IMPLEMENT_REFCOUNTING(ClientSchemeHandlerFactory); DISALLOW_COPY_AND_ASSIGN(ClientSchemeHandlerFactory); }; // Global test results object. TestResults g_TestResults; // If |domain| is empty the scheme will be registered as non-standard. void RegisterTestScheme(const std::string& scheme, const std::string& domain) { g_TestResults.reset(); EXPECT_TRUE(CefRegisterSchemeHandlerFactory( scheme, domain, new ClientSchemeHandlerFactory(&g_TestResults))); WaitForIOThread(); } void ClearTestSchemes() { EXPECT_TRUE(CefClearSchemeHandlerFactories()); WaitForIOThread(); } struct XHRTestSettings { XHRTestSettings() : synchronous(true) {} std::string url; std::string sub_url; std::string sub_allow_origin; std::string sub_redirect_url; bool synchronous; }; void SetUpXHR(const XHRTestSettings& settings) { g_TestResults.sub_url = settings.sub_url; g_TestResults.sub_html = "SUCCESS"; g_TestResults.sub_allow_origin = settings.sub_allow_origin; g_TestResults.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; g_TestResults.url = settings.url; std::stringstream ss; ss << "" "" "" "Running execXMLHttpRequest..." ""; g_TestResults.html = ss.str(); g_TestResults.exit_url = "http://tests/exit"; } struct FetchTestSettings { FetchTestSettings() {} std::string url; std::string sub_url; std::string sub_allow_origin; std::string sub_redirect_url; }; void SetUpFetch(const FetchTestSettings& settings) { g_TestResults.sub_url = settings.sub_url; g_TestResults.sub_html = "SUCCESS"; g_TestResults.sub_allow_origin = settings.sub_allow_origin; g_TestResults.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; g_TestResults.url = settings.url; std::stringstream ss; ss << "" "" "" "Running execFetchHttpRequest..." ""; g_TestResults.html = ss.str(); g_TestResults.exit_url = "http://tests/exit"; } // namespace void SetUpXSS(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 + "';"; g_TestResults.sub_url = sub_url; ss << "" "" "" "Running execXSSRequest..." ""; g_TestResults.sub_html = ss.str(); g_TestResults.url = url; ss.str(""); ss << "" "" "" "