diff options
author | lzheng@google.com <lzheng@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-05 21:56:22 +0000 |
---|---|---|
committer | lzheng@google.com <lzheng@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-05 21:56:22 +0000 |
commit | 14a474b652821407cb9c093611169c4cf46255e3 (patch) | |
tree | 707c4e6a9e31c41d609d7426a972df2442ce09bf /chrome | |
parent | dc310e6377bcebbfd593714099487d066fabc724 (diff) | |
download | chromium_src-14a474b652821407cb9c093611169c4cf46255e3.zip chromium_src-14a474b652821407cb9c093611169c4cf46255e3.tar.gz chromium_src-14a474b652821407cb9c093611169c4cf46255e3.tar.bz2 |
Full end to end test using safebrowsing test server suite.
Review URL: http://codereview.chromium.org/3032008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@55138 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/safe_browsing/safe_browsing_browsertest.cc | 425 |
1 files changed, 394 insertions, 31 deletions
diff --git a/chrome/browser/safe_browsing/safe_browsing_browsertest.cc b/chrome/browser/safe_browsing/safe_browsing_browsertest.cc index be3510029..537f62a 100644 --- a/chrome/browser/safe_browsing/safe_browsing_browsertest.cc +++ b/chrome/browser/safe_browsing/safe_browsing_browsertest.cc @@ -2,18 +2,226 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <vector> + #include "base/command_line.h" #include "base/lock.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_thread.h" +#include "chrome/browser/profile.h" #include "chrome/browser/renderer_host/resource_dispatcher_host.h" #include "chrome/browser/safe_browsing/protocol_manager.h" #include "chrome/browser/safe_browsing/safe_browsing_service.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/in_process_browser_test.h" #include "chrome/test/ui_test_utils.h" +#include "net/base/host_resolver.h" +#include "net/base/load_flags.h" +#include "net/base/net_log.h" +#include "net/socket/tcp_pinger.h" #include "testing/gtest/include/gtest/gtest.h" +const char kHost[] = "127.0.0.1"; +const char kPort[] = "40101"; +const char kDataFile[] = "datafile.dat"; +const char kUrlVerifyPath[] = "/safebrowsing/verify_urls"; +const char kTestCompletePath[] = "/test_complete"; + +class SafeBrowsingTestServer { + public: + SafeBrowsingTestServer(const std::string& hostname, + const std::string& port, + const std::string& datafile) + : hostname_(hostname), + port_(port), + datafile_(datafile), + server_handle_(base::kNullProcessHandle) { + } + ~SafeBrowsingTestServer() { + EXPECT_EQ(server_handle_, base::kNullProcessHandle); + } + + // Start the python server test suite. + bool Start() { + // Get path to python server script + FilePath testserver_path; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &testserver_path)) + return false; + testserver_path = testserver_path + .Append(FILE_PATH_LITERAL("chrome")) + .Append(FILE_PATH_LITERAL("browser")) + .Append(FILE_PATH_LITERAL("safe_browsing")) + .Append(FILE_PATH_LITERAL("testserver")); + AppendToPythonPath(testserver_path); + + FilePath testserver = testserver_path.Append( + FILE_PATH_LITERAL("safebrowsing_test_server.py")); + +#if defined(OS_WIN) + FilePath datafile = testserver_path.Append(ASCIIToWide(datafile_)); + FilePath python_runtime; + // Get path to python interpreter + if (!PathService::Get(base::DIR_SOURCE_ROOT, &python_runtime)) + return false; + python_runtime = python_runtime + .Append(FILE_PATH_LITERAL("third_party")) + .Append(FILE_PATH_LITERAL("python_24")) + .Append(FILE_PATH_LITERAL("python.exe")); + + std::wstring command_line = + L"\"" + python_runtime.ToWStringHack() + L"\" " + + L"\"" + testserver.ToWStringHack() + + L"\" --port=" + UTF8ToWide(port_) + + L"\" --datafile=" + datafile.ToWStringHack() + L"\""; + + if (!base::LaunchApp(command_line, false, true, &server_handle_)) { + LOG(ERROR) << "Failed to launch " << command_line; + return false; + } +#elif defined(OS_POSIX) + FilePath datafile = testserver_path.Append(datafile_); + std::vector<std::string> command_line; + command_line.push_back("python"); + command_line.push_back(testserver.value()); + command_line.push_back("--port=" + port_); + command_line.push_back("--datafile=" + datafile.value()); + + base::file_handle_mapping_vector no_mappings; + LOG(INFO) << "Trying to launch " << command_line[0] << " ..."; + if (!base::LaunchApp(command_line, no_mappings, false, &server_handle_)) { + LOG(ERROR) << "Failed to launch " << command_line[0] << " ..."; + return false; + } +#endif + + // Let the server start, then verify that it's up. + // Our server is Python, and takes about 500ms to start + // up the first time, and about 200ms after that. + if (!WaitServerToStart(hostname_, StringToInt(port_))) { + LOG(ERROR) << "Failed to connect to server"; + Stop(); + return false; + } + + LOG(INFO) << "Started on port " << port_; + return true; + } + + // Stop the python server test suite. + bool Stop() { + if (!server_handle_) { + return true; + } + + // First check if the process has already terminated. + bool ret = base::WaitForSingleProcess(server_handle_, 0); + if (!ret) { + ret = base::KillProcess(server_handle_, 1, true); + } + + if (ret) { + base::CloseProcessHandle(server_handle_); + server_handle_ = base::kNullProcessHandle; + LOG(INFO) << "Stopped."; + } else { + LOG(INFO) << "Kill failed?"; + return false; + } + return true; + } + + private: + // Make sure the test server is actually started and ready to serve. + bool WaitServerToStart(const std::string& host_name, int port) { + net::AddressList addr; + scoped_refptr<net::HostResolver> resolver(net::CreateSystemHostResolver( + net::HostResolver::kDefaultParallelism)); + net::HostResolver::RequestInfo info(host_name, port); + int rv = resolver->Resolve(info, &addr, NULL, NULL, net::BoundNetLog()); + if (rv != net::OK) + return false; + + net::TCPPinger pinger(addr); + rv = pinger.Ping(base::TimeDelta::FromSeconds(kConnectionTimeoutSec), + kRetryAttempts); + return rv == net::OK; + } + + void AppendToPythonPath(const FilePath& dir) { +#if defined(OS_WIN) + const wchar_t kPythonPath[] = L"PYTHONPATH"; + wchar_t oldpath[8192]; + if (GetEnvironmentVariable(kPythonPath, oldpath, arraysize(oldpath)) == 0) { + SetEnvironmentVariableW(kPythonPath, dir.value().c_str()); + } else if (!wcsstr(oldpath, dir.value().c_str())) { + std::wstring newpath(oldpath); + newpath.append(L";"); + newpath.append(dir.value()); + SetEnvironmentVariableW(kPythonPath, newpath.c_str()); + } +#elif defined(OS_POSIX) + const char kPythonPath[] = "PYTHONPATH"; + const char* oldpath = getenv(kPythonPath); + // setenv() leaks memory intentionally on Mac + if (!oldpath) { + setenv(kPythonPath, dir.value().c_str(), 1); + } else if (!strstr(oldpath, dir.value().c_str())) { + std::string newpath(oldpath); + newpath.append(":"); + newpath.append(dir.value()); + setenv(kPythonPath, newpath.c_str(), 1); + } +#endif + } + + std::string hostname_; + std::string port_; + std::string datafile_; + static const int kConnectionTimeoutSec = 5; + static const int kRetryAttempts = 3; + base::ProcessHandle server_handle_; +}; + +typedef struct { + std::string url; + std::string list_name; + bool is_phishing; +} PhishingUrl; + +// Parses server response for verify_urls. The expected format is: +// +// first.random.url.com/ internal-test-shavar yes +// second.random.url.com/ internal-test-shavar yes +// ... +static bool ParsePhishingUrls(const std::string& data, + std::vector<PhishingUrl>* phishing_urls) { + std::vector<std::string> urls; + SplitString(data, '\n', &urls); + for (size_t i = 0; i < urls.size(); ++i) { + PhishingUrl phishing_url; + std::vector<std::string> url_parts; + SplitStringAlongWhitespace(urls[i], &url_parts); + if (url_parts.size() < 3) { + CHECK(false) << "Unexpected URL format in phishing URL list: " + << urls[i]; + return false; + } + phishing_url.url = url_parts[0]; + phishing_url.list_name = url_parts[1]; + if (url_parts[2] == "yes") { + phishing_url.is_phishing = true; + } else { + CHECK_EQ("no", url_parts[2]); + phishing_url.is_phishing = false; + } + phishing_urls->push_back(phishing_url); + } + return true; +} + // This starts the browser and keeps status of states related to SafeBrowsing. class SafeBrowsingServiceTest : public InProcessBrowserTest { public: @@ -22,7 +230,8 @@ class SafeBrowsingServiceTest : public InProcessBrowserTest { is_update_in_progress_(false), is_initial_request_(false), is_update_scheduled_(false), - is_url_match_in_db_(false) { + is_checked_url_in_db_(false), + is_checked_url_safe_(false) { } void UpdateSafeBrowsingStatus() { @@ -36,19 +245,34 @@ class SafeBrowsingServiceTest : public InProcessBrowserTest { safe_browsing_service_->protocol_manager_->update_timer_.IsRunning(); } + void ForceUpdate() { + CHECK(safe_browsing_service_); + safe_browsing_service_->protocol_manager_->ForceScheduleNextUpdate(0); + } + void CheckUrl(SafeBrowsingService::Client* helper, const GURL& url) { CHECK(safe_browsing_service_); AutoLock lock(update_status_mutex_); - if (!safe_browsing_service_->CheckUrl(url, helper)) { - safe_browsing_service_->CancelCheck(helper); - is_url_match_in_db_ = false; + if (safe_browsing_service_->CheckUrl(url, helper)) { + is_checked_url_in_db_ = false; + return; } - is_url_match_in_db_ = true; + is_checked_url_in_db_ = true; + } + + bool is_checked_url_in_db() { + AutoLock l(update_status_mutex_); + return is_checked_url_in_db_; + } + + void set_is_checked_url_safe(bool safe) { + AutoLock l(update_status_mutex_); + is_checked_url_safe_ = safe; } - bool is_url_match_in_db() { + bool is_checked_url_safe() { AutoLock l(update_status_mutex_); - return is_url_match_in_db_; + return is_checked_url_safe_; } bool is_update_in_progress() { @@ -81,6 +305,15 @@ class SafeBrowsingServiceTest : public InProcessBrowserTest { // Makes sure the auto update is not triggered. This test will force the // update when needed. command_line->AppendSwitch(switches::kSbDisableAutoUpdate); + command_line->AppendSwitchWithValue( + switches::kSbInfoURLPrefix, "http://localhost:40101/safebrowsing"); + command_line->AppendSwitchWithValue( + switches::kSbMacKeyURLPrefix, "http://localhost:40101/safebrowsing"); + } + + void SetTestStep(int step) { + std::string test_step = StringPrintf("&test_step=%d", step); + safe_browsing_service_->protocol_manager_->set_additional_query(test_step); } private: @@ -96,7 +329,9 @@ class SafeBrowsingServiceTest : public InProcessBrowserTest { bool is_update_scheduled_; // Indicates if there is a match between a URL's prefix and safebrowsing // database (thus potentially it is a phishing url). - bool is_url_match_in_db_; + bool is_checked_url_in_db_; + // True if last verified URL is not a phishing URL and thus it is safe. + bool is_checked_url_safe_; DISALLOW_COPY_AND_ASSIGN(SafeBrowsingServiceTest); }; @@ -104,24 +339,45 @@ class SafeBrowsingServiceTest : public InProcessBrowserTest { // thread. class SafeBrowsingServiceTestHelper : public base::RefCountedThreadSafe<SafeBrowsingServiceTestHelper>, - public SafeBrowsingService::Client { + public SafeBrowsingService::Client, + public URLFetcher::Delegate { public: explicit SafeBrowsingServiceTestHelper( SafeBrowsingServiceTest* safe_browsing_test) : safe_browsing_test_(safe_browsing_test) { } - // Callbacks for SafeBrowsingService::Client. Not implemented yet. + // Callbacks for SafeBrowsingService::Client. virtual void OnUrlCheckResult(const GURL& url, SafeBrowsingService::UrlCheckResult result) { - NOTREACHED() << "Not implemented."; + CHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + CHECK(safe_browsing_test_->is_checked_url_in_db()); + safe_browsing_test_->set_is_checked_url_safe( + result == SafeBrowsingService::URL_SAFE); + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, + &SafeBrowsingServiceTestHelper::OnCheckUrlOnIOThreadDone)); } virtual void OnBlockingPageComplete(bool proceed) { NOTREACHED() << "Not implemented."; } - // Functions and callbacks related to CheckUrl. These are used to verify if - // a URL is a phishing URL. + // Functions and callbacks to start the safebrowsing database update. + void ForceUpdate() { + ChromeThread::PostTask(ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, + &SafeBrowsingServiceTestHelper::ForceUpdateInIOThread)); + } + void ForceUpdateInIOThread() { + CHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + safe_browsing_test_->ForceUpdate(); + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, + &SafeBrowsingServiceTestHelper::OnForceUpdateDone)); + } + void OnForceUpdateDone() { + StopUILoop(); + } + + // Functions and callbacks related to CheckUrl. These are used to verify + // phishing URLs. void CheckUrl(const GURL& url) { ChromeThread::PostTask(ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, &SafeBrowsingServiceTestHelper::CheckUrlOnIOThread, url)); @@ -129,14 +385,27 @@ class SafeBrowsingServiceTestHelper void CheckUrlOnIOThread(const GURL& url) { CHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); safe_browsing_test_->CheckUrl(this, url); - ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, - &SafeBrowsingServiceTestHelper::OnCheckUrlOnIOThreadDone)); + if (!safe_browsing_test_->is_checked_url_in_db()) { + // Ends the checking since this url's prefix is not in database. + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, NewRunnableMethod( + this, &SafeBrowsingServiceTestHelper::OnCheckUrlOnIOThreadDone)); + } + // Otherwise, OnCheckUrlOnIOThreadDone is called in OnUrlCheckResult since + // safebrowsing service further fetches hashes from safebrowsing server. } void OnCheckUrlOnIOThreadDone() { StopUILoop(); } // Functions and callbacks related to safebrowsing server status. + void CheckStatusAfterDelay(int64 wait_time_sec) { + ChromeThread::PostDelayedTask( + ChromeThread::IO, + FROM_HERE, + NewRunnableMethod(this, + &SafeBrowsingServiceTestHelper::CheckStatusOnIOThread), + wait_time_sec * 1000); + } void CheckStatusOnIOThread() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); safe_browsing_test_->UpdateSafeBrowsingStatus(); @@ -146,13 +415,47 @@ class SafeBrowsingServiceTestHelper void OnCheckStatusOnIOThreadDone() { StopUILoop(); } - void CheckStatusAfterDelay(int64 wait_time_sec) { - ChromeThread::PostDelayedTask( - ChromeThread::IO, - FROM_HERE, - NewRunnableMethod(this, - &SafeBrowsingServiceTestHelper::CheckStatusOnIOThread), - wait_time_sec * 1000); + + // Calls test server to fetch urls for verification. + void FetchUrlsToVerify(const std::string& host, + const std::string& port, + int test_step) { + GURL url(StringPrintf("http://%s:%s/%s?" + "client=chromium&appver=1.0&pver=2.2&test_step=%d", + host.c_str(), port.c_str(), kUrlVerifyPath, + test_step)); + url_fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this)); + url_fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE); + url_fetcher_->set_request_context(Profile::GetDefaultRequestContext()); + url_fetcher_->Start(); + } + + // Calls test server to check if test data is done. + void VerifyTestComplete(const std::string& host, + const std::string& port, + int test_step) { + GURL url(StringPrintf("http://%s:%s/%s?test_step=%d", + host.c_str(), port.c_str(), kTestCompletePath, + test_step)); + url_fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this)); + url_fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE); + url_fetcher_->set_request_context(Profile::GetDefaultRequestContext()); + url_fetcher_->Start(); + } + + // Callback for URLFetcher. + virtual void OnURLFetchComplete(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data) { + response_data_ = data; + StopUILoop(); + } + + const std::string& response_data() { + return response_data_; } private: @@ -164,6 +467,8 @@ class SafeBrowsingServiceTestHelper base::OneShotTimer<SafeBrowsingServiceTestHelper> check_update_timer_; SafeBrowsingServiceTest* safe_browsing_test_; + scoped_ptr<URLFetcher> url_fetcher_; + std::string response_data_; DISALLOW_COPY_AND_ASSIGN(SafeBrowsingServiceTestHelper); }; @@ -173,24 +478,82 @@ IN_PROC_BROWSER_TEST_F(SafeBrowsingServiceTest, SafeBrowsingSystemTest) { new SafeBrowsingServiceTestHelper(this); // Waits for 1 sec and makes sure safebrowsing update is not happening. + // MessageLoop will stop once OnCheckStatusOnIOThreadDone in + // safe_browsing_helper is called and status from safe_browsing_service_ + // is checked. safe_browsing_helper->CheckStatusAfterDelay(1); - // Loop will stop once OnCheckStatusOnIOThreadDone in safe_browsing_helper - // is called and status from safe_browsing_service_ is checked. ui_test_utils::RunMessageLoop(); EXPECT_FALSE(is_update_in_progress()); EXPECT_TRUE(is_initial_request()); EXPECT_FALSE(is_update_scheduled()); EXPECT_TRUE(last_update().is_null()); - // Verify URL. + // Verify with a test phishing URL. Loop will stop once + // OnCheckUrlOnIOThreadDone in safe_browsing_helper is called and URL check + // is done. const char test_url[] = "http://ianfette.org"; safe_browsing_helper->CheckUrl(GURL(test_url)); - // Loop will stop once OnCheckUrlOnIOThreadDone in safe_browsing_helper - // is called and url check is done. ui_test_utils::RunMessageLoop(); - EXPECT_TRUE(is_url_match_in_db()); + EXPECT_TRUE(is_checked_url_in_db()); + + SafeBrowsingTestServer test_server(kHost, kPort, kDataFile); + test_server.Start(); + // Limits max test steps so the test won't run wild. + const int kMaxSteps = 10; + int last_step = 0; + // Starts from test step 1. + for (int step = 1; step < kMaxSteps; step++) { + // Every step should be a fresh start. + EXPECT_FALSE(is_update_in_progress()); + EXPECT_FALSE(is_update_scheduled()); + + // Starts safebrowsing update on IO thread. Waits till scheduled + // update finishes. Stops waiting after kMaxWaitSec if the update + // could not finish. + base::Time now = base::Time::Now(); + SetTestStep(step); + safe_browsing_helper->ForceUpdate(); + ui_test_utils::RunMessageLoop(); + int wait_sec = 0; + const int kMaxWaitSec = 10; + while ((is_update_scheduled() || is_update_in_progress()) && + wait_sec++ < kMaxWaitSec) { + safe_browsing_helper->CheckStatusAfterDelay(1); + ui_test_utils::RunMessageLoop(); + } + EXPECT_LT(wait_sec, kMaxWaitSec) + << "Can't finish update in required time limit!"; + if (last_update() < now) { + // This means no data available anymore. + break; + } + + // Fetches urls to verify and waits till server responses with data. + safe_browsing_helper->FetchUrlsToVerify(kHost, kPort, step); + ui_test_utils::RunMessageLoop(); + LOG(INFO) << safe_browsing_helper->response_data(); + + std::vector<PhishingUrl> phishing_urls; + EXPECT_TRUE(ParsePhishingUrls(safe_browsing_helper->response_data(), + &phishing_urls)); + for (size_t j = 0; j < phishing_urls.size(); ++j) { + // Verifes with server if a URL is a phishing URL and waits till server + // responses. + safe_browsing_helper->CheckUrl(GURL(phishing_urls[j].url)); + ui_test_utils::RunMessageLoop(); + if (phishing_urls[j].is_phishing) { + EXPECT_TRUE(is_checked_url_in_db()); + EXPECT_FALSE(is_checked_url_safe()); + } else { + EXPECT_TRUE(is_checked_url_in_db()); + } + } + last_step = step; + } - // TODO(lzheng): Add tests to launch a testing safebrowsing server - // and issue requests repeatedly: - // http://code.google.com/p/google-safe-browsing/wiki/ProtocolTesting + // Verifies with server if test is done and waits till server responses. + safe_browsing_helper->VerifyTestComplete(kHost, kPort, last_step); + ui_test_utils::RunMessageLoop(); + EXPECT_EQ("yes", safe_browsing_helper->response_data()); + test_server.Stop(); } |