1 // Copyright 2016 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 "content/browser/devtools/protocol/storage_handler.h"
6 
7 #include <memory>
8 #include <unordered_set>
9 #include <utility>
10 #include <vector>
11 
12 #include "base/bind.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/task/post_task.h"
16 #include "content/browser/cache_storage/cache_storage_context_impl.h"
17 #include "content/browser/devtools/protocol/browser_handler.h"
18 #include "content/browser/devtools/protocol/network.h"
19 #include "content/browser/devtools/protocol/network_handler.h"
20 #include "content/browser/storage_partition_impl.h"
21 #include "content/public/browser/browser_context.h"
22 #include "content/public/browser/browser_task_traits.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/render_process_host.h"
25 #include "content/public/browser/storage_partition.h"
26 #include "storage/browser/quota/quota_client.h"
27 #include "storage/browser/quota/quota_manager.h"
28 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
29 #include "url/gurl.h"
30 #include "url/origin.h"
31 
32 namespace content {
33 namespace protocol {
34 
35 using ClearCookiesCallback = Storage::Backend::ClearCookiesCallback;
36 using GetCookiesCallback = Storage::Backend::GetCookiesCallback;
37 using SetCookiesCallback = Storage::Backend::SetCookiesCallback;
38 
39 struct UsageListInitializer {
40   const char* type;
41   int64_t blink::mojom::UsageBreakdown::*usage_member;
42 };
43 
44 UsageListInitializer initializers[] = {
45     {Storage::StorageTypeEnum::File_systems,
46      &blink::mojom::UsageBreakdown::fileSystem},
47     {Storage::StorageTypeEnum::Websql, &blink::mojom::UsageBreakdown::webSql},
48     {Storage::StorageTypeEnum::Appcache,
49      &blink::mojom::UsageBreakdown::appcache},
50     {Storage::StorageTypeEnum::Indexeddb,
51      &blink::mojom::UsageBreakdown::indexedDatabase},
52     {Storage::StorageTypeEnum::Cache_storage,
53      &blink::mojom::UsageBreakdown::serviceWorkerCache},
54     {Storage::StorageTypeEnum::Service_workers,
55      &blink::mojom::UsageBreakdown::serviceWorker},
56 };
57 
58 namespace {
59 
ReportUsageAndQuotaDataOnUIThread(std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback,blink::mojom::QuotaStatusCode code,int64_t usage,int64_t quota,blink::mojom::UsageBreakdownPtr usage_breakdown)60 void ReportUsageAndQuotaDataOnUIThread(
61     std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback,
62     blink::mojom::QuotaStatusCode code,
63     int64_t usage,
64     int64_t quota,
65     blink::mojom::UsageBreakdownPtr usage_breakdown) {
66   DCHECK_CURRENTLY_ON(BrowserThread::UI);
67   if (code != blink::mojom::QuotaStatusCode::kOk) {
68     return callback->sendFailure(
69         Response::ServerError("Quota information is not available"));
70   }
71 
72   auto usageList = std::make_unique<Array<Storage::UsageForType>>();
73 
74   blink::mojom::UsageBreakdown* breakdown_ptr = usage_breakdown.get();
75   for (const auto initializer : initializers) {
76     std::unique_ptr<Storage::UsageForType> entry =
77         Storage::UsageForType::Create()
78             .SetStorageType(initializer.type)
79             .SetUsage(breakdown_ptr->*(initializer.usage_member))
80             .Build();
81     usageList->emplace_back(std::move(entry));
82   }
83 
84   callback->sendSuccess(usage, quota, std::move(usageList));
85 }
86 
GotUsageAndQuotaDataCallback(std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback,blink::mojom::QuotaStatusCode code,int64_t usage,int64_t quota,blink::mojom::UsageBreakdownPtr usage_breakdown)87 void GotUsageAndQuotaDataCallback(
88     std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback,
89     blink::mojom::QuotaStatusCode code,
90     int64_t usage,
91     int64_t quota,
92     blink::mojom::UsageBreakdownPtr usage_breakdown) {
93   DCHECK_CURRENTLY_ON(BrowserThread::IO);
94   base::PostTask(
95       FROM_HERE, {BrowserThread::UI},
96       base::BindOnce(ReportUsageAndQuotaDataOnUIThread, std::move(callback),
97                      code, usage, quota, std::move(usage_breakdown)));
98 }
99 
GetUsageAndQuotaOnIOThread(storage::QuotaManager * manager,const url::Origin & origin,std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback)100 void GetUsageAndQuotaOnIOThread(
101     storage::QuotaManager* manager,
102     const url::Origin& origin,
103     std::unique_ptr<StorageHandler::GetUsageAndQuotaCallback> callback) {
104   DCHECK_CURRENTLY_ON(BrowserThread::IO);
105   manager->GetUsageAndQuotaWithBreakdown(
106       origin, blink::mojom::StorageType::kTemporary,
107       base::BindOnce(&GotUsageAndQuotaDataCallback, std::move(callback)));
108 }
109 
110 }  // namespace
111 
112 // Observer that listens on the IO thread for cache storage notifications and
113 // informs the StorageHandler on the UI thread for origins of interest.
114 // Created on the UI thread but predominantly used and deleted on the IO thread.
115 // Registered on creation as an observer in CacheStorageContextImpl,
116 // unregistered on destruction.
117 class StorageHandler::CacheStorageObserver : CacheStorageContextImpl::Observer {
118  public:
CacheStorageObserver(base::WeakPtr<StorageHandler> owner_storage_handler,CacheStorageContextImpl * cache_storage_context)119   CacheStorageObserver(base::WeakPtr<StorageHandler> owner_storage_handler,
120                        CacheStorageContextImpl* cache_storage_context)
121       : owner_(owner_storage_handler), context_(cache_storage_context) {
122     DCHECK_CURRENTLY_ON(BrowserThread::UI);
123     context_->AddObserver(this);
124   }
125 
~CacheStorageObserver()126   ~CacheStorageObserver() override {
127     DCHECK_CURRENTLY_ON(BrowserThread::UI);
128     context_->RemoveObserver(this);
129   }
130 
TrackOrigin(const url::Origin & origin)131   void TrackOrigin(const url::Origin& origin) {
132     DCHECK_CURRENTLY_ON(BrowserThread::UI);
133     if (origins_.find(origin) != origins_.end())
134       return;
135     origins_.insert(origin);
136   }
137 
UntrackOrigin(const url::Origin & origin)138   void UntrackOrigin(const url::Origin& origin) {
139     DCHECK_CURRENTLY_ON(BrowserThread::UI);
140     origins_.erase(origin);
141   }
142 
OnCacheListChanged(const url::Origin & origin)143   void OnCacheListChanged(const url::Origin& origin) override {
144     DCHECK_CURRENTLY_ON(BrowserThread::UI);
145     auto found = origins_.find(origin);
146     if (found == origins_.end())
147       return;
148     owner_->NotifyCacheStorageListChanged(origin.Serialize());
149   }
150 
OnCacheContentChanged(const url::Origin & origin,const std::string & cache_name)151   void OnCacheContentChanged(const url::Origin& origin,
152                              const std::string& cache_name) override {
153     DCHECK_CURRENTLY_ON(BrowserThread::UI);
154     if (origins_.find(origin) == origins_.end())
155       return;
156     owner_->NotifyCacheStorageContentChanged(origin.Serialize(), cache_name);
157   }
158 
159  private:
160   // Maintained on the IO thread to avoid thread contention.
161   base::flat_set<url::Origin> origins_;
162 
163   base::WeakPtr<StorageHandler> owner_;
164   scoped_refptr<CacheStorageContextImpl> context_;
165 
166   DISALLOW_COPY_AND_ASSIGN(CacheStorageObserver);
167 };
168 
169 // Observer that listens on the IDB thread for IndexedDB notifications and
170 // informs the StorageHandler on the UI thread for origins of interest.
171 // Created on the UI thread but predominantly used and deleted on the IDB
172 // thread.
173 class StorageHandler::IndexedDBObserver
174     : public storage::mojom::IndexedDBObserver {
175  public:
IndexedDBObserver(base::WeakPtr<StorageHandler> owner_storage_handler)176   explicit IndexedDBObserver(
177       base::WeakPtr<StorageHandler> owner_storage_handler)
178       : owner_(owner_storage_handler), receiver_(this) {
179     DCHECK_CURRENTLY_ON(BrowserThread::UI);
180 
181     ReconnectObserver();
182   }
183 
~IndexedDBObserver()184   ~IndexedDBObserver() override { DCHECK_CURRENTLY_ON(BrowserThread::UI); }
185 
TrackOrigin(const url::Origin & origin)186   void TrackOrigin(const url::Origin& origin) {
187     DCHECK_CURRENTLY_ON(BrowserThread::UI);
188     if (origins_.find(origin) != origins_.end())
189       return;
190     origins_.insert(origin);
191   }
192 
UntrackOrigin(const url::Origin & origin)193   void UntrackOrigin(const url::Origin& origin) {
194     DCHECK_CURRENTLY_ON(BrowserThread::UI);
195     origins_.erase(origin);
196   }
197 
OnIndexedDBListChanged(const url::Origin & origin)198   void OnIndexedDBListChanged(const url::Origin& origin) override {
199     DCHECK_CURRENTLY_ON(BrowserThread::UI);
200     if (!owner_)
201       return;
202     auto found = origins_.find(origin);
203     if (found == origins_.end())
204       return;
205     owner_->NotifyIndexedDBListChanged(origin.Serialize());
206   }
207 
OnIndexedDBContentChanged(const url::Origin & origin,const base::string16 & database_name,const base::string16 & object_store_name)208   void OnIndexedDBContentChanged(
209       const url::Origin& origin,
210       const base::string16& database_name,
211       const base::string16& object_store_name) override {
212     DCHECK_CURRENTLY_ON(BrowserThread::UI);
213     if (!owner_)
214       return;
215     auto found = origins_.find(origin);
216     if (found == origins_.end())
217       return;
218     owner_->NotifyIndexedDBContentChanged(origin.Serialize(), database_name,
219                                           object_store_name);
220   }
221 
222  private:
ReconnectObserver()223   void ReconnectObserver() {
224     DCHECK(!receiver_.is_bound());
225     if (!owner_)
226       return;
227 
228     auto& control = owner_->storage_partition_->GetIndexedDBControl();
229     mojo::PendingRemote<storage::mojom::IndexedDBObserver> remote;
230     receiver_.Bind(remote.InitWithNewPipeAndPassReceiver());
231     receiver_.set_disconnect_handler(base::BindOnce(
232         [](IndexedDBObserver* observer) {
233           // If this observer disconnects because IndexedDB or the storage
234           // service goes away, reconnect again.
235           observer->ReconnectObserver();
236         },
237         this));
238     control.AddObserver(std::move(remote));
239   }
240 
241   base::flat_set<url::Origin> origins_;
242   base::WeakPtr<StorageHandler> owner_;
243   mojo::Receiver<storage::mojom::IndexedDBObserver> receiver_;
244 
245   DISALLOW_COPY_AND_ASSIGN(IndexedDBObserver);
246 };
247 
StorageHandler()248 StorageHandler::StorageHandler()
249     : DevToolsDomainHandler(Storage::Metainfo::domainName),
250       storage_partition_(nullptr) {}
251 
~StorageHandler()252 StorageHandler::~StorageHandler() {
253   DCHECK(!cache_storage_observer_);
254   DCHECK(!indexed_db_observer_);
255 }
256 
Wire(UberDispatcher * dispatcher)257 void StorageHandler::Wire(UberDispatcher* dispatcher) {
258   frontend_ = std::make_unique<Storage::Frontend>(dispatcher->channel());
259   Storage::Dispatcher::wire(dispatcher, this);
260 }
261 
SetRenderer(int process_host_id,RenderFrameHostImpl * frame_host)262 void StorageHandler::SetRenderer(int process_host_id,
263                                  RenderFrameHostImpl* frame_host) {
264   RenderProcessHost* process = RenderProcessHost::FromID(process_host_id);
265   storage_partition_ = process ? process->GetStoragePartition() : nullptr;
266 }
267 
Disable()268 Response StorageHandler::Disable() {
269   cache_storage_observer_.reset();
270   indexed_db_observer_.reset();
271   return Response::Success();
272 }
273 
GetCookies(Maybe<std::string> browser_context_id,std::unique_ptr<GetCookiesCallback> callback)274 void StorageHandler::GetCookies(Maybe<std::string> browser_context_id,
275                                 std::unique_ptr<GetCookiesCallback> callback) {
276   StoragePartition* storage_partition = nullptr;
277   Response response = StorageHandler::FindStoragePartition(browser_context_id,
278                                                            &storage_partition);
279   if (!response.IsSuccess()) {
280     callback->sendFailure(std::move(response));
281     return;
282   }
283 
284   storage_partition->GetCookieManagerForBrowserProcess()->GetAllCookies(
285       base::BindOnce(
286           [](std::unique_ptr<GetCookiesCallback> callback,
287              const std::vector<net::CanonicalCookie>& cookies) {
288             callback->sendSuccess(NetworkHandler::BuildCookieArray(cookies));
289           },
290           std::move(callback)));
291 }
292 
SetCookies(std::unique_ptr<protocol::Array<Network::CookieParam>> cookies,Maybe<std::string> browser_context_id,std::unique_ptr<SetCookiesCallback> callback)293 void StorageHandler::SetCookies(
294     std::unique_ptr<protocol::Array<Network::CookieParam>> cookies,
295     Maybe<std::string> browser_context_id,
296     std::unique_ptr<SetCookiesCallback> callback) {
297   StoragePartition* storage_partition = nullptr;
298   Response response = StorageHandler::FindStoragePartition(browser_context_id,
299                                                            &storage_partition);
300   if (!response.IsSuccess()) {
301     callback->sendFailure(std::move(response));
302     return;
303   }
304 
305   NetworkHandler::SetCookies(
306       storage_partition, std::move(cookies),
307       base::BindOnce(
308           [](std::unique_ptr<SetCookiesCallback> callback, bool success) {
309             if (success) {
310               callback->sendSuccess();
311             } else {
312               callback->sendFailure(
313                   Response::InvalidParams("Invalid cookie fields"));
314             }
315           },
316           std::move(callback)));
317 }
318 
ClearCookies(Maybe<std::string> browser_context_id,std::unique_ptr<ClearCookiesCallback> callback)319 void StorageHandler::ClearCookies(
320     Maybe<std::string> browser_context_id,
321     std::unique_ptr<ClearCookiesCallback> callback) {
322   StoragePartition* storage_partition = nullptr;
323   Response response = StorageHandler::FindStoragePartition(browser_context_id,
324                                                            &storage_partition);
325   if (!response.IsSuccess()) {
326     callback->sendFailure(std::move(response));
327     return;
328   }
329 
330   storage_partition->GetCookieManagerForBrowserProcess()->DeleteCookies(
331       network::mojom::CookieDeletionFilter::New(),
332       base::BindOnce([](std::unique_ptr<ClearCookiesCallback> callback,
333                         uint32_t) { callback->sendSuccess(); },
334                      std::move(callback)));
335 }
336 
ClearDataForOrigin(const std::string & origin,const std::string & storage_types,std::unique_ptr<ClearDataForOriginCallback> callback)337 void StorageHandler::ClearDataForOrigin(
338     const std::string& origin,
339     const std::string& storage_types,
340     std::unique_ptr<ClearDataForOriginCallback> callback) {
341   if (!storage_partition_)
342     return callback->sendFailure(Response::InternalError());
343 
344   std::vector<std::string> types = base::SplitString(
345       storage_types, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
346   std::unordered_set<std::string> set(types.begin(), types.end());
347   uint32_t remove_mask = 0;
348   if (set.count(Storage::StorageTypeEnum::Appcache))
349     remove_mask |= StoragePartition::REMOVE_DATA_MASK_APPCACHE;
350   if (set.count(Storage::StorageTypeEnum::Cookies))
351     remove_mask |= StoragePartition::REMOVE_DATA_MASK_COOKIES;
352   if (set.count(Storage::StorageTypeEnum::File_systems))
353     remove_mask |= StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS;
354   if (set.count(Storage::StorageTypeEnum::Indexeddb))
355     remove_mask |= StoragePartition::REMOVE_DATA_MASK_INDEXEDDB;
356   if (set.count(Storage::StorageTypeEnum::Local_storage))
357     remove_mask |= StoragePartition::REMOVE_DATA_MASK_LOCAL_STORAGE;
358   if (set.count(Storage::StorageTypeEnum::Shader_cache))
359     remove_mask |= StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE;
360   if (set.count(Storage::StorageTypeEnum::Websql))
361     remove_mask |= StoragePartition::REMOVE_DATA_MASK_WEBSQL;
362   if (set.count(Storage::StorageTypeEnum::Service_workers))
363     remove_mask |= StoragePartition::REMOVE_DATA_MASK_SERVICE_WORKERS;
364   if (set.count(Storage::StorageTypeEnum::Cache_storage))
365     remove_mask |= StoragePartition::REMOVE_DATA_MASK_CACHE_STORAGE;
366   if (set.count(Storage::StorageTypeEnum::All))
367     remove_mask |= StoragePartition::REMOVE_DATA_MASK_ALL;
368 
369   if (!remove_mask) {
370     return callback->sendFailure(
371         Response::InvalidParams("No valid storage type specified"));
372   }
373 
374   storage_partition_->ClearData(
375       remove_mask, StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
376       GURL(origin), base::Time(), base::Time::Max(),
377       base::BindOnce(&ClearDataForOriginCallback::sendSuccess,
378                      std::move(callback)));
379 }
380 
GetUsageAndQuota(const String & origin,std::unique_ptr<GetUsageAndQuotaCallback> callback)381 void StorageHandler::GetUsageAndQuota(
382     const String& origin,
383     std::unique_ptr<GetUsageAndQuotaCallback> callback) {
384   if (!storage_partition_)
385     return callback->sendFailure(Response::InternalError());
386 
387   GURL origin_url(origin);
388   if (!origin_url.is_valid()) {
389     return callback->sendFailure(
390         Response::ServerError(origin + " is not a valid URL"));
391   }
392 
393   storage::QuotaManager* manager = storage_partition_->GetQuotaManager();
394   base::PostTask(
395       FROM_HERE, {BrowserThread::IO},
396       base::BindOnce(&GetUsageAndQuotaOnIOThread, base::RetainedRef(manager),
397                      url::Origin::Create(origin_url), std::move(callback)));
398 }
399 
TrackCacheStorageForOrigin(const std::string & origin)400 Response StorageHandler::TrackCacheStorageForOrigin(const std::string& origin) {
401   if (!storage_partition_)
402     return Response::InternalError();
403 
404   GURL origin_url(origin);
405   if (!origin_url.is_valid())
406     return Response::InvalidParams(origin + " is not a valid URL");
407 
408   GetCacheStorageObserver()->TrackOrigin(url::Origin::Create(origin_url));
409   return Response::Success();
410 }
411 
UntrackCacheStorageForOrigin(const std::string & origin)412 Response StorageHandler::UntrackCacheStorageForOrigin(
413     const std::string& origin) {
414   if (!storage_partition_)
415     return Response::InternalError();
416 
417   GURL origin_url(origin);
418   if (!origin_url.is_valid())
419     return Response::InvalidParams(origin + " is not a valid URL");
420 
421   GetCacheStorageObserver()->UntrackOrigin(url::Origin::Create(origin_url));
422   return Response::Success();
423 }
424 
TrackIndexedDBForOrigin(const std::string & origin)425 Response StorageHandler::TrackIndexedDBForOrigin(const std::string& origin) {
426   if (!storage_partition_)
427     return Response::InternalError();
428 
429   GURL origin_url(origin);
430   if (!origin_url.is_valid())
431     return Response::InvalidParams(origin + " is not a valid URL");
432 
433   GetIndexedDBObserver()->TrackOrigin(url::Origin::Create(origin_url));
434   return Response::Success();
435 }
436 
UntrackIndexedDBForOrigin(const std::string & origin)437 Response StorageHandler::UntrackIndexedDBForOrigin(const std::string& origin) {
438   if (!storage_partition_)
439     return Response::InternalError();
440 
441   GURL origin_url(origin);
442   if (!origin_url.is_valid())
443     return Response::InvalidParams(origin + " is not a valid URL");
444 
445   GetIndexedDBObserver()->UntrackOrigin(url::Origin::Create(origin_url));
446   return Response::Success();
447 }
448 
449 StorageHandler::CacheStorageObserver*
GetCacheStorageObserver()450 StorageHandler::GetCacheStorageObserver() {
451   DCHECK_CURRENTLY_ON(BrowserThread::UI);
452   if (!cache_storage_observer_) {
453     cache_storage_observer_ = std::make_unique<CacheStorageObserver>(
454         weak_ptr_factory_.GetWeakPtr(),
455         static_cast<CacheStorageContextImpl*>(
456             storage_partition_->GetCacheStorageContext()));
457   }
458   return cache_storage_observer_.get();
459 }
460 
GetIndexedDBObserver()461 StorageHandler::IndexedDBObserver* StorageHandler::GetIndexedDBObserver() {
462   DCHECK_CURRENTLY_ON(BrowserThread::UI);
463   if (!indexed_db_observer_) {
464     indexed_db_observer_ =
465         std::make_unique<IndexedDBObserver>(weak_ptr_factory_.GetWeakPtr());
466   }
467   return indexed_db_observer_.get();
468 }
469 
NotifyCacheStorageListChanged(const std::string & origin)470 void StorageHandler::NotifyCacheStorageListChanged(const std::string& origin) {
471   DCHECK_CURRENTLY_ON(BrowserThread::UI);
472   frontend_->CacheStorageListUpdated(origin);
473 }
474 
NotifyCacheStorageContentChanged(const std::string & origin,const std::string & name)475 void StorageHandler::NotifyCacheStorageContentChanged(const std::string& origin,
476                                                       const std::string& name) {
477   DCHECK_CURRENTLY_ON(BrowserThread::UI);
478   frontend_->CacheStorageContentUpdated(origin, name);
479 }
480 
NotifyIndexedDBListChanged(const std::string & origin)481 void StorageHandler::NotifyIndexedDBListChanged(const std::string& origin) {
482   DCHECK_CURRENTLY_ON(BrowserThread::UI);
483   frontend_->IndexedDBListUpdated(origin);
484 }
485 
NotifyIndexedDBContentChanged(const std::string & origin,const base::string16 & database_name,const base::string16 & object_store_name)486 void StorageHandler::NotifyIndexedDBContentChanged(
487     const std::string& origin,
488     const base::string16& database_name,
489     const base::string16& object_store_name) {
490   DCHECK_CURRENTLY_ON(BrowserThread::UI);
491   frontend_->IndexedDBContentUpdated(origin, base::UTF16ToUTF8(database_name),
492                                      base::UTF16ToUTF8(object_store_name));
493 }
494 
FindStoragePartition(const Maybe<std::string> & browser_context_id,StoragePartition ** storage_partition)495 Response StorageHandler::FindStoragePartition(
496     const Maybe<std::string>& browser_context_id,
497     StoragePartition** storage_partition) {
498   BrowserContext* browser_context = nullptr;
499   Response response =
500       BrowserHandler::FindBrowserContext(browser_context_id, &browser_context);
501   if (!response.IsSuccess())
502     return response;
503   *storage_partition =
504       BrowserContext::GetDefaultStoragePartition(browser_context);
505   if (!*storage_partition)
506     return Response::InternalError();
507   return Response::Success();
508 }
509 
510 }  // namespace protocol
511 }  // namespace content
512