cookies_tree_model.cc revision c407dc5cd9bdc5668497f21b26b09d988ab439de
1// Copyright (c) 2010 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/cookies_tree_model.h"
6
7#include <algorithm>
8#include <functional>
9#include <vector>
10
11#include "app/l10n_util.h"
12#include "app/resource_bundle.h"
13#include "app/table_model_observer.h"
14#include "app/tree_node_model.h"
15#include "base/callback.h"
16#include "base/i18n/rtl.h"
17#include "base/linked_ptr.h"
18#include "base/string_util.h"
19#include "chrome/browser/extensions/extensions_service.h"
20#include "chrome/browser/in_process_webkit/webkit_context.h"
21#include "grit/app_resources.h"
22#include "grit/generated_resources.h"
23#include "grit/theme_resources.h"
24#include "net/base/cookie_monster.h"
25#include "net/base/registry_controlled_domain.h"
26#include "net/url_request/url_request_context.h"
27#include "third_party/skia/include/core/SkBitmap.h"
28
29static const char kFileOriginNodeName[] = "file://";
30
31///////////////////////////////////////////////////////////////////////////////
32// CookieTreeNode, public:
33
34void CookieTreeNode::DeleteStoredObjects() {
35  std::for_each(children().begin(),
36                children().end(),
37                std::mem_fun(&CookieTreeNode::DeleteStoredObjects));
38}
39
40CookiesTreeModel* CookieTreeNode::GetModel() const {
41  if (GetParent())
42    return GetParent()->GetModel();
43  else
44    return NULL;
45}
46
47///////////////////////////////////////////////////////////////////////////////
48// CookieTreeCookieNode, public:
49
50CookieTreeCookieNode::CookieTreeCookieNode(
51    net::CookieMonster::CanonicalCookie* cookie)
52    : CookieTreeNode(UTF8ToWide(cookie->Name())),
53      cookie_(cookie) {
54}
55
56void CookieTreeCookieNode::DeleteStoredObjects() {
57  // notify CookieMonster that we should delete this cookie
58  // We have stored a copy of all the cookies in the model, and our model is
59  // never re-calculated. Thus, we just need to delete the nodes from our
60  // model, and tell CookieMonster to delete the cookies. We can keep the
61  // vector storing the cookies in-tact and not delete from there (that would
62  // invalidate our pointers), and the fact that it contains semi out-of-date
63  // data is not problematic as we don't re-build the model based on that.
64  GetModel()->cookie_monster_->
65      DeleteCookie(cookie_->Domain(), *cookie_, true);
66}
67
68namespace {
69// comparison functor, for use in CookieTreeRootNode
70class OriginNodeComparator {
71 public:
72  bool operator() (const CookieTreeNode* lhs,
73                   const CookieTreeNode* rhs) {
74    // We want to order by registry controlled domain, so we would get
75    // google.com, ad.google.com, www.google.com,
76    // microsoft.com, ad.microsoft.com. CanonicalizeHost transforms the origins
77    // into a form like google.com.www so that string comparisons work.
78    return (CanonicalizeHost(lhs->GetTitleAsString16()) <
79            CanonicalizeHost(rhs->GetTitleAsString16()));
80  }
81
82 private:
83  static std::string CanonicalizeHost(const string16& host16) {
84    // The canonicalized representation makes the registry controlled domain
85    // come first, and then adds subdomains in reverse order, e.g.
86    // 1.mail.google.com would become google.com.mail.1, and then a standard
87    // string comparison works to order hosts by registry controlled domain
88    // first. Leading dots are ignored, ".google.com" is the same as
89    // "google.com".
90
91    std::string host = UTF16ToUTF8(host16);
92    std::string retval = net::RegistryControlledDomainService::
93        GetDomainAndRegistry(host);
94    if (!retval.length())  // Is an IP address or other special origin.
95      return host;
96
97    std::string::size_type position = host.rfind(retval);
98
99    // The host may be the registry controlled domain, in which case fail fast.
100    if (position == 0 || position == std::string::npos)
101      return host;
102
103    // If host is www.google.com, retval will contain google.com at this point.
104    // Start operating to the left of the registry controlled domain, e.g. in
105    // the www.google.com example, start at index 3.
106    --position;
107
108    // If position == 0, that means it's a dot; this will be ignored to treat
109    // ".google.com" the same as "google.com".
110    while (position > 0) {
111      retval += std::string(".");
112      // Copy up to the next dot. host[position] is a dot so start after it.
113      std::string::size_type next_dot = host.rfind(".", position - 1);
114      if (next_dot == std::string::npos) {
115        retval += host.substr(0, position);
116        break;
117      }
118      retval += host.substr(next_dot + 1, position - (next_dot + 1));
119      position = next_dot;
120    }
121    return retval;
122  }
123};
124
125}  // namespace
126
127///////////////////////////////////////////////////////////////////////////////
128// CookieTreeAppCacheNode, public:
129
130CookieTreeAppCacheNode::CookieTreeAppCacheNode(
131    const appcache::AppCacheInfo* appcache_info)
132    : CookieTreeNode(UTF8ToWide(appcache_info->manifest_url.spec())),
133      appcache_info_(appcache_info) {
134}
135
136void CookieTreeAppCacheNode::DeleteStoredObjects() {
137  DCHECK(GetModel()->appcache_helper_);
138  GetModel()->appcache_helper_->DeleteAppCacheGroup(
139      appcache_info_->manifest_url);
140}
141
142///////////////////////////////////////////////////////////////////////////////
143// CookieTreeDatabaseNode, public:
144
145CookieTreeDatabaseNode::CookieTreeDatabaseNode(
146    BrowsingDataDatabaseHelper::DatabaseInfo* database_info)
147    : CookieTreeNode(database_info->database_name.empty() ?
148          l10n_util::GetString(IDS_COOKIES_WEB_DATABASE_UNNAMED_NAME) :
149          UTF8ToWide(database_info->database_name)),
150      database_info_(database_info) {
151}
152
153void CookieTreeDatabaseNode::DeleteStoredObjects() {
154  GetModel()->database_helper_->DeleteDatabase(
155      database_info_->origin_identifier, database_info_->database_name);
156}
157
158///////////////////////////////////////////////////////////////////////////////
159// CookieTreeLocalStorageNode, public:
160
161CookieTreeLocalStorageNode::CookieTreeLocalStorageNode(
162    BrowsingDataLocalStorageHelper::LocalStorageInfo* local_storage_info)
163    : CookieTreeNode(UTF8ToWide(
164          local_storage_info->origin.empty() ?
165              local_storage_info->database_identifier :
166              local_storage_info->origin)),
167      local_storage_info_(local_storage_info) {
168}
169
170void CookieTreeLocalStorageNode::DeleteStoredObjects() {
171  GetModel()->local_storage_helper_->DeleteLocalStorageFile(
172      local_storage_info_->file_path);
173}
174
175///////////////////////////////////////////////////////////////////////////////
176// CookieTreeRootNode, public:
177
178CookieTreeOriginNode* CookieTreeRootNode::GetOrCreateOriginNode(
179    const GURL& url) {
180  CookieTreeOriginNode origin_node(url);
181
182  // First see if there is an existing match.
183  std::vector<CookieTreeNode*>::iterator origin_node_iterator =
184      lower_bound(children().begin(),
185                  children().end(),
186                  &origin_node,
187                  OriginNodeComparator());
188
189  if (origin_node_iterator != children().end() &&
190      CookieTreeOriginNode::TitleForUrl(url) ==
191      (*origin_node_iterator)->GetTitle())
192    return static_cast<CookieTreeOriginNode*>(*origin_node_iterator);
193  // Node doesn't exist, create a new one and insert it into the (ordered)
194  // children.
195  CookieTreeOriginNode* retval = new CookieTreeOriginNode(url);
196  DCHECK(model_);
197  model_->Add(this, (origin_node_iterator - children().begin()), retval);
198  return retval;
199}
200
201///////////////////////////////////////////////////////////////////////////////
202// CookieTreeOriginNode, public:
203
204// static
205std::wstring CookieTreeOriginNode::TitleForUrl(
206    const GURL& url) {
207  return UTF8ToWide(url.SchemeIsFile() ? kFileOriginNodeName : url.host());
208}
209
210CookieTreeOriginNode::CookieTreeOriginNode(const GURL& url)
211    : CookieTreeNode(TitleForUrl(url)),
212      cookies_child_(NULL),
213      databases_child_(NULL),
214      local_storages_child_(NULL),
215      appcaches_child_(NULL),
216      url_(url) {}
217
218
219CookieTreeCookiesNode* CookieTreeOriginNode::GetOrCreateCookiesNode() {
220  if (cookies_child_)
221    return cookies_child_;
222  cookies_child_ = new CookieTreeCookiesNode;
223  AddChildSortedByTitle(cookies_child_);
224  return cookies_child_;
225}
226
227CookieTreeDatabasesNode* CookieTreeOriginNode::GetOrCreateDatabasesNode() {
228  if (databases_child_)
229    return databases_child_;
230  databases_child_ = new CookieTreeDatabasesNode;
231  AddChildSortedByTitle(databases_child_);
232  return databases_child_;
233}
234
235CookieTreeLocalStoragesNode*
236    CookieTreeOriginNode::GetOrCreateLocalStoragesNode() {
237  if (local_storages_child_)
238    return local_storages_child_;
239  local_storages_child_ = new CookieTreeLocalStoragesNode;
240  AddChildSortedByTitle(local_storages_child_);
241  return local_storages_child_;
242}
243
244CookieTreeAppCachesNode* CookieTreeOriginNode::GetOrCreateAppCachesNode() {
245  if (appcaches_child_)
246    return appcaches_child_;
247  appcaches_child_ = new CookieTreeAppCachesNode;
248  AddChildSortedByTitle(appcaches_child_);
249  return appcaches_child_;
250}
251
252void CookieTreeOriginNode::CreateContentException(
253    HostContentSettingsMap* content_settings, ContentSetting setting) const {
254  if (CanCreateContentException()) {
255    content_settings->AddExceptionForURL(url_,
256                                         CONTENT_SETTINGS_TYPE_COOKIES,
257                                         setting);
258  }
259}
260
261bool CookieTreeOriginNode::CanCreateContentException() const {
262  return !url_.SchemeIsFile();
263}
264
265///////////////////////////////////////////////////////////////////////////////
266// CookieTreeCookiesNode, public:
267
268CookieTreeCookiesNode::CookieTreeCookiesNode()
269    : CookieTreeNode(l10n_util::GetString(IDS_COOKIES_COOKIES)) {
270}
271
272///////////////////////////////////////////////////////////////////////////////
273// CookieTreeAppCachesNode, public:
274
275CookieTreeAppCachesNode::CookieTreeAppCachesNode()
276    : CookieTreeNode(l10n_util::GetString(IDS_COOKIES_APPLICATION_CACHES)) {
277}
278
279///////////////////////////////////////////////////////////////////////////////
280// CookieTreeDatabasesNode, public:
281
282CookieTreeDatabasesNode::CookieTreeDatabasesNode()
283    : CookieTreeNode(l10n_util::GetString(IDS_COOKIES_WEB_DATABASES)) {
284}
285
286///////////////////////////////////////////////////////////////////////////////
287// CookieTreeLocalStoragesNode, public:
288
289CookieTreeLocalStoragesNode::CookieTreeLocalStoragesNode()
290    : CookieTreeNode(l10n_util::GetString(IDS_COOKIES_LOCAL_STORAGE)) {
291}
292
293///////////////////////////////////////////////////////////////////////////////
294// CookieTreeNode, protected
295
296bool CookieTreeNode::NodeTitleComparator::operator() (
297    const CookieTreeNode* lhs, const CookieTreeNode* rhs) {
298  const CookieTreeNode* left =
299      static_cast<const CookieTreeNode*>(lhs);
300  const CookieTreeNode* right =
301      static_cast<const CookieTreeNode*>(rhs);
302  return (left->GetTitleAsString16() < right->GetTitleAsString16());
303}
304
305void CookieTreeNode::AddChildSortedByTitle(CookieTreeNode* new_child) {
306  std::vector<CookieTreeNode*>::iterator iter =
307      lower_bound(children().begin(),
308                  children().end(),
309                  new_child,
310                  NodeTitleComparator());
311  GetModel()->Add(this, iter - children().begin(), new_child);
312}
313
314///////////////////////////////////////////////////////////////////////////////
315// CookiesTreeModel, public:
316
317CookiesTreeModel::CookiesTreeModel(
318    net::CookieMonster* cookie_monster,
319    BrowsingDataDatabaseHelper* database_helper,
320    BrowsingDataLocalStorageHelper* local_storage_helper,
321    BrowsingDataAppCacheHelper* appcache_helper)
322    : ALLOW_THIS_IN_INITIALIZER_LIST(TreeNodeModel<CookieTreeNode>(
323          new CookieTreeRootNode(this))),
324      cookie_monster_(cookie_monster),
325      appcache_helper_(appcache_helper),
326      database_helper_(database_helper),
327      local_storage_helper_(local_storage_helper),
328      batch_update_(0) {
329  LoadCookies();
330  DCHECK(database_helper_);
331  database_helper_->StartFetching(NewCallback(
332      this, &CookiesTreeModel::OnDatabaseModelInfoLoaded));
333  DCHECK(local_storage_helper_);
334  local_storage_helper_->StartFetching(NewCallback(
335      this, &CookiesTreeModel::OnStorageModelInfoLoaded));
336
337  // TODO(michaeln): when all of the ui impls have been updated,
338  // make this a required parameter.
339  if (appcache_helper_) {
340    appcache_helper_->StartFetching(NewCallback(
341        this, &CookiesTreeModel::OnAppCacheModelInfoLoaded));
342  }
343}
344
345CookiesTreeModel::~CookiesTreeModel() {
346  database_helper_->CancelNotification();
347  local_storage_helper_->CancelNotification();
348  if (appcache_helper_)
349    appcache_helper_->CancelNotification();
350}
351
352///////////////////////////////////////////////////////////////////////////////
353// CookiesTreeModel, TreeModel methods (public):
354
355// TreeModel methods:
356// Returns the set of icons for the nodes in the tree. You only need override
357// this if you don't want to use the default folder icons.
358void CookiesTreeModel::GetIcons(std::vector<SkBitmap>* icons) {
359  icons->push_back(*ResourceBundle::GetSharedInstance().GetBitmapNamed(
360      IDR_DEFAULT_FAVICON));
361  icons->push_back(*ResourceBundle::GetSharedInstance().GetBitmapNamed(
362      IDR_COOKIE_ICON));
363  icons->push_back(*ResourceBundle::GetSharedInstance().GetBitmapNamed(
364      IDR_COOKIE_STORAGE_ICON));
365}
366
367// Returns the index of the icon to use for |node|. Return -1 to use the
368// default icon. The index is relative to the list of icons returned from
369// GetIcons.
370int CookiesTreeModel::GetIconIndex(TreeModelNode* node) {
371  CookieTreeNode* ct_node = static_cast<CookieTreeNode*>(node);
372  switch (ct_node->GetDetailedInfo().node_type) {
373    case CookieTreeNode::DetailedInfo::TYPE_ORIGIN:
374      return ORIGIN;
375      break;
376    case CookieTreeNode::DetailedInfo::TYPE_COOKIE:
377      return COOKIE;
378      break;
379    case CookieTreeNode::DetailedInfo::TYPE_DATABASE:
380      return DATABASE;
381      break;
382    case CookieTreeNode::DetailedInfo::TYPE_LOCAL_STORAGE:
383      return DATABASE;  // close enough
384      break;
385    case CookieTreeNode::DetailedInfo::TYPE_APPCACHE:
386      return DATABASE;  // ditto
387      break;
388    default:
389      return -1;
390  }
391}
392
393void CookiesTreeModel::LoadCookies() {
394  LoadCookiesWithFilter(std::wstring());
395}
396
397void CookiesTreeModel::LoadCookiesWithFilter(const std::wstring& filter) {
398  // mmargh mmargh mmargh!
399
400  all_cookies_ = cookie_monster_->GetAllCookies();
401  CookieTreeRootNode* root = static_cast<CookieTreeRootNode*>(GetRoot());
402  for (CookieList::iterator it = all_cookies_.begin();
403       it != all_cookies_.end(); ++it) {
404        std::string origin_host = it->Domain();
405    if (origin_host.length() > 1 && origin_host[0] == '.')
406      origin_host = it->Domain().substr(1);
407    // We treat secure cookies just the same as normal ones.
408    GURL origin(std::string(chrome::kHttpScheme) +
409                chrome::kStandardSchemeSeparator + origin_host + "/");
410    if (!filter.size() ||
411        (CookieTreeOriginNode::TitleForUrl(origin).find(filter) !=
412         std::string::npos)) {
413      CookieTreeOriginNode* origin_node =
414          root->GetOrCreateOriginNode(origin);
415      CookieTreeCookiesNode* cookies_node =
416          origin_node->GetOrCreateCookiesNode();
417      CookieTreeCookieNode* new_cookie = new CookieTreeCookieNode(&*it);
418      cookies_node->AddCookieNode(new_cookie);
419    }
420  }
421}
422
423void CookiesTreeModel::DeleteAllStoredObjects() {
424  NotifyObserverBeginBatch();
425  CookieTreeNode* root = GetRoot();
426  root->DeleteStoredObjects();
427  int num_children = root->GetChildCount();
428  for (int i = num_children - 1; i >= 0; --i)
429    delete Remove(root, i);
430  NotifyObserverTreeNodeChanged(root);
431  NotifyObserverEndBatch();
432}
433
434void CookiesTreeModel::DeleteCookieNode(CookieTreeNode* cookie_node) {
435  if (cookie_node == GetRoot())
436    return;
437  cookie_node->DeleteStoredObjects();
438  // find the parent and index
439  CookieTreeNode* parent_node = cookie_node->GetParent();
440  int cookie_node_index = parent_node->IndexOfChild(cookie_node);
441  delete Remove(parent_node, cookie_node_index);
442  if (parent_node->GetChildCount() == 0)
443    DeleteCookieNode(parent_node);
444}
445
446void CookiesTreeModel::UpdateSearchResults(const std::wstring& filter) {
447  CookieTreeNode* root = GetRoot();
448  int num_children = root->GetChildCount();
449  NotifyObserverBeginBatch();
450  for (int i = num_children - 1; i >= 0; --i)
451    delete Remove(root, i);
452  LoadCookiesWithFilter(filter);
453  PopulateDatabaseInfoWithFilter(filter);
454  PopulateLocalStorageInfoWithFilter(filter);
455  PopulateAppCacheInfoWithFilter(filter);
456  NotifyObserverTreeNodeChanged(root);
457  NotifyObserverEndBatch();
458}
459
460void CookiesTreeModel::AddObserver(Observer* observer) {
461  cookies_observer_list_.AddObserver(observer);
462  // Call super so that TreeNodeModel can notify, too.
463  TreeNodeModel<CookieTreeNode>::AddObserver(observer);
464}
465
466void CookiesTreeModel::RemoveObserver(Observer* observer) {
467  cookies_observer_list_.RemoveObserver(observer);
468  // Call super so that TreeNodeModel doesn't have dead pointers.
469  TreeNodeModel<CookieTreeNode>::RemoveObserver(observer);
470}
471
472void CookiesTreeModel::OnAppCacheModelInfoLoaded() {
473  appcache_info_ = appcache_helper_->info_collection();
474  PopulateAppCacheInfoWithFilter(std::wstring());
475}
476
477void CookiesTreeModel::PopulateAppCacheInfoWithFilter(
478    const std::wstring& filter) {
479  using appcache::AppCacheInfo;
480  using appcache::AppCacheInfoVector;
481  typedef std::map<GURL, AppCacheInfoVector> InfoByOrigin;
482
483  if (!appcache_info_ || appcache_info_->infos_by_origin.empty())
484    return;
485
486  CookieTreeRootNode* root = static_cast<CookieTreeRootNode*>(GetRoot());
487  NotifyObserverBeginBatch();
488  for (InfoByOrigin::const_iterator origin =
489           appcache_info_->infos_by_origin.begin();
490       origin != appcache_info_->infos_by_origin.end(); ++origin) {
491    std::wstring origin_node_name = UTF8ToWide(origin->first.host());
492    if (filter.empty() ||
493        (origin_node_name.find(filter) != std::wstring::npos)) {
494      CookieTreeOriginNode* origin_node =
495          root->GetOrCreateOriginNode(origin->first);
496      CookieTreeAppCachesNode* appcaches_node =
497          origin_node->GetOrCreateAppCachesNode();
498
499      for (AppCacheInfoVector::const_iterator info = origin->second.begin();
500           info != origin->second.end(); ++info) {
501        appcaches_node->AddAppCacheNode(
502            new CookieTreeAppCacheNode(&(*info)));
503      }
504    }
505  }
506  NotifyObserverTreeNodeChanged(root);
507  NotifyObserverEndBatch();
508}
509
510void CookiesTreeModel::OnDatabaseModelInfoLoaded(
511    const DatabaseInfoList& database_info) {
512  database_info_list_ = database_info;
513  PopulateDatabaseInfoWithFilter(std::wstring());
514}
515
516void CookiesTreeModel::PopulateDatabaseInfoWithFilter(
517    const std::wstring& filter) {
518  if (database_info_list_.empty())
519    return;
520  CookieTreeRootNode* root = static_cast<CookieTreeRootNode*>(GetRoot());
521  NotifyObserverBeginBatch();
522  for (DatabaseInfoList::iterator database_info = database_info_list_.begin();
523       database_info != database_info_list_.end();
524       ++database_info) {
525    GURL origin(database_info->origin);
526
527    if (!filter.size() ||
528        (CookieTreeOriginNode::TitleForUrl(origin).find(filter) !=
529         std::wstring::npos)) {
530      CookieTreeOriginNode* origin_node =
531          root->GetOrCreateOriginNode(origin);
532      CookieTreeDatabasesNode* databases_node =
533          origin_node->GetOrCreateDatabasesNode();
534      databases_node->AddDatabaseNode(
535          new CookieTreeDatabaseNode(&(*database_info)));
536    }
537  }
538  NotifyObserverTreeNodeChanged(root);
539  NotifyObserverEndBatch();
540}
541
542void CookiesTreeModel::OnStorageModelInfoLoaded(
543    const LocalStorageInfoList& local_storage_info) {
544  local_storage_info_list_ = local_storage_info;
545  PopulateLocalStorageInfoWithFilter(std::wstring());
546}
547
548void CookiesTreeModel::PopulateLocalStorageInfoWithFilter(
549    const std::wstring& filter) {
550  if (local_storage_info_list_.empty())
551    return;
552  CookieTreeRootNode* root = static_cast<CookieTreeRootNode*>(GetRoot());
553  NotifyObserverBeginBatch();
554  for (LocalStorageInfoList::iterator local_storage_info =
555       local_storage_info_list_.begin();
556       local_storage_info != local_storage_info_list_.end();
557       ++local_storage_info) {
558    GURL origin(local_storage_info->origin);
559
560    if (!filter.size() ||
561        (CookieTreeOriginNode::TitleForUrl(origin).find(filter) !=
562         std::wstring::npos)) {
563      CookieTreeOriginNode* origin_node =
564          root->GetOrCreateOriginNode(origin);
565      CookieTreeLocalStoragesNode* local_storages_node =
566          origin_node->GetOrCreateLocalStoragesNode();
567      local_storages_node->AddLocalStorageNode(
568          new CookieTreeLocalStorageNode(&(*local_storage_info)));
569    }
570  }
571  NotifyObserverTreeNodeChanged(root);
572  NotifyObserverEndBatch();
573}
574
575void CookiesTreeModel::NotifyObserverBeginBatch() {
576  // Only notify the model once if we're batching in a nested manner.
577  if (batch_update_++ == 0) {
578    FOR_EACH_OBSERVER(Observer,
579                      cookies_observer_list_,
580                      TreeModelBeginBatch(this));
581  }
582}
583
584void CookiesTreeModel::NotifyObserverEndBatch() {
585  // Only notify the observers if this is the outermost call to EndBatch() if
586  // called in a nested manner.
587  if (--batch_update_ == 0) {
588    FOR_EACH_OBSERVER(Observer,
589                      cookies_observer_list_,
590                      TreeModelEndBatch(this));
591  }
592}
593