1 // Copyright (c) 2013 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/indexed_db/indexed_db_internals_ui.h"
6 
7 #include <string>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/files/file_util.h"
13 #include "base/macros.h"
14 #include "base/task/post_task.h"
15 #include "base/task/thread_pool.h"
16 #include "base/threading/platform_thread.h"
17 #include "base/values.h"
18 #include "content/grit/dev_ui_content_resources.h"
19 #include "content/public/browser/browser_context.h"
20 #include "content/public/browser/browser_task_traits.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/download_manager.h"
23 #include "content/public/browser/download_request_utils.h"
24 #include "content/public/browser/storage_partition.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/browser/web_ui.h"
27 #include "content/public/browser/web_ui_data_source.h"
28 #include "content/public/common/url_constants.h"
29 #include "net/traffic_annotation/network_traffic_annotation.h"
30 #include "storage/common/database/database_identifier.h"
31 #include "ui/base/text/bytes_formatting.h"
32 #include "url/origin.h"
33 
34 using url::Origin;
35 
36 namespace content {
37 
IndexedDBInternalsUI(WebUI * web_ui)38 IndexedDBInternalsUI::IndexedDBInternalsUI(WebUI* web_ui)
39     : WebUIController(web_ui) {
40   web_ui->RegisterMessageCallback(
41       "getAllOrigins", base::BindRepeating(&IndexedDBInternalsUI::GetAllOrigins,
42                                            base::Unretained(this)));
43 
44   web_ui->RegisterMessageCallback(
45       "downloadOriginData",
46       base::BindRepeating(&IndexedDBInternalsUI::DownloadOriginData,
47                           base::Unretained(this)));
48   web_ui->RegisterMessageCallback(
49       "forceClose", base::BindRepeating(&IndexedDBInternalsUI::ForceCloseOrigin,
50                                         base::Unretained(this)));
51   WebUIDataSource* source =
52       WebUIDataSource::Create(kChromeUIIndexedDBInternalsHost);
53   source->OverrideContentSecurityPolicyScriptSrc(
54       "script-src chrome://resources 'self' 'unsafe-eval';");
55   source->UseStringsJs();
56   source->AddResourcePath("indexeddb_internals.js",
57                           IDR_INDEXED_DB_INTERNALS_JS);
58   source->AddResourcePath("indexeddb_internals.css",
59                           IDR_INDEXED_DB_INTERNALS_CSS);
60   source->SetDefaultResource(IDR_INDEXED_DB_INTERNALS_HTML);
61 
62   BrowserContext* browser_context =
63       web_ui->GetWebContents()->GetBrowserContext();
64   WebUIDataSource::Add(browser_context, source);
65 }
66 
~IndexedDBInternalsUI()67 IndexedDBInternalsUI::~IndexedDBInternalsUI() {}
68 
GetAllOrigins(const base::ListValue * args)69 void IndexedDBInternalsUI::GetAllOrigins(const base::ListValue* args) {
70   DCHECK_CURRENTLY_ON(BrowserThread::UI);
71 
72   BrowserContext* browser_context =
73       web_ui()->GetWebContents()->GetBrowserContext();
74 
75   BrowserContext::ForEachStoragePartition(
76       browser_context,
77       base::BindRepeating(
78           [](base::WeakPtr<IndexedDBInternalsUI> ui,
79              StoragePartition* partition) {
80             if (!ui)
81               return;
82             auto& control = partition->GetIndexedDBControl();
83             control.GetAllOriginsDetails(base::BindOnce(
84                 [](base::WeakPtr<IndexedDBInternalsUI> ui,
85                    base::FilePath partition_path, bool incognito,
86                    base::Value info_list) {
87                   if (!ui)
88                     return;
89 
90                   ui->OnOriginsReady(
91                       info_list, incognito ? base::FilePath() : partition_path);
92                 },
93                 ui, partition->GetPath()));
94           },
95           weak_factory_.GetWeakPtr()));
96 }
97 
OnOriginsReady(const base::Value & origins,const base::FilePath & path)98 void IndexedDBInternalsUI::OnOriginsReady(const base::Value& origins,
99                                           const base::FilePath& path) {
100   DCHECK_CURRENTLY_ON(BrowserThread::UI);
101   web_ui()->CallJavascriptFunctionUnsafe("indexeddb.onOriginsReady", origins,
102                                          base::Value(path.value()));
103 }
104 
FindControl(const base::FilePath & partition_path,StoragePartition ** result_partition,storage::mojom::IndexedDBControl ** result_control,StoragePartition * storage_partition)105 static void FindControl(const base::FilePath& partition_path,
106                         StoragePartition** result_partition,
107                         storage::mojom::IndexedDBControl** result_control,
108                         StoragePartition* storage_partition) {
109   if (storage_partition->GetPath() == partition_path) {
110     *result_partition = storage_partition;
111     *result_control = &storage_partition->GetIndexedDBControl();
112   }
113 }
114 
GetOriginData(const base::ListValue * args,base::FilePath * partition_path,Origin * origin,storage::mojom::IndexedDBControl ** control)115 bool IndexedDBInternalsUI::GetOriginData(
116     const base::ListValue* args,
117     base::FilePath* partition_path,
118     Origin* origin,
119     storage::mojom::IndexedDBControl** control) {
120   base::FilePath::StringType path_string;
121   if (!args->GetString(0, &path_string))
122     return false;
123   *partition_path = base::FilePath(path_string);
124 
125   std::string url_string;
126   if (!args->GetString(1, &url_string))
127     return false;
128 
129   *origin = Origin::Create(GURL(url_string));
130 
131   return GetOriginControl(*partition_path, *origin, control);
132 }
133 
GetOriginControl(const base::FilePath & path,const Origin & origin,storage::mojom::IndexedDBControl ** control)134 bool IndexedDBInternalsUI::GetOriginControl(
135     const base::FilePath& path,
136     const Origin& origin,
137     storage::mojom::IndexedDBControl** control) {
138   // search the origins to find the right context
139   BrowserContext* browser_context =
140       web_ui()->GetWebContents()->GetBrowserContext();
141 
142   StoragePartition* result_partition = nullptr;
143   *control = nullptr;
144   BrowserContext::ForEachStoragePartition(
145       browser_context,
146       base::BindRepeating(&FindControl, path, &result_partition, control));
147 
148   if (!result_partition || !control)
149     return false;
150 
151   return true;
152 }
153 
DownloadOriginData(const base::ListValue * args)154 void IndexedDBInternalsUI::DownloadOriginData(const base::ListValue* args) {
155   DCHECK_CURRENTLY_ON(BrowserThread::UI);
156 
157   base::FilePath partition_path;
158   Origin origin;
159   storage::mojom::IndexedDBControl* control;
160   if (!GetOriginData(args, &partition_path, &origin, &control))
161     return;
162 
163   DCHECK(control);
164   control->ForceClose(
165       origin, storage::mojom::ForceCloseReason::FORCE_CLOSE_INTERNALS_PAGE,
166       base::BindOnce(
167           [](base::WeakPtr<IndexedDBInternalsUI> ui, Origin origin,
168              base::FilePath partition_path,
169              storage::mojom::IndexedDBControl* control) {
170             // Is the connection count always zero after closing,
171             // such that this can be simplified?
172             control->GetConnectionCount(
173                 origin,
174                 base::BindOnce(
175                     [](base::WeakPtr<IndexedDBInternalsUI> ui, Origin origin,
176                        base::FilePath partition_path,
177                        storage::mojom::IndexedDBControl* control,
178                        uint64_t connection_count) {
179                       if (!ui)
180                         return;
181 
182                       control->DownloadOriginData(
183                           origin,
184                           base::BindOnce(
185                               &IndexedDBInternalsUI::OnDownloadDataReady, ui,
186                               partition_path, origin, connection_count));
187                     },
188                     ui, origin, partition_path, control));
189           },
190           weak_factory_.GetWeakPtr(), origin, partition_path, control));
191 }
192 
ForceCloseOrigin(const base::ListValue * args)193 void IndexedDBInternalsUI::ForceCloseOrigin(const base::ListValue* args) {
194   DCHECK_CURRENTLY_ON(BrowserThread::UI);
195 
196   base::FilePath partition_path;
197   Origin origin;
198   storage::mojom::IndexedDBControl* control;
199   if (!GetOriginData(args, &partition_path, &origin, &control))
200     return;
201 
202   control->ForceClose(
203       origin, storage::mojom::ForceCloseReason::FORCE_CLOSE_INTERNALS_PAGE,
204       base::BindOnce(
205           [](base::WeakPtr<IndexedDBInternalsUI> ui,
206              base::FilePath partition_path, Origin origin,
207              storage::mojom::IndexedDBControl* control) {
208             if (!ui)
209               return;
210             control->GetConnectionCount(
211                 origin, base::BindOnce(&IndexedDBInternalsUI::OnForcedClose, ui,
212                                        partition_path, origin));
213           },
214           weak_factory_.GetWeakPtr(), partition_path, origin, control));
215 }
216 
OnForcedClose(const base::FilePath & partition_path,const Origin & origin,uint64_t connection_count)217 void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path,
218                                          const Origin& origin,
219                                          uint64_t connection_count) {
220   web_ui()->CallJavascriptFunctionUnsafe(
221       "indexeddb.onForcedClose", base::Value(partition_path.value()),
222       base::Value(origin.Serialize()),
223       base::Value(static_cast<double>(connection_count)));
224 }
225 
OnDownloadDataReady(const base::FilePath & partition_path,const Origin & origin,uint64_t connection_count,bool success,const base::FilePath & temp_path,const base::FilePath & zip_path)226 void IndexedDBInternalsUI::OnDownloadDataReady(
227     const base::FilePath& partition_path,
228     const Origin& origin,
229     uint64_t connection_count,
230     bool success,
231     const base::FilePath& temp_path,
232     const base::FilePath& zip_path) {
233   DCHECK_CURRENTLY_ON(BrowserThread::UI);
234   if (!success)
235     return;
236 
237   const GURL url = GURL(FILE_PATH_LITERAL("file://") + zip_path.value());
238   WebContents* web_contents = web_ui()->GetWebContents();
239   net::NetworkTrafficAnnotationTag traffic_annotation =
240       net::DefineNetworkTrafficAnnotation("indexed_db_internals_handler", R"(
241         semantics {
242           sender: "Indexed DB Internals"
243           description:
244             "This is an internal Chrome webpage that displays debug "
245             "information about IndexedDB usage and data, used by developers."
246           trigger: "When a user navigates to chrome://indexeddb-internals/."
247           data: "None."
248           destination: LOCAL
249         }
250         policy {
251           cookies_allowed: NO
252           setting:
253             "This feature cannot be disabled by settings, but it's only "
254             "triggered by navigating to the specified URL."
255           policy_exception_justification:
256             "Not implemented. Indexed DB is Chrome's internal local data "
257             "storage."
258         })");
259   std::unique_ptr<download::DownloadUrlParameters> dl_params(
260       DownloadRequestUtils::CreateDownloadForWebContentsMainFrame(
261           web_contents, url, traffic_annotation));
262   content::Referrer referrer = content::Referrer::SanitizeForRequest(
263       url, content::Referrer(web_contents->GetLastCommittedURL(),
264                              network::mojom::ReferrerPolicy::kDefault));
265   dl_params->set_referrer(referrer.url);
266   dl_params->set_referrer_policy(
267       Referrer::ReferrerPolicyForUrlRequest(referrer.policy));
268 
269   // This is how to watch for the download to finish: first wait for it
270   // to start, then attach a download::DownloadItem::Observer to observe the
271   // state change to the finished state.
272   dl_params->set_callback(base::BindOnce(
273       &IndexedDBInternalsUI::OnDownloadStarted, base::Unretained(this),
274       partition_path, origin, temp_path, connection_count));
275 
276   BrowserContext* context = web_contents->GetBrowserContext();
277   BrowserContext::GetDownloadManager(context)->DownloadUrl(
278       std::move(dl_params));
279 }
280 
281 // The entire purpose of this class is to delete the temp file after
282 // the download is complete.
283 class FileDeleter : public download::DownloadItem::Observer {
284  public:
FileDeleter(const base::FilePath & temp_dir)285   explicit FileDeleter(const base::FilePath& temp_dir) : temp_dir_(temp_dir) {}
286   ~FileDeleter() override;
287 
288   void OnDownloadUpdated(download::DownloadItem* download) override;
289   void OnDownloadOpened(download::DownloadItem* item) override {}
290   void OnDownloadRemoved(download::DownloadItem* item) override {}
291   void OnDownloadDestroyed(download::DownloadItem* item) override {}
292 
293  private:
294   const base::FilePath temp_dir_;
295 
296   DISALLOW_COPY_AND_ASSIGN(FileDeleter);
297 };
298 
299 void FileDeleter::OnDownloadUpdated(download::DownloadItem* item) {
300   switch (item->GetState()) {
301     case download::DownloadItem::IN_PROGRESS:
302       break;
303     case download::DownloadItem::COMPLETE:
304     case download::DownloadItem::CANCELLED:
305     case download::DownloadItem::INTERRUPTED: {
306       item->RemoveObserver(this);
307       delete this;
308       break;
309     }
310     default:
311       NOTREACHED();
312   }
313 }
314 
315 FileDeleter::~FileDeleter() {
316   base::ThreadPool::PostTask(
317       FROM_HERE,
318       {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
319        base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
320       base::BindOnce(base::IgnoreResult(&base::DeleteFile),
321                      std::move(temp_dir_), true));
322 }
323 
324 void IndexedDBInternalsUI::OnDownloadStarted(
325     const base::FilePath& partition_path,
326     const Origin& origin,
327     const base::FilePath& temp_path,
328     size_t connection_count,
329     download::DownloadItem* item,
330     download::DownloadInterruptReason interrupt_reason) {
331   if (interrupt_reason != download::DOWNLOAD_INTERRUPT_REASON_NONE) {
332     LOG(ERROR) << "Error downloading database dump: "
333                << DownloadInterruptReasonToString(interrupt_reason);
334     return;
335   }
336 
337   item->AddObserver(new FileDeleter(temp_path));
338   web_ui()->CallJavascriptFunctionUnsafe(
339       "indexeddb.onOriginDownloadReady", base::Value(partition_path.value()),
340       base::Value(origin.Serialize()),
341       base::Value(static_cast<double>(connection_count)));
342 }
343 
344 }  // namespace content
345