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