// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // This test creates a fake safebrowsing service, where we can inject // malware and phishing urls. It then uses a real browser to go to // these urls, and sends "goback" or "proceed" commands and verifies // they work. #include "base/bind.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/safe_browsing/malware_details.h" #include "chrome/browser/safe_browsing/safe_browsing_blocking_page.h" #include "chrome/browser/safe_browsing/safe_browsing_service.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" #include "content/browser/renderer_host/resource_dispatcher_host.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/browser/tab_contents/tab_contents_view.h" #include "content/test/test_browser_thread.h" using content::BrowserThread; // A SafeBrowingService class that allows us to inject the malicious URLs. class FakeSafeBrowsingService : public SafeBrowsingService { public: FakeSafeBrowsingService() {} virtual ~FakeSafeBrowsingService() {} // Called on the IO thread to check if the given url is safe or not. If we // can synchronously determine that the url is safe, CheckUrl returns true. // Otherwise it returns false, and "client" is called asynchronously with the // result when it is ready. // Overrides SafeBrowsingService::CheckBrowseUrl. virtual bool CheckBrowseUrl(const GURL& gurl, Client* client) { if (badurls[gurl.spec()] == SAFE) return true; BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&FakeSafeBrowsingService::OnCheckBrowseURLDone, this, gurl, client)); return false; } void OnCheckBrowseURLDone(const GURL& gurl, Client* client) { SafeBrowsingService::SafeBrowsingCheck check; check.urls.push_back(gurl); check.client = client; check.result = badurls[gurl.spec()]; client->OnSafeBrowsingResult(check); } void AddURLResult(const GURL& url, UrlCheckResult checkresult) { badurls[url.spec()] = checkresult; } // Overrides SafeBrowsingService. virtual void SendSerializedMalwareDetails(const std::string& serialized) { reports_.push_back(serialized); // Notify the UI thread that we got a report. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&FakeSafeBrowsingService::OnMalwareDetailsDone, this)); } void OnMalwareDetailsDone() { EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI)); MessageLoopForUI::current()->Quit(); } std::string GetReport() { EXPECT_TRUE(reports_.size() == 1); return reports_[0]; } std::vector reports_; private: base::hash_map badurls; }; // Factory that creates FakeSafeBrowsingService instances. class TestSafeBrowsingServiceFactory : public SafeBrowsingServiceFactory { public: TestSafeBrowsingServiceFactory() { } virtual ~TestSafeBrowsingServiceFactory() { } virtual SafeBrowsingService* CreateSafeBrowsingService() { return new FakeSafeBrowsingService(); } }; // A MalwareDetails class lets us intercept calls from the renderer. class FakeMalwareDetails : public MalwareDetails { public: FakeMalwareDetails(SafeBrowsingService* sb_service, TabContents* tab_contents, const SafeBrowsingService::UnsafeResource& unsafe_resource) : MalwareDetails(sb_service, tab_contents, unsafe_resource) { } virtual ~FakeMalwareDetails() {} virtual void AddDOMDetails( const std::vector& params) { EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); MalwareDetails::AddDOMDetails(params); // Notify the UI thread that we got the dom details. BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&FakeMalwareDetails::OnDOMDetailsDone, this)); } void OnDOMDetailsDone() { got_dom_ = true; if (waiting_) { MessageLoopForUI::current()->Quit(); } } bool got_dom() const { return got_dom_; } bool waiting() const { return waiting_; } void set_got_dom(bool got_dom) { got_dom_ = got_dom; } void set_waiting(bool waiting) { waiting_ = waiting; } safe_browsing::ClientMalwareReportRequest* get_report() { return report_.get(); } private: // Some logic to figure out if we should wait for the dom details or not. // These variables should only be accessed in the UI thread. bool got_dom_; bool waiting_; }; class TestMalwareDetailsFactory : public MalwareDetailsFactory { public: TestMalwareDetailsFactory() { } virtual ~TestMalwareDetailsFactory() { } virtual MalwareDetails* CreateMalwareDetails( SafeBrowsingService* sb_service, TabContents* tab_contents, const SafeBrowsingService::UnsafeResource& unsafe_resource) { details_ = new FakeMalwareDetails(sb_service, tab_contents, unsafe_resource); return details_; } FakeMalwareDetails* get_details() { return details_; } private: FakeMalwareDetails* details_; }; // A SafeBrowingBlockingPage class that lets us wait until it's hidden. class TestSafeBrowsingBlockingPage : public SafeBrowsingBlockingPage { public: TestSafeBrowsingBlockingPage(SafeBrowsingService* service, TabContents* tab_contents, const UnsafeResourceList& unsafe_resources) : SafeBrowsingBlockingPage(service, tab_contents, unsafe_resources) { // Don't wait the whole 3 seconds for the browser test. malware_details_proceed_delay_ms_ = 100; wait_for_delete_ = false; } ~TestSafeBrowsingBlockingPage() { if (wait_for_delete_) { // Notify that we are gone MessageLoopForUI::current()->Quit(); } } void set_wait_for_delete() { wait_for_delete_ = true; } private: bool wait_for_delete_; }; class TestSafeBrowsingBlockingPageFactory : public SafeBrowsingBlockingPageFactory { public: TestSafeBrowsingBlockingPageFactory() { } ~TestSafeBrowsingBlockingPageFactory() { } virtual SafeBrowsingBlockingPage* CreateSafeBrowsingPage( SafeBrowsingService* service, TabContents* tab_contents, const SafeBrowsingBlockingPage::UnsafeResourceList& unsafe_resources) { return new TestSafeBrowsingBlockingPage(service, tab_contents, unsafe_resources); } }; // Tests the safe browsing blocking page in a browser. class SafeBrowsingBlockingPageTest : public InProcessBrowserTest, public SafeBrowsingService::Client { public: SafeBrowsingBlockingPageTest() { } virtual void SetUp() { SafeBrowsingService::RegisterFactory(&factory_); SafeBrowsingBlockingPage::RegisterFactory(&blocking_page_factory_); MalwareDetails::RegisterFactory(&details_factory_); InProcessBrowserTest::SetUp(); } virtual void TearDown() { InProcessBrowserTest::TearDown(); SafeBrowsingBlockingPage::RegisterFactory(NULL); SafeBrowsingService::RegisterFactory(NULL); MalwareDetails::RegisterFactory(NULL); } virtual void SetUpInProcessBrowserTestFixture() { ASSERT_TRUE(test_server()->Start()); } // SafeBrowsingService::Client implementation. virtual void OnSafeBrowsingResult( const SafeBrowsingService::SafeBrowsingCheck& check) { } virtual void OnBlockingPageComplete(bool proceed) { } void AddURLResult(const GURL& url, SafeBrowsingService::UrlCheckResult checkresult) { FakeSafeBrowsingService* service = static_cast( g_browser_process->safe_browsing_service()); ASSERT_TRUE(service); service->AddURLResult(url, checkresult); } void SendCommand(const std::string& command) { TabContents* contents = browser()->GetSelectedTabContents(); // We use InterstitialPage::GetInterstitialPage(tab) instead of // tab->interstitial_page() because the tab doesn't have a pointer // to its interstital page until it gets a command from the renderer // that it has indeed displayed it -- and this sometimes happens after // NavigateToURL returns. SafeBrowsingBlockingPage* interstitial_page = static_cast( InterstitialPage::GetInterstitialPage(contents)); ASSERT_TRUE(interstitial_page); interstitial_page->CommandReceived(command); } void DontProceedThroughInterstitial() { TabContents* contents = browser()->GetSelectedTabContents(); InterstitialPage* interstitial_page = InterstitialPage::GetInterstitialPage( contents); ASSERT_TRUE(interstitial_page); interstitial_page->DontProceed(); } void ProceedThroughInterstitial() { TabContents* contents = browser()->GetSelectedTabContents(); InterstitialPage* interstitial_page = InterstitialPage::GetInterstitialPage( contents); ASSERT_TRUE(interstitial_page); interstitial_page->Proceed(); } void AssertNoInterstitial(bool wait_for_delete) { TabContents* contents = browser()->GetSelectedTabContents(); if (contents->showing_interstitial_page() && wait_for_delete) { // We'll get notified when the interstitial is deleted. static_cast( contents->interstitial_page())->set_wait_for_delete(); ui_test_utils::RunMessageLoop(); } // Can't use InterstitialPage::GetInterstitialPage() because that // gets updated after the TestSafeBrowsingBlockingPage destructor ASSERT_FALSE(contents->showing_interstitial_page()); } bool YesInterstitial() { TabContents* contents = browser()->GetSelectedTabContents(); InterstitialPage* interstitial_page = InterstitialPage::GetInterstitialPage( contents); return interstitial_page != NULL; } void WaitForInterstitial() { TabContents* contents = browser()->GetSelectedTabContents(); ui_test_utils::WindowedNotificationObserver interstitial_observer( content::NOTIFICATION_INTERSTITIAL_ATTACHED, content::Source(contents)); if (!InterstitialPage::GetInterstitialPage(contents)) interstitial_observer.Wait(); } void AssertReportSent() { // When a report is scheduled in the IO thread we should get notified. ui_test_utils::RunMessageLoop(); FakeSafeBrowsingService* service = static_cast( g_browser_process->safe_browsing_service()); std::string serialized = service->GetReport(); safe_browsing::ClientMalwareReportRequest report; ASSERT_TRUE(report.ParseFromString(serialized)); // Verify the report is complete. EXPECT_TRUE(report.complete()); } void MalwareRedirectCancelAndProceed(const std::string open_function); protected: TestMalwareDetailsFactory details_factory_; private: TestSafeBrowsingServiceFactory factory_; TestSafeBrowsingBlockingPageFactory blocking_page_factory_; DISALLOW_COPY_AND_ASSIGN(SafeBrowsingBlockingPageTest); }; void SafeBrowsingBlockingPageTest::MalwareRedirectCancelAndProceed( const std::string open_function) { GURL load_url = test_server()->GetURL( "files/safe_browsing/interstitial_cancel.html"); GURL malware_url("http://localhost/files/safe_browsing/malware.html"); AddURLResult(malware_url, SafeBrowsingService::URL_MALWARE); // Load the test page. ui_test_utils::NavigateToURL(browser(), load_url); // Trigger the safe browsing interstitial page via a redirect in "openWin()". ui_test_utils::NavigateToURLWithDisposition( browser(), GURL("javascript:" + open_function + "()"), CURRENT_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB); WaitForInterstitial(); // Cancel the redirect request while interstitial page is open. browser()->ActivateTabAt(0, true); ui_test_utils::NavigateToURLWithDisposition( browser(), GURL("javascript:stopWin()"), CURRENT_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); browser()->ActivateTabAt(1, true); // Simulate the user clicking "proceed", there should be no crash. SendCommand("\"proceed\""); } namespace { const char kEmptyPage[] = "files/empty.html"; const char kMalwarePage[] = "files/safe_browsing/malware.html"; const char kMalwareIframe[] = "files/safe_browsing/malware_iframe.html"; IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageTest, MalwareRedirectInIFrameCanceled) { // 1. Test the case that redirect is a subresource. MalwareRedirectCancelAndProceed("openWinIFrame"); // If the redirect was from subresource but canceled, "proceed" will continue // with the rest of resources. AssertNoInterstitial(true); } IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageTest, MalwareRedirectCanceled) { // 2. Test the case that redirect is the only resource. MalwareRedirectCancelAndProceed("openWin"); // Clicking proceed won't do anything if the main request is cancelled // already. See crbug.com/76460. EXPECT_TRUE(YesInterstitial()); } IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageTest, MalwareDontProceed) { GURL url = test_server()->GetURL(kEmptyPage); AddURLResult(url, SafeBrowsingService::URL_MALWARE); ui_test_utils::NavigateToURL(browser(), url); SendCommand("\"takeMeBack\""); // Simulate the user clicking "back" AssertNoInterstitial(false); // Assert the interstitial is gone EXPECT_EQ(GURL(chrome::kAboutBlankURL), // Back to "about:blank" browser()->GetSelectedTabContents()->GetURL()); } IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageTest, MalwareProceed) { GURL url = test_server()->GetURL(kEmptyPage); AddURLResult(url, SafeBrowsingService::URL_MALWARE); ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::WindowedNotificationObserver observer( content::NOTIFICATION_LOAD_STOP, content::Source( &browser()->GetSelectedTabContentsWrapper()->controller())); SendCommand("\"proceed\""); // Simulate the user clicking "proceed" observer.Wait(); AssertNoInterstitial(true); // Assert the interstitial is gone. EXPECT_EQ(url, browser()->GetSelectedTabContents()->GetURL()); } IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageTest, PhishingDontProceed) { GURL url = test_server()->GetURL(kEmptyPage); AddURLResult(url, SafeBrowsingService::URL_PHISHING); ui_test_utils::NavigateToURL(browser(), url); SendCommand("\"takeMeBack\""); // Simulate the user clicking "proceed" AssertNoInterstitial(false); // Assert the interstitial is gone EXPECT_EQ(GURL(chrome::kAboutBlankURL), // We are back to "about:blank". browser()->GetSelectedTabContents()->GetURL()); } IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageTest, PhishingProceed) { GURL url = test_server()->GetURL(kEmptyPage); AddURLResult(url, SafeBrowsingService::URL_PHISHING); ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::WindowedNotificationObserver observer( content::NOTIFICATION_LOAD_STOP, content::Source( &browser()->GetSelectedTabContentsWrapper()->controller())); SendCommand("\"proceed\""); // Simulate the user clicking "proceed". observer.Wait(); AssertNoInterstitial(true); // Assert the interstitial is gone EXPECT_EQ(url, browser()->GetSelectedTabContents()->GetURL()); } IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageTest, PhishingReportError) { GURL url = test_server()->GetURL(kEmptyPage); AddURLResult(url, SafeBrowsingService::URL_PHISHING); ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::WindowedNotificationObserver observer( content::NOTIFICATION_LOAD_STOP, content::Source( &browser()->GetSelectedTabContentsWrapper()->controller())); SendCommand("\"reportError\""); // Simulate the user clicking "report error" observer.Wait(); AssertNoInterstitial(false); // Assert the interstitial is gone // We are in the error reporting page. EXPECT_EQ("/safebrowsing/report_error/", browser()->GetSelectedTabContents()->GetURL().path()); } IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageTest, PhishingLearnMore) { GURL url = test_server()->GetURL(kEmptyPage); AddURLResult(url, SafeBrowsingService::URL_PHISHING); ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::WindowedNotificationObserver observer( content::NOTIFICATION_LOAD_STOP, content::Source( &browser()->GetSelectedTabContentsWrapper()->controller())); SendCommand("\"learnMore\""); // Simulate the user clicking "learn more" observer.Wait(); AssertNoInterstitial(false); // Assert the interstitial is gone // We are in the help page. EXPECT_EQ("/support/bin/answer.py", browser()->GetSelectedTabContents()->GetURL().path()); } IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageTest, MalwareIframeDontProceed) { GURL url = test_server()->GetURL(kMalwarePage); GURL iframe_url = test_server()->GetURL(kMalwareIframe); AddURLResult(iframe_url, SafeBrowsingService::URL_MALWARE); ui_test_utils::NavigateToURL(browser(), url); SendCommand("\"takeMeBack\""); // Simulate the user clicking "back" AssertNoInterstitial(false); // Assert the interstitial is gone EXPECT_EQ(GURL(chrome::kAboutBlankURL), // Back to "about:blank" browser()->GetSelectedTabContents()->GetURL()); } // Crashy, http://crbug.com/68834. IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageTest, DISABLED_MalwareIframeProceed) { GURL url = test_server()->GetURL(kMalwarePage); GURL iframe_url = test_server()->GetURL(kMalwareIframe); AddURLResult(iframe_url, SafeBrowsingService::URL_MALWARE); ui_test_utils::NavigateToURL(browser(), url); SendCommand("\"proceed\""); // Simulate the user clicking "proceed" AssertNoInterstitial(true); // Assert the interstitial is gone EXPECT_EQ(url, browser()->GetSelectedTabContents()->GetURL()); } IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageTest, MalwareIframeReportDetails) { GURL url = test_server()->GetURL(kMalwarePage); GURL iframe_url = test_server()->GetURL(kMalwareIframe); AddURLResult(iframe_url, SafeBrowsingService::URL_MALWARE); ui_test_utils::NavigateToURL(browser(), url); // If the DOM details from renderer did not already return, wait for them. if (!details_factory_.get_details()->got_dom()) { // This condition might not trigger normally, but if you add a // sleep(1) in malware_dom_details it triggers :). details_factory_.get_details()->set_waiting(true); LOG(INFO) << "Waiting for dom details."; ui_test_utils::RunMessageLoop(); } else { LOG(INFO) << "Already got the dom details."; } SendCommand("\"doReport\""); // Simulate the user checking the checkbox. EXPECT_TRUE(browser()->GetProfile()->GetPrefs()->GetBoolean( prefs::kSafeBrowsingReportingEnabled)); SendCommand("\"proceed\""); // Simulate the user clicking "back" AssertNoInterstitial(true); // Assert the interstitial is gone EXPECT_EQ(url, browser()->GetSelectedTabContents()->GetURL()); AssertReportSent(); } } // namespace