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#include "chrome/browser/browsing_data/browsing_data_cookie_helper.h"
6
7#include "base/bind.h"
8#include "base/run_loop.h"
9#include "chrome/test/base/testing_profile.h"
10#include "content/public/test/test_browser_thread_bundle.h"
11#include "net/cookies/canonical_cookie.h"
12#include "net/cookies/parsed_cookie.h"
13#include "net/url_request/url_request_context_getter.h"
14#include "testing/gtest/include/gtest/gtest.h"
15
16namespace {
17
18// Test expectations for a given cookie.
19class CookieExpectation {
20 public:
21  CookieExpectation() : matched_(false) {}
22
23  bool MatchesCookie(const net::CanonicalCookie& cookie) const {
24    if (!source_.empty() && source_ != cookie.Source())
25      return false;
26    if (!domain_.empty() && domain_ != cookie.Domain())
27      return false;
28    if (!path_.empty() && path_ != cookie.Path())
29      return false;
30    if (!name_.empty() && name_ != cookie.Name())
31      return false;
32    if (!value_.empty() && value_ != cookie.Value())
33      return false;
34    return true;
35  }
36
37  std::string source_;
38  std::string domain_;
39  std::string path_;
40  std::string name_;
41  std::string value_;
42  bool matched_;
43};
44
45// Matches a CookieExpectation against a Cookie.
46class CookieMatcher {
47 public:
48  explicit CookieMatcher(const net::CanonicalCookie& cookie)
49      : cookie_(cookie) {}
50  bool operator()(const CookieExpectation& expectation) {
51    return expectation.MatchesCookie(cookie_);
52  }
53  net::CanonicalCookie cookie_;
54};
55
56// Unary predicate to determine whether an expectation has been matched.
57bool ExpectationIsMatched(const CookieExpectation& expectation) {
58  return expectation.matched_;
59}
60
61class BrowsingDataCookieHelperTest : public testing::Test {
62 public:
63  BrowsingDataCookieHelperTest()
64      : testing_profile_(new TestingProfile()) {
65  }
66
67  virtual void SetUp() OVERRIDE { cookie_expectations_.clear(); }
68
69  // Adds an expectation for a cookie that satisfies the given parameters.
70  void AddCookieExpectation(const char* source,
71                            const char* domain,
72                            const char* path,
73                            const char* name,
74                            const char* value) {
75    CookieExpectation matcher;
76    if (source)
77      matcher.source_ = source;
78    if (domain)
79      matcher.domain_ = domain;
80    if (path)
81      matcher.path_ = path;
82    if (name)
83      matcher.name_ = name;
84    if (value)
85      matcher.value_ = value;
86    cookie_expectations_.push_back(matcher);
87  }
88
89  // Checks the existing expectations, and then clears all existing
90  // expectations.
91  void CheckCookieExpectations() {
92    ASSERT_EQ(cookie_expectations_.size(), cookie_list_.size());
93
94    // For each cookie, look for a matching expectation.
95    for (net::CookieList::iterator it = cookie_list_.begin();
96         it != cookie_list_.end();
97         ++it) {
98      CookieMatcher matcher(*it);
99      std::vector<CookieExpectation>::iterator match = std::find_if(
100          cookie_expectations_.begin(), cookie_expectations_.end(), matcher);
101      if (match != cookie_expectations_.end())
102        match->matched_ = true;
103    }
104
105    // Check that each expectation has been matched.
106    unsigned long match_count = std::count_if(cookie_expectations_.begin(),
107                                              cookie_expectations_.end(),
108                                              ExpectationIsMatched);
109    EXPECT_EQ(cookie_expectations_.size(), match_count);
110
111    cookie_expectations_.clear();
112  }
113
114  void CreateCookiesForTest() {
115    scoped_refptr<net::CookieMonster> cookie_monster =
116        testing_profile_->GetCookieMonster();
117    cookie_monster->SetCookieWithOptionsAsync(
118        GURL("http://www.google.com"), "A=1", net::CookieOptions(),
119        net::CookieMonster::SetCookiesCallback());
120    cookie_monster->SetCookieWithOptionsAsync(
121        GURL("http://www.gmail.google.com"), "B=1", net::CookieOptions(),
122        net::CookieMonster::SetCookiesCallback());
123  }
124
125  void CreateCookiesForDomainCookieTest() {
126    scoped_refptr<net::CookieMonster> cookie_monster =
127        testing_profile_->GetCookieMonster();
128    cookie_monster->SetCookieWithOptionsAsync(
129        GURL("http://www.google.com"), "A=1", net::CookieOptions(),
130        net::CookieMonster::SetCookiesCallback());
131    cookie_monster->SetCookieWithOptionsAsync(
132        GURL("http://www.google.com"), "A=2; Domain=.www.google.com ",
133        net::CookieOptions(), net::CookieMonster::SetCookiesCallback());
134  }
135
136  void FetchCallback(const net::CookieList& cookies) {
137    cookie_list_ = cookies;
138
139    AddCookieExpectation(NULL, "www.google.com", NULL, "A", NULL);
140    AddCookieExpectation(NULL, "www.gmail.google.com", NULL, "B", NULL);
141    CheckCookieExpectations();
142  }
143
144  void DomainCookieCallback(const net::CookieList& cookies) {
145    cookie_list_ = cookies;
146
147    AddCookieExpectation(NULL, "www.google.com", NULL, "A", "1");
148    AddCookieExpectation(NULL, ".www.google.com", NULL, "A", "2");
149    CheckCookieExpectations();
150  }
151
152  void DeleteCallback(const net::CookieList& cookies) {
153    cookie_list_ = cookies;
154    AddCookieExpectation(NULL, "www.gmail.google.com", NULL, "B", NULL);
155    CheckCookieExpectations();
156  }
157
158  void CannedUniqueCallback(const net::CookieList& cookies) {
159    cookie_list_ = cookies;
160    AddCookieExpectation(
161        "http://www.google.com/", "www.google.com", "/", "A", NULL);
162    CheckCookieExpectations();
163  }
164
165  void CannedReplaceCookieCallback(const net::CookieList& cookies) {
166    cookie_list_ = cookies;
167    AddCookieExpectation(
168        "http://www.google.com/", "www.google.com", "/", "A", "2");
169    AddCookieExpectation(
170        "http://www.google.com/", "www.google.com", "/example/0", "A", "4");
171    AddCookieExpectation(
172        "http://www.google.com/", ".google.com", "/", "A", "6");
173    AddCookieExpectation(
174        "http://www.google.com/", ".google.com", "/example/1", "A", "8");
175    AddCookieExpectation(
176        "http://www.google.com/", ".www.google.com", "/", "A", "10");
177    CheckCookieExpectations();
178  }
179
180  void CannedDomainCookieCallback(const net::CookieList& cookies) {
181    cookie_list_ = cookies;
182    AddCookieExpectation(
183        "http://www.google.com/", "www.google.com", NULL, "A", NULL);
184    AddCookieExpectation(
185        "http://www.google.com/", ".www.google.com", NULL, "A", NULL);
186    CheckCookieExpectations();
187  }
188
189  void CannedDifferentFramesCallback(const net::CookieList& cookie_list) {
190    ASSERT_EQ(3U, cookie_list.size());
191  }
192
193  void DeleteCookie(BrowsingDataCookieHelper* helper, const GURL origin) {
194    for (net::CookieList::iterator it = cookie_list_.begin();
195         it != cookie_list_.end();
196         ++it) {
197      if (it->Source() == net::CanonicalCookie::GetCookieSourceFromURL(origin))
198        helper->DeleteCookie(*it);
199    }
200  }
201
202 protected:
203  content::TestBrowserThreadBundle thread_bundle_;
204  scoped_ptr<TestingProfile> testing_profile_;
205
206  std::vector<CookieExpectation> cookie_expectations_;
207  net::CookieList cookie_list_;
208};
209
210TEST_F(BrowsingDataCookieHelperTest, FetchData) {
211  CreateCookiesForTest();
212  scoped_refptr<BrowsingDataCookieHelper> cookie_helper(
213      new BrowsingDataCookieHelper(testing_profile_->GetRequestContext()));
214
215  cookie_helper->StartFetching(
216      base::Bind(&BrowsingDataCookieHelperTest::FetchCallback,
217                 base::Unretained(this)));
218  base::RunLoop().RunUntilIdle();
219}
220
221TEST_F(BrowsingDataCookieHelperTest, DomainCookie) {
222  CreateCookiesForDomainCookieTest();
223  scoped_refptr<BrowsingDataCookieHelper> cookie_helper(
224      new BrowsingDataCookieHelper(testing_profile_->GetRequestContext()));
225
226  cookie_helper->StartFetching(
227      base::Bind(&BrowsingDataCookieHelperTest::DomainCookieCallback,
228                 base::Unretained(this)));
229  base::RunLoop().RunUntilIdle();
230}
231
232TEST_F(BrowsingDataCookieHelperTest, DeleteCookie) {
233  CreateCookiesForTest();
234  scoped_refptr<BrowsingDataCookieHelper> cookie_helper(
235      new BrowsingDataCookieHelper(testing_profile_->GetRequestContext()));
236
237  cookie_helper->StartFetching(
238      base::Bind(&BrowsingDataCookieHelperTest::FetchCallback,
239                 base::Unretained(this)));
240  base::RunLoop().RunUntilIdle();
241
242  net::CanonicalCookie cookie = cookie_list_[0];
243  cookie_helper->DeleteCookie(cookie);
244
245  cookie_helper->StartFetching(
246      base::Bind(&BrowsingDataCookieHelperTest::DeleteCallback,
247                 base::Unretained(this)));
248  base::RunLoop().RunUntilIdle();
249}
250
251TEST_F(BrowsingDataCookieHelperTest, CannedDeleteCookie) {
252  CreateCookiesForTest();
253  scoped_refptr<CannedBrowsingDataCookieHelper> helper(
254      new CannedBrowsingDataCookieHelper(
255          testing_profile_->GetRequestContext()));
256
257  ASSERT_TRUE(helper->empty());
258
259  const GURL origin1("http://www.google.com");
260  const GURL origin2("http://www.gmail.google.com");
261  helper->AddChangedCookie(origin1, origin1, "A=1", net::CookieOptions());
262  helper->AddChangedCookie(origin2, origin2, "B=1", net::CookieOptions());
263
264  helper->StartFetching(
265      base::Bind(&BrowsingDataCookieHelperTest::FetchCallback,
266                 base::Unretained(this)));
267  base::RunLoop().RunUntilIdle();
268
269  EXPECT_EQ(2u, helper->GetCookieCount());
270
271  DeleteCookie(helper.get(), origin1);
272
273  EXPECT_EQ(1u, helper->GetCookieCount());
274  helper->StartFetching(
275      base::Bind(&BrowsingDataCookieHelperTest::DeleteCallback,
276                 base::Unretained(this)));
277  base::RunLoop().RunUntilIdle();
278}
279
280TEST_F(BrowsingDataCookieHelperTest, CannedDomainCookie) {
281  const GURL origin("http://www.google.com");
282  net::CookieList cookie;
283
284  scoped_refptr<CannedBrowsingDataCookieHelper> helper(
285      new CannedBrowsingDataCookieHelper(
286          testing_profile_->GetRequestContext()));
287
288  ASSERT_TRUE(helper->empty());
289  helper->AddChangedCookie(origin, origin, "A=1", net::CookieOptions());
290  helper->AddChangedCookie(origin, origin, "A=1; Domain=.www.google.com",
291                           net::CookieOptions());
292  // Try adding invalid cookies that will be ignored.
293  helper->AddChangedCookie(origin, origin, std::string(), net::CookieOptions());
294  helper->AddChangedCookie(origin,
295                           origin,
296                           "C=bad guy; Domain=wrongdomain.com",
297                           net::CookieOptions());
298
299  helper->StartFetching(
300      base::Bind(&BrowsingDataCookieHelperTest::CannedDomainCookieCallback,
301                 base::Unretained(this)));
302  cookie = cookie_list_;
303
304  helper->Reset();
305  ASSERT_TRUE(helper->empty());
306
307  helper->AddReadCookies(origin, origin, cookie);
308  helper->StartFetching(
309      base::Bind(&BrowsingDataCookieHelperTest::CannedDomainCookieCallback,
310                 base::Unretained(this)));
311}
312
313TEST_F(BrowsingDataCookieHelperTest, CannedUnique) {
314  const GURL origin("http://www.google.com");
315  net::CookieList cookie;
316
317  scoped_refptr<CannedBrowsingDataCookieHelper> helper(
318      new CannedBrowsingDataCookieHelper(
319          testing_profile_->GetRequestContext()));
320
321  ASSERT_TRUE(helper->empty());
322  helper->AddChangedCookie(origin, origin, "A=1", net::CookieOptions());
323  helper->AddChangedCookie(origin, origin, "A=1", net::CookieOptions());
324  helper->StartFetching(
325      base::Bind(&BrowsingDataCookieHelperTest::CannedUniqueCallback,
326                 base::Unretained(this)));
327
328  cookie = cookie_list_;
329  helper->Reset();
330  ASSERT_TRUE(helper->empty());
331
332  helper->AddReadCookies(origin, origin, cookie);
333  helper->AddReadCookies(origin, origin, cookie);
334  helper->StartFetching(
335      base::Bind(&BrowsingDataCookieHelperTest::CannedUniqueCallback,
336                 base::Unretained(this)));
337}
338
339TEST_F(BrowsingDataCookieHelperTest, CannedReplaceCookie) {
340  const GURL origin("http://www.google.com");
341  net::CookieList cookie;
342
343  scoped_refptr<CannedBrowsingDataCookieHelper> helper(
344      new CannedBrowsingDataCookieHelper(
345          testing_profile_->GetRequestContext()));
346
347  ASSERT_TRUE(helper->empty());
348  helper->AddChangedCookie(origin, origin, "A=1", net::CookieOptions());
349  helper->AddChangedCookie(origin, origin, "A=2", net::CookieOptions());
350  helper->AddChangedCookie(origin, origin, "A=3; Path=/example/0",
351                           net::CookieOptions());
352  helper->AddChangedCookie(origin, origin, "A=4; Path=/example/0",
353                           net::CookieOptions());
354  helper->AddChangedCookie(origin, origin, "A=5; Domain=google.com",
355                           net::CookieOptions());
356  helper->AddChangedCookie(origin, origin, "A=6; Domain=google.com",
357                           net::CookieOptions());
358  helper->AddChangedCookie(origin, origin,
359                           "A=7; Domain=google.com; Path=/example/1",
360                           net::CookieOptions());
361  helper->AddChangedCookie(origin, origin,
362                           "A=8; Domain=google.com; Path=/example/1",
363                           net::CookieOptions());
364
365  helper->AddChangedCookie(origin, origin,
366                          "A=9; Domain=www.google.com",
367                           net::CookieOptions());
368  helper->AddChangedCookie(origin, origin,
369                           "A=10; Domain=www.google.com",
370                           net::CookieOptions());
371
372  helper->StartFetching(
373      base::Bind(&BrowsingDataCookieHelperTest::CannedReplaceCookieCallback,
374                 base::Unretained(this)));
375
376  cookie = cookie_list_;
377  helper->Reset();
378  ASSERT_TRUE(helper->empty());
379
380  helper->AddReadCookies(origin, origin, cookie);
381  helper->AddReadCookies(origin, origin, cookie);
382  helper->StartFetching(
383      base::Bind(&BrowsingDataCookieHelperTest::CannedReplaceCookieCallback,
384                 base::Unretained(this)));
385}
386
387TEST_F(BrowsingDataCookieHelperTest, CannedEmpty) {
388  const GURL url_google("http://www.google.com");
389
390  scoped_refptr<CannedBrowsingDataCookieHelper> helper(
391      new CannedBrowsingDataCookieHelper(
392          testing_profile_->GetRequestContext()));
393
394  ASSERT_TRUE(helper->empty());
395  helper->AddChangedCookie(url_google, url_google, "a=1",
396                          net::CookieOptions());
397  ASSERT_FALSE(helper->empty());
398  helper->Reset();
399  ASSERT_TRUE(helper->empty());
400
401  net::CookieList cookies;
402  net::ParsedCookie pc("a=1");
403  scoped_ptr<net::CanonicalCookie> cookie(
404      new net::CanonicalCookie(url_google, pc));
405  cookies.push_back(*cookie);
406
407  helper->AddReadCookies(url_google, url_google, cookies);
408  ASSERT_FALSE(helper->empty());
409  helper->Reset();
410  ASSERT_TRUE(helper->empty());
411}
412
413TEST_F(BrowsingDataCookieHelperTest, CannedDifferentFrames) {
414  GURL frame1_url("http://www.google.com");
415  GURL frame2_url("http://www.google.de");
416  GURL request_url("http://www.google.com");
417
418  scoped_refptr<CannedBrowsingDataCookieHelper> helper(
419      new CannedBrowsingDataCookieHelper(
420          testing_profile_->GetRequestContext()));
421
422  ASSERT_TRUE(helper->empty());
423  helper->AddChangedCookie(frame1_url, request_url, "a=1",
424                           net::CookieOptions());
425  helper->AddChangedCookie(frame1_url, request_url, "b=1",
426                           net::CookieOptions());
427  helper->AddChangedCookie(frame2_url, request_url, "c=1",
428                           net::CookieOptions());
429
430  helper->StartFetching(
431      base::Bind(&BrowsingDataCookieHelperTest::CannedDifferentFramesCallback,
432                 base::Unretained(this)));
433}
434
435TEST_F(BrowsingDataCookieHelperTest, CannedGetCookieCount) {
436  // The URL in the omnibox is a frame URL. This is not necessarily the request
437  // URL, since websites usually include other resources.
438  GURL frame1_url("http://www.google.com");
439  GURL frame2_url("http://www.google.de");
440  // The request URL used for all cookies that are added to the |helper|.
441  GURL request1_url("http://static.google.com/foo/res1.html");
442  GURL request2_url("http://static.google.com/bar/res2.html");
443  std::string cookie_domain(".www.google.com");
444  std::string cookie_pair1("A=1");
445  std::string cookie_pair2("B=1");
446  // The cookie pair used for adding a cookie that overrides the cookie created
447  // with |cookie_pair1|. The cookie-name of |cookie_pair3| must match the
448  // cookie-name of |cookie-pair1|.
449  std::string cookie_pair3("A=2");
450  // The cookie pair used for adding a non host-only cookie. The cookie-name
451  // must match the cookie-name of |cookie_pair1| in order to add a host-only
452  // and a non host-only cookie with the same name below.
453  std::string cookie_pair4("A=3");
454
455  scoped_refptr<CannedBrowsingDataCookieHelper> helper(
456      new CannedBrowsingDataCookieHelper(
457          testing_profile_->GetRequestContext()));
458
459  // Add two different cookies (distinguished by the tuple [cookie-name,
460  // domain-value, path-value]) for a HTTP request to |frame1_url| and verify
461  // that the cookie count is increased to two. The set-cookie-string consists
462  // only of the cookie-pair. This means that the host and the default-path of
463  // the |request_url| are used as domain-value and path-value for the added
464  // cookies.
465  EXPECT_EQ(0U, helper->GetCookieCount());
466  helper->AddChangedCookie(frame1_url, frame1_url, cookie_pair1,
467                           net::CookieOptions());
468  EXPECT_EQ(1U, helper->GetCookieCount());
469  helper->AddChangedCookie(frame1_url, frame1_url, cookie_pair2,
470                           net::CookieOptions());
471  EXPECT_EQ(2U, helper->GetCookieCount());
472
473  // Use a different frame URL for adding another cookie that will replace one
474  // of the previously added cookies. This could happen during an automatic
475  // redirect e.g. |frame1_url| redirects to |frame2_url| and a cookie set by a
476  // request to |frame1_url| is updated.
477  helper->AddChangedCookie(frame2_url, frame1_url, cookie_pair3,
478                           net::CookieOptions());
479  EXPECT_EQ(2U, helper->GetCookieCount());
480
481  // Add two more cookies that are set while loading resources. The two cookies
482  // below have a differnt path-value since the request URLs have different
483  // paths.
484  helper->AddChangedCookie(frame2_url, request1_url, cookie_pair3,
485                           net::CookieOptions());
486  EXPECT_EQ(3U, helper->GetCookieCount());
487  helper->AddChangedCookie(frame2_url, request2_url, cookie_pair3,
488                           net::CookieOptions());
489  EXPECT_EQ(4U, helper->GetCookieCount());
490
491  // Host-only and domain cookies are treated as seperate items. This means that
492  // the following two cookie-strings are stored as two separate cookies, even
493  // though they have the same name and are send with the same request:
494  //   "A=1;
495  //   "A=3; Domain=www.google.com"
496  // Add a domain cookie and check if it increases the cookie count.
497  helper->AddChangedCookie(frame2_url, frame1_url,
498                           cookie_pair4 + "; Domain=" + cookie_domain,
499                           net::CookieOptions());
500  EXPECT_EQ(5U, helper->GetCookieCount());
501}
502
503}  // namespace
504