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/ui/webui/downloads/downloads_dom_handler.h"
6 
7 #include <algorithm>
8 #include <functional>
9 #include <memory>
10 #include <string>
11 #include <utility>
12 
13 #include "base/bind.h"
14 #include "base/callback_helpers.h"
15 #include "base/i18n/rtl.h"
16 #include "base/logging.h"
17 #include "base/metrics/histogram_macros.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_piece.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/supports_user_data.h"
22 #include "base/task/current_thread.h"
23 #include "base/threading/thread.h"
24 #include "base/values.h"
25 #include "chrome/browser/browser_process.h"
26 #include "chrome/browser/download/download_danger_prompt.h"
27 #include "chrome/browser/download/download_history.h"
28 #include "chrome/browser/download/download_item_model.h"
29 #include "chrome/browser/download/download_prefs.h"
30 #include "chrome/browser/download/download_query.h"
31 #include "chrome/browser/download/drag_download_item.h"
32 #include "chrome/browser/platform_util.h"
33 #include "chrome/browser/profiles/profile.h"
34 #include "chrome/browser/ui/webui/downloads/downloads.mojom.h"
35 #include "chrome/browser/ui/webui/fileicon_source.h"
36 #include "chrome/common/chrome_switches.h"
37 #include "chrome/common/pref_names.h"
38 #include "chrome/common/url_constants.h"
39 #include "components/download/public/common/download_item.h"
40 #include "components/prefs/pref_service.h"
41 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
42 #include "content/public/browser/browser_thread.h"
43 #include "content/public/browser/download_manager.h"
44 #include "content/public/browser/render_process_host.h"
45 #include "content/public/browser/render_view_host.h"
46 #include "content/public/browser/url_data_source.h"
47 #include "content/public/browser/web_contents.h"
48 #include "content/public/browser/web_ui.h"
49 #include "mojo/public/cpp/bindings/pending_receiver.h"
50 #include "mojo/public/cpp/bindings/pending_remote.h"
51 #include "mojo/public/cpp/bindings/receiver.h"
52 #include "net/base/filename_util.h"
53 #include "ui/base/l10n/time_format.h"
54 #include "ui/gfx/image/image.h"
55 
56 using content::BrowserThread;
57 
58 namespace {
59 
60 enum DownloadsDOMEvent {
61   DOWNLOADS_DOM_EVENT_GET_DOWNLOADS = 0,
62   DOWNLOADS_DOM_EVENT_OPEN_FILE = 1,
63   DOWNLOADS_DOM_EVENT_DRAG = 2,
64   DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS = 3,
65   DOWNLOADS_DOM_EVENT_DISCARD_DANGEROUS = 4,
66   DOWNLOADS_DOM_EVENT_SHOW = 5,
67   DOWNLOADS_DOM_EVENT_PAUSE = 6,
68   DOWNLOADS_DOM_EVENT_REMOVE = 7,
69   DOWNLOADS_DOM_EVENT_CANCEL = 8,
70   DOWNLOADS_DOM_EVENT_CLEAR_ALL = 9,
71   DOWNLOADS_DOM_EVENT_OPEN_FOLDER = 10,
72   DOWNLOADS_DOM_EVENT_RESUME = 11,
73   DOWNLOADS_DOM_EVENT_RETRY_DOWNLOAD = 12,
74   DOWNLOADS_DOM_EVENT_OPEN_DURING_SCANNING = 13,
75   DOWNLOADS_DOM_EVENT_MAX
76 };
77 
CountDownloadsDOMEvents(DownloadsDOMEvent event)78 void CountDownloadsDOMEvents(DownloadsDOMEvent event) {
79   UMA_HISTOGRAM_ENUMERATION("Download.DOMEvent",
80                             event,
81                             DOWNLOADS_DOM_EVENT_MAX);
82 }
83 
84 }  // namespace
85 
DownloadsDOMHandler(mojo::PendingReceiver<downloads::mojom::PageHandler> receiver,mojo::PendingRemote<downloads::mojom::Page> page,content::DownloadManager * download_manager,content::WebUI * web_ui)86 DownloadsDOMHandler::DownloadsDOMHandler(
87     mojo::PendingReceiver<downloads::mojom::PageHandler> receiver,
88     mojo::PendingRemote<downloads::mojom::Page> page,
89     content::DownloadManager* download_manager,
90     content::WebUI* web_ui)
91     : list_tracker_(download_manager, std::move(page)),
92       web_ui_(web_ui),
93       receiver_(this, std::move(receiver)) {
94   // Create our fileicon data source.
95   content::URLDataSource::Add(
96       Profile::FromBrowserContext(download_manager->GetBrowserContext()),
97       std::make_unique<FileIconSource>());
98   CheckForRemovedFiles();
99 }
100 
~DownloadsDOMHandler()101 DownloadsDOMHandler::~DownloadsDOMHandler() {
102   list_tracker_.Stop();
103   list_tracker_.Reset();
104   if (!render_process_gone_)
105     CheckForRemovedFiles();
106   FinalizeRemovals();
107 }
108 
RenderProcessGone(base::TerminationStatus status)109 void DownloadsDOMHandler::RenderProcessGone(base::TerminationStatus status) {
110   // TODO(dbeam): WebUI + WebUIMessageHandler should do this automatically.
111   // http://crbug.com/610450
112   render_process_gone_ = true;
113 }
114 
GetDownloads(const std::vector<std::string> & search_terms)115 void DownloadsDOMHandler::GetDownloads(
116     const std::vector<std::string>& search_terms) {
117   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_GET_DOWNLOADS);
118 
119   bool terms_changed = list_tracker_.SetSearchTerms(search_terms);
120   if (terms_changed)
121     list_tracker_.Reset();
122 
123   list_tracker_.StartAndSendChunk();
124 }
125 
OpenFileRequiringGesture(const std::string & id)126 void DownloadsDOMHandler::OpenFileRequiringGesture(const std::string& id) {
127   if (!GetWebUIWebContents()->HasRecentInteractiveInputEvent()) {
128     LOG(ERROR) << "OpenFileRequiringGesture received without recent "
129                   "user interaction";
130     return;
131   }
132 
133   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_FILE);
134   download::DownloadItem* file = GetDownloadByStringId(id);
135   if (file)
136     file->OpenDownload();
137 }
138 
Drag(const std::string & id)139 void DownloadsDOMHandler::Drag(const std::string& id) {
140   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DRAG);
141   download::DownloadItem* file = GetDownloadByStringId(id);
142   if (!file)
143     return;
144 
145   content::WebContents* web_contents = GetWebUIWebContents();
146   // |web_contents| is only NULL in the test.
147   if (!web_contents)
148     return;
149 
150   if (file->GetState() != download::DownloadItem::COMPLETE)
151     return;
152 
153   gfx::Image* icon = g_browser_process->icon_manager()->LookupIconFromFilepath(
154       file->GetTargetFilePath(), IconLoader::NORMAL);
155   gfx::NativeView view = web_contents->GetNativeView();
156   {
157     // Enable nested tasks during DnD, while |DragDownload()| blocks.
158     base::CurrentThread::ScopedNestableTaskAllower allow;
159     DragDownloadItem(file, icon, view);
160   }
161 }
162 
SaveDangerousRequiringGesture(const std::string & id)163 void DownloadsDOMHandler::SaveDangerousRequiringGesture(const std::string& id) {
164   if (!GetWebUIWebContents()->HasRecentInteractiveInputEvent()) {
165     LOG(ERROR) << "SaveDangerousRequiringGesture received without recent "
166                   "user interaction";
167     return;
168   }
169 
170   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS);
171   download::DownloadItem* file = GetDownloadByStringId(id);
172   if (file)
173     ShowDangerPrompt(file);
174 }
175 
DiscardDangerous(const std::string & id)176 void DownloadsDOMHandler::DiscardDangerous(const std::string& id) {
177   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DISCARD_DANGEROUS);
178   RemoveDownloadInArgs(id);
179 }
180 
RetryDownload(const std::string & id)181 void DownloadsDOMHandler::RetryDownload(const std::string& id) {
182   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_RETRY_DOWNLOAD);
183 
184   download::DownloadItem* file = GetDownloadByStringId(id);
185   if (!file)
186     return;
187   content::WebContents* web_contents = GetWebUIWebContents();
188   content::RenderFrameHost* render_frame_host = web_contents->GetMainFrame();
189   const GURL url = file->GetURL();
190 
191   net::NetworkTrafficAnnotationTag traffic_annotation =
192       net::DefineNetworkTrafficAnnotation("downloads_dom_handler", R"(
193         semantics {
194           sender: "The downloads page."
195           description: "Retrying a download."
196           trigger:
197             "The user selects the 'Retry' button for a cancelled download on "
198             "the downloads page."
199           data: "None."
200           destination: WEBSITE
201         }
202         policy {
203           cookies_allowed: YES
204           cookies_store: "user"
205           setting:
206             "This feature cannot be disabled by settings, but it's only "
207             "triggered by user request."
208           policy_exception_justification: "Not implemented."
209         })");
210 
211   // For "Retry", we want to use the network isolation key associated with the
212   // initial download request rather than treating it as initiated from the
213   // chrome://downloads/ page. Thus we get the NIK from |file|, not from
214   // |render_frame_host|.
215   auto dl_params = std::make_unique<download::DownloadUrlParameters>(
216       url, render_frame_host->GetProcess()->GetID(),
217       render_frame_host->GetRoutingID(), traffic_annotation);
218   dl_params->set_content_initiated(true);
219   dl_params->set_initiator(url::Origin::Create(GURL("chrome://downloads")));
220   dl_params->set_download_source(download::DownloadSource::RETRY);
221 
222   content::BrowserContext::GetDownloadManager(web_contents->GetBrowserContext())
223       ->DownloadUrl(std::move(dl_params));
224 }
225 
226 void DownloadsDOMHandler::Show(const std::string& id) {
227   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SHOW);
228   download::DownloadItem* file = GetDownloadByStringId(id);
229   if (file)
230     file->ShowDownloadInShell();
231 }
232 
233 void DownloadsDOMHandler::Pause(const std::string& id) {
234   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_PAUSE);
235   download::DownloadItem* file = GetDownloadByStringId(id);
236   if (file)
237     file->Pause();
238 }
239 
240 void DownloadsDOMHandler::Resume(const std::string& id) {
241   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_RESUME);
242   download::DownloadItem* file = GetDownloadByStringId(id);
243   if (file)
244     file->Resume(true);
245 }
246 
247 void DownloadsDOMHandler::Remove(const std::string& id) {
248   if (!IsDeletingHistoryAllowed())
249     return;
250 
251   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_REMOVE);
252   RemoveDownloadInArgs(id);
253 }
254 
255 void DownloadsDOMHandler::Undo() {
256   // TODO(dbeam): handle more than removed downloads someday?
257   if (removals_.empty())
258     return;
259 
260   const IdSet last_removed_ids = removals_.back();
261   removals_.pop_back();
262 
263   const bool undoing_clear_all = last_removed_ids.size() > 1;
264   if (undoing_clear_all) {
265     list_tracker_.Reset();
266     list_tracker_.Stop();
267   }
268 
269   for (auto id : last_removed_ids) {
270     download::DownloadItem* download = GetDownloadById(id);
271     if (!download)
272       continue;
273 
274     DownloadItemModel model(download);
275     model.SetShouldShowInShelf(true);
276     model.SetIsBeingRevived(true);
277 
278     download->UpdateObservers();
279 
280     model.SetIsBeingRevived(false);
281   }
282 
283   if (undoing_clear_all)
284     list_tracker_.StartAndSendChunk();
285 }
286 
287 void DownloadsDOMHandler::Cancel(const std::string& id) {
288   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CANCEL);
289   download::DownloadItem* file = GetDownloadByStringId(id);
290   if (file)
291     file->Cancel(true);
292 }
293 
294 void DownloadsDOMHandler::ClearAll() {
295   if (!IsDeletingHistoryAllowed()) {
296     // This should only be reached during tests.
297     return;
298   }
299 
300   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CLEAR_ALL);
301 
302   list_tracker_.Reset();
303   list_tracker_.Stop();
304 
305   DownloadVector downloads;
306   if (GetMainNotifierManager())
307     GetMainNotifierManager()->GetAllDownloads(&downloads);
308   if (GetOriginalNotifierManager())
309     GetOriginalNotifierManager()->GetAllDownloads(&downloads);
310   RemoveDownloads(downloads);
311 
312   list_tracker_.StartAndSendChunk();
313 }
314 
315 void DownloadsDOMHandler::RemoveDownloads(const DownloadVector& to_remove) {
316   IdSet ids;
317 
318   for (auto* download : to_remove) {
319     if (download->IsDangerous() || download->IsMixedContent()) {
320       // Don't allow users to revive dangerous downloads; just nuke 'em.
321       download->Remove();
322       continue;
323     }
324 
325     DownloadItemModel item_model(download);
326     if (!item_model.ShouldShowInShelf() ||
327         download->GetState() == download::DownloadItem::IN_PROGRESS) {
328       continue;
329     }
330 
331     item_model.SetShouldShowInShelf(false);
332     ids.insert(download->GetId());
333     download->UpdateObservers();
334   }
335 
336   if (!ids.empty())
337     removals_.push_back(ids);
338 }
339 
340 void DownloadsDOMHandler::OpenDownloadsFolderRequiringGesture() {
341   if (!GetWebUIWebContents()->HasRecentInteractiveInputEvent()) {
342     LOG(ERROR) << "OpenDownloadsFolderRequiringGesture received without recent "
343                   "user interaction";
344     return;
345   }
346 
347   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_FOLDER);
348   content::DownloadManager* manager = GetMainNotifierManager();
349   if (manager) {
350     platform_util::OpenItem(
351         Profile::FromBrowserContext(manager->GetBrowserContext()),
352         DownloadPrefs::FromDownloadManager(manager)->DownloadPath(),
353         platform_util::OPEN_FOLDER, platform_util::OpenOperationCallback());
354   }
355 }
356 
357 void DownloadsDOMHandler::OpenDuringScanningRequiringGesture(
358     const std::string& id) {
359   if (!GetWebUIWebContents()->HasRecentInteractiveInputEvent()) {
360     LOG(ERROR) << "OpenDownloadsFolderRequiringGesture received without recent "
361                   "user interaction";
362     return;
363   }
364 
365   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_DURING_SCANNING);
366   download::DownloadItem* download = GetDownloadByStringId(id);
367   if (download) {
368     DownloadItemModel model(download);
369     model.SetOpenWhenComplete(true);
370     model.CompleteSafeBrowsingScan();
371   }
372 }
373 
374 // DownloadsDOMHandler, private: --------------------------------------------
375 
376 content::DownloadManager* DownloadsDOMHandler::GetMainNotifierManager() const {
377   return list_tracker_.GetMainNotifierManager();
378 }
379 
380 content::DownloadManager* DownloadsDOMHandler::GetOriginalNotifierManager()
381     const {
382   return list_tracker_.GetOriginalNotifierManager();
383 }
384 
385 void DownloadsDOMHandler::FinalizeRemovals() {
386   while (!removals_.empty()) {
387     const IdSet remove = removals_.back();
388     removals_.pop_back();
389 
390     for (const auto id : remove) {
391       download::DownloadItem* download = GetDownloadById(id);
392       if (download)
393         download->Remove();
394     }
395   }
396 }
397 
398 void DownloadsDOMHandler::ShowDangerPrompt(
399     download::DownloadItem* dangerous_item) {
400   DownloadDangerPrompt* danger_prompt = DownloadDangerPrompt::Create(
401       dangerous_item, GetWebUIWebContents(), false,
402       base::Bind(&DownloadsDOMHandler::DangerPromptDone,
403                  weak_ptr_factory_.GetWeakPtr(), dangerous_item->GetId()));
404   // danger_prompt will delete itself.
405   DCHECK(danger_prompt);
406 }
407 
408 void DownloadsDOMHandler::DangerPromptDone(
409     int download_id,
410     DownloadDangerPrompt::Action action) {
411   if (action != DownloadDangerPrompt::ACCEPT)
412     return;
413   download::DownloadItem* item = NULL;
414   if (GetMainNotifierManager())
415     item = GetMainNotifierManager()->GetDownload(download_id);
416   if (!item && GetOriginalNotifierManager())
417     item = GetOriginalNotifierManager()->GetDownload(download_id);
418   if (!item || item->IsDone())
419     return;
420   CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS);
421 
422   // If a download is mixed content, validate that first. Is most cases, mixed
423   // content warnings will occur first, but in the worst case scenario, we show
424   // a dangerous warning twice. That's better than showing a mixed content
425   // warning, then dismissing the dangerous download warning. Since mixed
426   // content downloads triggering the UI are temporary and rare to begin with,
427   // this should very rarely occur.
428   if (item->IsMixedContent()) {
429     item->ValidateMixedContentDownload();
430     return;
431   }
432 
433   item->ValidateDangerousDownload();
434 }
435 
IsDeletingHistoryAllowed()436 bool DownloadsDOMHandler::IsDeletingHistoryAllowed() {
437   content::DownloadManager* manager = GetMainNotifierManager();
438   return manager &&
439          Profile::FromBrowserContext(manager->GetBrowserContext())->
440              GetPrefs()->GetBoolean(prefs::kAllowDeletingBrowserHistory);
441 }
442 
GetDownloadByStringId(const std::string & id)443 download::DownloadItem* DownloadsDOMHandler::GetDownloadByStringId(
444     const std::string& id) {
445   uint64_t id_num;
446   if (!base::StringToUint64(id, &id_num)) {
447     NOTREACHED();
448     return nullptr;
449   }
450 
451   return GetDownloadById(static_cast<uint32_t>(id_num));
452 }
453 
GetDownloadById(uint32_t id)454 download::DownloadItem* DownloadsDOMHandler::GetDownloadById(uint32_t id) {
455   download::DownloadItem* item = NULL;
456   if (GetMainNotifierManager())
457     item = GetMainNotifierManager()->GetDownload(id);
458   if (!item && GetOriginalNotifierManager())
459     item = GetOriginalNotifierManager()->GetDownload(id);
460   return item;
461 }
462 
GetWebUIWebContents()463 content::WebContents* DownloadsDOMHandler::GetWebUIWebContents() {
464   return web_ui_->GetWebContents();
465 }
466 
CheckForRemovedFiles()467 void DownloadsDOMHandler::CheckForRemovedFiles() {
468   if (GetMainNotifierManager())
469     GetMainNotifierManager()->CheckForHistoryFilesRemoval();
470   if (GetOriginalNotifierManager())
471     GetOriginalNotifierManager()->CheckForHistoryFilesRemoval();
472 }
473 
RemoveDownloadInArgs(const std::string & id)474 void DownloadsDOMHandler::RemoveDownloadInArgs(const std::string& id) {
475   download::DownloadItem* file = GetDownloadByStringId(id);
476   if (!file)
477     return;
478 
479   DownloadVector downloads;
480   downloads.push_back(file);
481   RemoveDownloads(downloads);
482 }
483