- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / renderer / safe_browsing / phishing_classifier_delegate_browsertest.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 //
5 // Note: this test uses RenderViewFakeResourcesTest in order to set up a
6 // real RenderThread to hold the phishing Scorer object.
7
8 #include "chrome/renderer/safe_browsing/phishing_classifier_delegate.h"
9
10 #include "base/memory/scoped_ptr.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/common/safe_browsing/csd.pb.h"
13 #include "chrome/common/safe_browsing/safebrowsing_messages.h"
14 #include "chrome/renderer/safe_browsing/features.h"
15 #include "chrome/renderer/safe_browsing/phishing_classifier.h"
16 #include "chrome/renderer/safe_browsing/scorer.h"
17 #include "content/public/renderer/render_view.h"
18 #include "content/public/test/render_view_fake_resources_test.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "third_party/WebKit/public/platform/WebURL.h"
21 #include "third_party/WebKit/public/platform/WebURLRequest.h"
22 #include "third_party/WebKit/public/web/WebFrame.h"
23 #include "third_party/WebKit/public/web/WebHistoryItem.h"
24 #include "url/gurl.h"
25
26 using ::testing::_;
27 using ::testing::InSequence;
28 using ::testing::Mock;
29 using ::testing::Pointee;
30 using ::testing::StrictMock;
31
32 namespace safe_browsing {
33
34 namespace {
35 class MockPhishingClassifier : public PhishingClassifier {
36  public:
37   explicit MockPhishingClassifier(content::RenderView* render_view)
38       : PhishingClassifier(render_view, NULL /* clock */) {}
39
40   virtual ~MockPhishingClassifier() {}
41
42   MOCK_METHOD2(BeginClassification, void(const string16*, const DoneCallback&));
43   MOCK_METHOD0(CancelPendingClassification, void());
44
45  private:
46   DISALLOW_COPY_AND_ASSIGN(MockPhishingClassifier);
47 };
48
49 class MockScorer : public Scorer {
50  public:
51   MockScorer() : Scorer() {}
52   virtual ~MockScorer() {}
53
54   MOCK_CONST_METHOD1(ComputeScore, double(const FeatureMap&));
55
56  private:
57   DISALLOW_COPY_AND_ASSIGN(MockScorer);
58 };
59 }  // namespace
60
61 class PhishingClassifierDelegateTest
62     : public content::RenderViewFakeResourcesTest {
63  protected:
64   virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
65     bool handled = true;
66     IPC_BEGIN_MESSAGE_MAP(PhishingClassifierDelegateTest, message)
67         IPC_MESSAGE_HANDLER(SafeBrowsingHostMsg_PhishingDetectionDone,
68                             OnPhishingDetectionDone)
69       IPC_MESSAGE_UNHANDLED(handled =
70           content::RenderViewFakeResourcesTest::OnMessageReceived(message))
71     IPC_END_MESSAGE_MAP()
72     return handled;
73   }
74
75   void OnPhishingDetectionDone(const std::string& verdict_str) {
76     scoped_ptr<ClientPhishingRequest> verdict(new ClientPhishingRequest);
77     if (verdict->ParseFromString(verdict_str) &&
78         verdict->IsInitialized()) {
79       verdict_.swap(verdict);
80     }
81     message_loop_.Quit();
82   }
83
84   // Runs the ClassificationDone callback, then waits for the
85   // PhishingDetectionDone IPC to arrive.
86   void RunClassificationDone(PhishingClassifierDelegate* delegate,
87                              const ClientPhishingRequest& verdict) {
88     // Clear out any previous state.
89     verdict_.reset();
90     delegate->ClassificationDone(verdict);
91     message_loop_.Run();
92   }
93
94   void OnStartPhishingDetection(PhishingClassifierDelegate* delegate,
95                                 const GURL& url) {
96     delegate->OnStartPhishingDetection(url);
97   }
98
99   scoped_ptr<ClientPhishingRequest> verdict_;
100 };
101
102 TEST_F(PhishingClassifierDelegateTest, Navigation) {
103   MockPhishingClassifier* classifier =
104       new StrictMock<MockPhishingClassifier>(view());
105   PhishingClassifierDelegate* delegate =
106       PhishingClassifierDelegate::Create(view(), classifier);
107   MockScorer scorer;
108   delegate->SetPhishingScorer(&scorer);
109   ASSERT_TRUE(classifier->is_ready());
110
111   // Test an initial load.  We expect classification to happen normally.
112   EXPECT_CALL(*classifier, CancelPendingClassification()).Times(2);
113   responses_["http://host.com/"] =
114       "<html><body><iframe src=\"http://sub1.com/\"></iframe></body></html>";
115   LoadURL("http://host.com/");
116   Mock::VerifyAndClearExpectations(classifier);
117   OnStartPhishingDetection(delegate, GURL("http://host.com/"));
118   string16 page_text = ASCIIToUTF16("dummy");
119   {
120     InSequence s;
121     EXPECT_CALL(*classifier, CancelPendingClassification());
122     EXPECT_CALL(*classifier, BeginClassification(Pointee(page_text), _));
123     delegate->PageCaptured(&page_text, false);
124     Mock::VerifyAndClearExpectations(classifier);
125   }
126
127   // Reloading the same page should not trigger a reclassification.
128   // However, it will cancel any pending classification since the
129   // content is being replaced.
130   EXPECT_CALL(*classifier, CancelPendingClassification()).Times(2);
131   GetMainFrame()->reload();
132   message_loop_.Run();
133   Mock::VerifyAndClearExpectations(classifier);
134   OnStartPhishingDetection(delegate, GURL("http://host.com/"));
135   page_text = ASCIIToUTF16("dummy");
136   EXPECT_CALL(*classifier, CancelPendingClassification());
137   delegate->PageCaptured(&page_text, false);
138   Mock::VerifyAndClearExpectations(classifier);
139
140   // Navigating in a subframe will not change the toplevel URL.  However, this
141   // should cancel pending classification since the page content is changing.
142   // Currently, we do not start a new classification after subframe loads.
143   EXPECT_CALL(*classifier, CancelPendingClassification());
144   GetMainFrame()->firstChild()->loadRequest(
145       WebKit::WebURLRequest(GURL("http://sub2.com/")));
146   message_loop_.Run();
147   Mock::VerifyAndClearExpectations(classifier);
148   OnStartPhishingDetection(delegate, GURL("http://host.com/"));
149   page_text = ASCIIToUTF16("dummy");
150   EXPECT_CALL(*classifier, CancelPendingClassification());
151   delegate->PageCaptured(&page_text, false);
152   Mock::VerifyAndClearExpectations(classifier);
153
154   // Scrolling to an anchor works similarly to a subframe navigation, but
155   // see the TODO in PhishingClassifierDelegate::DidCommitProvisionalLoad.
156   EXPECT_CALL(*classifier, CancelPendingClassification());
157   LoadURL("http://host.com/#foo");
158   Mock::VerifyAndClearExpectations(classifier);
159   OnStartPhishingDetection(delegate, GURL("http://host.com/#foo"));
160   page_text = ASCIIToUTF16("dummy");
161   EXPECT_CALL(*classifier, CancelPendingClassification());
162   delegate->PageCaptured(&page_text, false);
163   Mock::VerifyAndClearExpectations(classifier);
164
165   // Now load a new toplevel page, which should trigger another classification.
166   EXPECT_CALL(*classifier, CancelPendingClassification());
167   LoadURL("http://host2.com/");
168   Mock::VerifyAndClearExpectations(classifier);
169   page_text = ASCIIToUTF16("dummy2");
170   OnStartPhishingDetection(delegate, GURL("http://host2.com/"));
171   {
172     InSequence s;
173     EXPECT_CALL(*classifier, CancelPendingClassification());
174     EXPECT_CALL(*classifier, BeginClassification(Pointee(page_text), _));
175     delegate->PageCaptured(&page_text, false);
176     Mock::VerifyAndClearExpectations(classifier);
177   }
178
179   // No classification should happen on back/forward navigation.
180   // Note: in practice, the browser will not send a StartPhishingDetection IPC
181   // in this case.  However, we want to make sure that the delegate behaves
182   // correctly regardless.
183   WebKit::WebHistoryItem forward_item = GetMainFrame()->currentHistoryItem();
184   EXPECT_CALL(*classifier, CancelPendingClassification());
185   GoBack();
186   Mock::VerifyAndClearExpectations(classifier);
187   page_text = ASCIIToUTF16("dummy");
188   OnStartPhishingDetection(delegate, GURL("http://host.com/#foo"));
189   EXPECT_CALL(*classifier, CancelPendingClassification());
190   delegate->PageCaptured(&page_text, false);
191   Mock::VerifyAndClearExpectations(classifier);
192
193   EXPECT_CALL(*classifier, CancelPendingClassification());
194   GoForward(forward_item);
195   Mock::VerifyAndClearExpectations(classifier);
196   page_text = ASCIIToUTF16("dummy2");
197   OnStartPhishingDetection(delegate, GURL("http://host2.com/"));
198   EXPECT_CALL(*classifier, CancelPendingClassification());
199   delegate->PageCaptured(&page_text, false);
200   Mock::VerifyAndClearExpectations(classifier);
201
202   // Now go back again and scroll to a different anchor.
203   // No classification should happen.
204   EXPECT_CALL(*classifier, CancelPendingClassification());
205   GoBack();
206   Mock::VerifyAndClearExpectations(classifier);
207   page_text = ASCIIToUTF16("dummy");
208   OnStartPhishingDetection(delegate, GURL("http://host.com/#foo"));
209   EXPECT_CALL(*classifier, CancelPendingClassification());
210   delegate->PageCaptured(&page_text, false);
211   Mock::VerifyAndClearExpectations(classifier);
212
213   EXPECT_CALL(*classifier, CancelPendingClassification());
214   LoadURL("http://host.com/#foo2");
215   Mock::VerifyAndClearExpectations(classifier);
216   OnStartPhishingDetection(delegate, GURL("http://host.com/#foo2"));
217   page_text = ASCIIToUTF16("dummy");
218   EXPECT_CALL(*classifier, CancelPendingClassification());
219   delegate->PageCaptured(&page_text, false);
220   Mock::VerifyAndClearExpectations(classifier);
221
222   // The delegate will cancel pending classification on destruction.
223   EXPECT_CALL(*classifier, CancelPendingClassification());
224 }
225
226 TEST_F(PhishingClassifierDelegateTest, NoScorer) {
227   // For this test, we'll create the delegate with no scorer available yet.
228   MockPhishingClassifier* classifier =
229       new StrictMock<MockPhishingClassifier>(view());
230   PhishingClassifierDelegate* delegate =
231       PhishingClassifierDelegate::Create(view(), classifier);
232   ASSERT_FALSE(classifier->is_ready());
233
234   // Queue up a pending classification, cancel it, then queue up another one.
235   LoadURL("http://host.com/");
236   string16 page_text = ASCIIToUTF16("dummy");
237   OnStartPhishingDetection(delegate, GURL("http://host.com/"));
238   delegate->PageCaptured(&page_text, false);
239
240   LoadURL("http://host2.com/");
241   page_text = ASCIIToUTF16("dummy2");
242   OnStartPhishingDetection(delegate, GURL("http://host2.com/"));
243   delegate->PageCaptured(&page_text, false);
244
245   // Now set a scorer, which should cause a classifier to be created and
246   // the classification to proceed.
247   page_text = ASCIIToUTF16("dummy2");
248   EXPECT_CALL(*classifier, BeginClassification(Pointee(page_text), _));
249   MockScorer scorer;
250   delegate->SetPhishingScorer(&scorer);
251   Mock::VerifyAndClearExpectations(classifier);
252
253   // If we set a new scorer while a classification is going on the
254   // classification should be cancelled.
255   EXPECT_CALL(*classifier, CancelPendingClassification());
256   delegate->SetPhishingScorer(&scorer);
257   Mock::VerifyAndClearExpectations(classifier);
258
259   // The delegate will cancel pending classification on destruction.
260   EXPECT_CALL(*classifier, CancelPendingClassification());
261 }
262
263 TEST_F(PhishingClassifierDelegateTest, NoScorer_Ref) {
264   // Similar to the last test, but navigates within the page before
265   // setting the scorer.
266   MockPhishingClassifier* classifier =
267       new StrictMock<MockPhishingClassifier>(view());
268   PhishingClassifierDelegate* delegate =
269       PhishingClassifierDelegate::Create(view(), classifier);
270   ASSERT_FALSE(classifier->is_ready());
271
272   // Queue up a pending classification, cancel it, then queue up another one.
273   LoadURL("http://host.com/");
274   string16 page_text = ASCIIToUTF16("dummy");
275   OnStartPhishingDetection(delegate, GURL("http://host.com/"));
276   delegate->PageCaptured(&page_text, false);
277
278   LoadURL("http://host.com/#foo");
279   OnStartPhishingDetection(delegate, GURL("http://host.com/#foo"));
280   page_text = ASCIIToUTF16("dummy");
281   delegate->PageCaptured(&page_text, false);
282
283   // Now set a scorer, which should cause a classifier to be created and
284   // the classification to proceed.
285   page_text = ASCIIToUTF16("dummy");
286   EXPECT_CALL(*classifier, BeginClassification(Pointee(page_text), _));
287   MockScorer scorer;
288   delegate->SetPhishingScorer(&scorer);
289   Mock::VerifyAndClearExpectations(classifier);
290
291   // The delegate will cancel pending classification on destruction.
292   EXPECT_CALL(*classifier, CancelPendingClassification());
293 }
294
295 TEST_F(PhishingClassifierDelegateTest, NoStartPhishingDetection) {
296   // Tests the behavior when OnStartPhishingDetection has not yet been called
297   // when the page load finishes.
298   MockPhishingClassifier* classifier =
299       new StrictMock<MockPhishingClassifier>(view());
300   PhishingClassifierDelegate* delegate =
301       PhishingClassifierDelegate::Create(view(), classifier);
302   MockScorer scorer;
303   delegate->SetPhishingScorer(&scorer);
304   ASSERT_TRUE(classifier->is_ready());
305
306   EXPECT_CALL(*classifier, CancelPendingClassification());
307   responses_["http://host.com/"] = "<html><body>phish</body></html>";
308   LoadURL("http://host.com/");
309   Mock::VerifyAndClearExpectations(classifier);
310   string16 page_text = ASCIIToUTF16("phish");
311   EXPECT_CALL(*classifier, CancelPendingClassification());
312   delegate->PageCaptured(&page_text, false);
313   Mock::VerifyAndClearExpectations(classifier);
314   // Now simulate the StartPhishingDetection IPC.  We expect classification
315   // to begin.
316   page_text = ASCIIToUTF16("phish");
317   EXPECT_CALL(*classifier, BeginClassification(Pointee(page_text), _));
318   OnStartPhishingDetection(delegate, GURL("http://host.com/"));
319   Mock::VerifyAndClearExpectations(classifier);
320
321   // Now try again, but this time we will navigate the page away before
322   // the IPC is sent.
323   EXPECT_CALL(*classifier, CancelPendingClassification());
324   responses_["http://host2.com/"] = "<html><body>phish</body></html>";
325   LoadURL("http://host2.com/");
326   Mock::VerifyAndClearExpectations(classifier);
327   page_text = ASCIIToUTF16("phish");
328   EXPECT_CALL(*classifier, CancelPendingClassification());
329   delegate->PageCaptured(&page_text, false);
330   Mock::VerifyAndClearExpectations(classifier);
331
332   EXPECT_CALL(*classifier, CancelPendingClassification());
333   responses_["http://host3.com/"] = "<html><body>phish</body></html>";
334   LoadURL("http://host3.com/");
335   Mock::VerifyAndClearExpectations(classifier);
336   OnStartPhishingDetection(delegate, GURL("http://host2.com/"));
337
338   // In this test, the original page is a redirect, which we do not get a
339   // StartPhishingDetection IPC for.  We use location.replace() to load a
340   // new page while reusing the original session history entry, and check that
341   // classification begins correctly for the landing page.
342   EXPECT_CALL(*classifier, CancelPendingClassification());
343   responses_["http://host4.com/"] = "<html><body>abc</body></html>";
344   LoadURL("http://host4.com/");
345   Mock::VerifyAndClearExpectations(classifier);
346   page_text = ASCIIToUTF16("abc");
347   EXPECT_CALL(*classifier, CancelPendingClassification());
348   delegate->PageCaptured(&page_text, false);
349   Mock::VerifyAndClearExpectations(classifier);
350   responses_["http://host4.com/redir"] = "<html><body>123</body></html>";
351   EXPECT_CALL(*classifier, CancelPendingClassification());
352   LoadURL("javascript:location.replace(\'redir\');");
353   Mock::VerifyAndClearExpectations(classifier);
354   OnStartPhishingDetection(delegate, GURL("http://host4.com/redir"));
355   page_text = ASCIIToUTF16("123");
356   {
357     InSequence s;
358     EXPECT_CALL(*classifier, CancelPendingClassification());
359     EXPECT_CALL(*classifier, BeginClassification(Pointee(page_text), _));
360     delegate->PageCaptured(&page_text, false);
361     Mock::VerifyAndClearExpectations(classifier);
362   }
363
364   // The delegate will cancel pending classification on destruction.
365   EXPECT_CALL(*classifier, CancelPendingClassification());
366 }
367
368 TEST_F(PhishingClassifierDelegateTest, IgnorePreliminaryCapture) {
369   // Tests that preliminary PageCaptured notifications are ignored.
370   MockPhishingClassifier* classifier =
371       new StrictMock<MockPhishingClassifier>(view());
372   PhishingClassifierDelegate* delegate =
373       PhishingClassifierDelegate::Create(view(), classifier);
374   MockScorer scorer;
375   delegate->SetPhishingScorer(&scorer);
376   ASSERT_TRUE(classifier->is_ready());
377
378   EXPECT_CALL(*classifier, CancelPendingClassification());
379   responses_["http://host.com/"] = "<html><body>phish</body></html>";
380   LoadURL("http://host.com/");
381   Mock::VerifyAndClearExpectations(classifier);
382   OnStartPhishingDetection(delegate, GURL("http://host.com/"));
383   string16 page_text = ASCIIToUTF16("phish");
384   delegate->PageCaptured(&page_text, true);
385
386   // Once the non-preliminary capture happens, classification should begin.
387   page_text = ASCIIToUTF16("phish");
388   {
389     InSequence s;
390     EXPECT_CALL(*classifier, CancelPendingClassification());
391     EXPECT_CALL(*classifier, BeginClassification(Pointee(page_text), _));
392     delegate->PageCaptured(&page_text, false);
393     Mock::VerifyAndClearExpectations(classifier);
394   }
395
396   // The delegate will cancel pending classification on destruction.
397   EXPECT_CALL(*classifier, CancelPendingClassification());
398 }
399
400 TEST_F(PhishingClassifierDelegateTest, DuplicatePageCapture) {
401   // Tests that a second PageCaptured notification causes classification to
402   // be cancelled.
403   MockPhishingClassifier* classifier =
404       new StrictMock<MockPhishingClassifier>(view());
405   PhishingClassifierDelegate* delegate =
406       PhishingClassifierDelegate::Create(view(), classifier);
407   MockScorer scorer;
408   delegate->SetPhishingScorer(&scorer);
409   ASSERT_TRUE(classifier->is_ready());
410
411   EXPECT_CALL(*classifier, CancelPendingClassification());
412   responses_["http://host.com/"] = "<html><body>phish</body></html>";
413   LoadURL("http://host.com/");
414   Mock::VerifyAndClearExpectations(classifier);
415   OnStartPhishingDetection(delegate, GURL("http://host.com/"));
416   string16 page_text = ASCIIToUTF16("phish");
417   {
418     InSequence s;
419     EXPECT_CALL(*classifier, CancelPendingClassification());
420     EXPECT_CALL(*classifier, BeginClassification(Pointee(page_text), _));
421     delegate->PageCaptured(&page_text, false);
422     Mock::VerifyAndClearExpectations(classifier);
423   }
424
425   page_text = ASCIIToUTF16("phish");
426   EXPECT_CALL(*classifier, CancelPendingClassification());
427   delegate->PageCaptured(&page_text, false);
428   Mock::VerifyAndClearExpectations(classifier);
429
430   // The delegate will cancel pending classification on destruction.
431   EXPECT_CALL(*classifier, CancelPendingClassification());
432 }
433
434 TEST_F(PhishingClassifierDelegateTest, PhishingDetectionDone) {
435   // Tests that a PhishingDetectionDone IPC is sent to the browser
436   // whenever we finish classification.
437   MockPhishingClassifier* classifier =
438       new StrictMock<MockPhishingClassifier>(view());
439   PhishingClassifierDelegate* delegate =
440       PhishingClassifierDelegate::Create(view(), classifier);
441   MockScorer scorer;
442   delegate->SetPhishingScorer(&scorer);
443   ASSERT_TRUE(classifier->is_ready());
444
445   // Start by loading a page to populate the delegate's state.
446   responses_["http://host.com/"] = "<html><body>phish</body></html>";
447   EXPECT_CALL(*classifier, CancelPendingClassification());
448   LoadURL("http://host.com/#a");
449   Mock::VerifyAndClearExpectations(classifier);
450   string16 page_text = ASCIIToUTF16("phish");
451   OnStartPhishingDetection(delegate, GURL("http://host.com/#a"));
452   {
453     InSequence s;
454     EXPECT_CALL(*classifier, CancelPendingClassification());
455     EXPECT_CALL(*classifier, BeginClassification(Pointee(page_text), _));
456     delegate->PageCaptured(&page_text, false);
457     Mock::VerifyAndClearExpectations(classifier);
458   }
459
460   // Now run the callback to simulate the classifier finishing.
461   ClientPhishingRequest verdict;
462   verdict.set_url("http://host.com/#a");
463   verdict.set_client_score(0.8f);
464   verdict.set_is_phishing(false);  // Send IPC even if site is not phishing.
465   RunClassificationDone(delegate, verdict);
466   ASSERT_TRUE(verdict_.get());
467   EXPECT_EQ(verdict.SerializeAsString(), verdict_->SerializeAsString());
468
469   // The delegate will cancel pending classification on destruction.
470   EXPECT_CALL(*classifier, CancelPendingClassification());
471 }
472
473 }  // namespace safe_browsing