| // 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_service.h" |
| #include "chrome/browser/safe_browsing/safe_browsing_blocking_page.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/browser_thread.h" |
| #include "content/browser/renderer_host/render_process_host.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" |
| |
| // 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<std::string> reports_; |
| |
| private: |
| base::hash_map<std::string, UrlCheckResult> 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<SafeBrowsingHostMsg_MalwareDOMDetails_Node>& 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<FakeSafeBrowsingService*>( |
| 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<SafeBrowsingBlockingPage*>( |
| 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<TestSafeBrowsingBlockingPage*>( |
| 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<TabContents>(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<FakeSafeBrowsingService*>( |
| 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<NavigationController>( |
| &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<NavigationController>( |
| &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<NavigationController>( |
| &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<NavigationController>( |
| &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 |