// 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 this first to avoid type conflicts with CEF headers. #include "tests/unittests/chromium_includes.h" #include "include/base/cef_bind.h" #include "include/cef_origin_whitelist.h" #include "include/cef_callback.h" #include "include/cef_scheme.h" #include "include/wrapper/cef_closure_task.h" #include "tests/unittests/test_handler.h" #include "tests/unittests/test_suite.h" namespace { class TestResults { public: TestResults() : status_code(0), sub_status_code(0), delay(0) { } void reset() { url.clear(); html.clear(); status_code = 0; redirect_url.clear(); sub_url.clear(); sub_html.clear(); sub_status_code = 0; sub_allow_origin.clear(); exit_url.clear(); accept_language.clear(); delay = 0; got_request.reset(); got_read.reset(); got_output.reset(); got_redirect.reset(); got_error.reset(); got_sub_request.reset(); got_sub_read.reset(); got_sub_success.reset(); } std::string url; std::string html; int status_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_redirect, got_error, got_sub_redirect, got_sub_request, got_sub_read, got_sub_success; }; // Current scheme handler object. Used when destroying the test from // ClientSchemeHandler::ProcessRequest(). class TestSchemeHandler; TestSchemeHandler* g_current_handler = NULL; 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() { TestHandler::DestroyTest(); } bool OnBeforeResourceLoad(CefRefPtr browser, CefRefPtr frame, CefRefPtr request) override { std::string newUrl = request->GetURL(); if (!test_results_->exit_url.empty() && newUrl.find(test_results_->exit_url) != std::string::npos) { // XHR tests use an exit URL to destroy the test. if (newUrl.find("SUCCESS") != std::string::npos) test_results_->got_sub_success.yes(); DestroyTest(); return true; } 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 false; } void OnLoadEnd(CefRefPtr browser, CefRefPtr frame, int httpStatusCode) override { std::string url = frame->GetURL(); if (url == test_results_->url || test_results_->status_code != 200) { test_results_->got_output.yes(); // Test that the status code is correct. EXPECT_EQ(httpStatusCode, test_results_->status_code); if (test_results_->sub_url.empty()) DestroyTest(); } } void OnLoadError(CefRefPtr browser, CefRefPtr frame, ErrorCode errorCode, const CefString& errorText, const CefString& failedUrl) override { test_results_->got_error.yes(); DestroyTest(); } protected: TestResults* test_results_; }; class ClientSchemeHandler : public CefResourceHandler { public: explicit ClientSchemeHandler(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 { // Value from CefSettings.accept_language set in // CefTestSuite::GetSettings(). EXPECT_STREQ(CEF_SETTINGS_ACCEPT_LANGUAGE, 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; } // 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 { 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(&ClientSchemeHandler::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(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)); return new ClientSchemeHandler(test_results_); } TestResults* test_results_; IMPLEMENT_REFCOUNTING(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_status_code = 200; 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.status_code = 200; g_TestResults.exit_url = "http://tests/exit"; } 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 |xss_url|. // 3. |xss_url| tries to call a JS function in |url|. // 4. |url| tries to call a JS function in |xss_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.sub_status_code = 200; g_TestResults.url = url; ss.str(""); ss << "" "" "" "