// 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/cef_origin_whitelist.h" #include "include/cef_callback.h" #include "include/cef_runnable.h" #include "include/cef_scheme.h" #include "tests/unittests/test_handler.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(); 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 exit_url; // Delay for returning scheme handler results. int delay; TrackCallback got_request, got_read, got_output, got_redirect, got_error, 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; } virtual void RunTest() OVERRIDE { CreateBrowser(test_results_->url); } // Necessary to make the method public in order to destroy the test from // ClientSchemeHandler::ProcessRequest(). void DestroyTest() { TestHandler::DestroyTest(); } virtual 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 (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; } virtual 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. // TODO(cef): Enable this check once the HTTP status code is passed // correctly. // EXPECT_EQ(httpStatusCode, test_results_->status_code); if (test_results_->sub_url.empty()) DestroyTest(); } } virtual 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) { } virtual 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; } if (handled) { if (test_results_->delay > 0) { // Continue after the delay. CefPostDelayedTask(TID_IO, NewCefRunnableMethod(callback.get(), &CefCallback::Continue), test_results_->delay); } else { // Continue immediately. callback->Continue(); } return true; } // Response was canceled. if (g_current_handler) g_current_handler->DestroyTest(); return false; } virtual 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(); } } } virtual void Cancel() OVERRIDE { EXPECT_TRUE(CefCurrentlyOn(TID_IO)); } virtual 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, NewCefRunnableMethod(this, &ClientSchemeHandler::ContinueAfterDelay, 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; AutoLock lock_scope(this); 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); IMPLEMENT_LOCKING(ClientSchemeHandler); }; class ClientSchemeHandlerFactory : public CefSchemeHandlerFactory { public: explicit ClientSchemeHandlerFactory(TestResults* tr) : test_results_(tr) { } virtual 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(); } void SetUpXHR(const std::string& url, const std::string& sub_url, const std::string& sub_allow_origin = std::string()) { g_TestResults.sub_url = sub_url; g_TestResults.sub_html = "SUCCESS"; g_TestResults.sub_status_code = 200; g_TestResults.sub_allow_origin = sub_allow_origin; g_TestResults.url = 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 << "" "" "" "