• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "libcef/browser/download_manager_delegate.h"
6 
7 #include <tuple>
8 
9 #include "include/cef_download_handler.h"
10 #include "libcef/browser/alloy/alloy_browser_host_impl.h"
11 #include "libcef/browser/context.h"
12 #include "libcef/browser/download_item_impl.h"
13 #include "libcef/browser/thread_util.h"
14 
15 #include "base/bind.h"
16 #include "base/files/file_util.h"
17 #include "base/logging.h"
18 #include "base/path_service.h"
19 #include "base/strings/string_util.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "chrome/common/chrome_constants.h"
22 #include "content/public/browser/browser_context.h"
23 #include "content/public/browser/download_item_utils.h"
24 #include "content/public/browser/web_contents.h"
25 #include "net/base/filename_util.h"
26 #include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
27 
28 using content::DownloadManager;
29 using content::WebContents;
30 using download::DownloadItem;
31 
32 namespace {
33 
34 // Helper function to retrieve the CefDownloadHandler.
GetDownloadHandler(CefRefPtr<AlloyBrowserHostImpl> browser)35 CefRefPtr<CefDownloadHandler> GetDownloadHandler(
36     CefRefPtr<AlloyBrowserHostImpl> browser) {
37   CefRefPtr<CefClient> client = browser->GetClient();
38   if (client.get())
39     return client->GetDownloadHandler();
40   return nullptr;
41 }
42 
43 // CefBeforeDownloadCallback implementation.
44 class CefBeforeDownloadCallbackImpl : public CefBeforeDownloadCallback {
45  public:
CefBeforeDownloadCallbackImpl(const base::WeakPtr<DownloadManager> & manager,uint32 download_id,const base::FilePath & suggested_name,content::DownloadTargetCallback callback)46   CefBeforeDownloadCallbackImpl(const base::WeakPtr<DownloadManager>& manager,
47                                 uint32 download_id,
48                                 const base::FilePath& suggested_name,
49                                 content::DownloadTargetCallback callback)
50       : manager_(manager),
51         download_id_(download_id),
52         suggested_name_(suggested_name),
53         callback_(std::move(callback)) {}
54 
55   CefBeforeDownloadCallbackImpl(const CefBeforeDownloadCallbackImpl&) = delete;
56   CefBeforeDownloadCallbackImpl& operator=(
57       const CefBeforeDownloadCallbackImpl&) = delete;
58 
Continue(const CefString & download_path,bool show_dialog)59   void Continue(const CefString& download_path, bool show_dialog) override {
60     if (CEF_CURRENTLY_ON_UIT()) {
61       if (download_id_ <= 0)
62         return;
63 
64       if (manager_) {
65         base::FilePath path = base::FilePath(download_path);
66         CEF_POST_USER_VISIBLE_TASK(
67             base::BindOnce(&CefBeforeDownloadCallbackImpl::GenerateFilename,
68                            manager_, download_id_, suggested_name_, path,
69                            show_dialog, std::move(callback_)));
70       }
71 
72       download_id_ = 0;
73     } else {
74       CEF_POST_TASK(CEF_UIT,
75                     base::BindOnce(&CefBeforeDownloadCallbackImpl::Continue,
76                                    this, download_path, show_dialog));
77     }
78   }
79 
80  private:
GenerateFilename(base::WeakPtr<DownloadManager> manager,uint32 download_id,const base::FilePath & suggested_name,const base::FilePath & download_path,bool show_dialog,content::DownloadTargetCallback callback)81   static void GenerateFilename(base::WeakPtr<DownloadManager> manager,
82                                uint32 download_id,
83                                const base::FilePath& suggested_name,
84                                const base::FilePath& download_path,
85                                bool show_dialog,
86                                content::DownloadTargetCallback callback) {
87     CEF_REQUIRE_BLOCKING();
88 
89     base::FilePath suggested_path = download_path;
90     if (!suggested_path.empty()) {
91       // Create the directory if necessary.
92       base::FilePath dir_path = suggested_path.DirName();
93       if (!base::DirectoryExists(dir_path) &&
94           !base::CreateDirectory(dir_path)) {
95         NOTREACHED() << "failed to create the download directory";
96         suggested_path.clear();
97       }
98     }
99 
100     if (suggested_path.empty()) {
101       if (base::PathService::Get(base::DIR_TEMP, &suggested_path)) {
102         // Use the temp directory.
103         suggested_path = suggested_path.Append(suggested_name);
104       } else {
105         // Use the current working directory.
106         suggested_path = suggested_name;
107       }
108     }
109 
110     CEF_POST_TASK(
111         CEF_UIT,
112         base::BindOnce(&CefBeforeDownloadCallbackImpl::ChooseDownloadPath,
113                        manager, download_id, suggested_path, show_dialog,
114                        std::move(callback)));
115   }
116 
ChooseDownloadPath(base::WeakPtr<DownloadManager> manager,uint32 download_id,const base::FilePath & suggested_path,bool show_dialog,content::DownloadTargetCallback callback)117   static void ChooseDownloadPath(base::WeakPtr<DownloadManager> manager,
118                                  uint32 download_id,
119                                  const base::FilePath& suggested_path,
120                                  bool show_dialog,
121                                  content::DownloadTargetCallback callback) {
122     if (!manager)
123       return;
124 
125     DownloadItem* item = manager->GetDownload(download_id);
126     if (!item || item->GetState() != DownloadItem::IN_PROGRESS)
127       return;
128 
129     bool handled = false;
130 
131     if (show_dialog) {
132       WebContents* web_contents =
133           content::DownloadItemUtils::GetWebContents(item);
134       CefRefPtr<AlloyBrowserHostImpl> browser =
135           AlloyBrowserHostImpl::GetBrowserForContents(web_contents);
136       if (browser.get()) {
137         handled = true;
138 
139         CefFileDialogRunner::FileChooserParams params;
140         params.mode = blink::mojom::FileChooserParams::Mode::kSave;
141         if (!suggested_path.empty()) {
142           params.default_file_name = suggested_path;
143           if (!suggested_path.Extension().empty()) {
144             params.accept_types.push_back(
145                 CefString(suggested_path.Extension()));
146           }
147         }
148 
149         browser->RunFileChooser(
150             params,
151             base::BindOnce(
152                 &CefBeforeDownloadCallbackImpl::ChooseDownloadPathCallback,
153                 std::move(callback)));
154       }
155     }
156 
157     if (!handled) {
158       std::move(callback).Run(
159           suggested_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
160           download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
161           download::DownloadItem::MixedContentStatus::UNKNOWN, suggested_path,
162           absl::nullopt /*download_schedule*/,
163           download::DOWNLOAD_INTERRUPT_REASON_NONE);
164     }
165   }
166 
ChooseDownloadPathCallback(content::DownloadTargetCallback callback,int selected_accept_filter,const std::vector<base::FilePath> & file_paths)167   static void ChooseDownloadPathCallback(
168       content::DownloadTargetCallback callback,
169       int selected_accept_filter,
170       const std::vector<base::FilePath>& file_paths) {
171     DCHECK_LE(file_paths.size(), (size_t)1);
172 
173     base::FilePath path;
174     if (file_paths.size() > 0)
175       path = file_paths.front();
176 
177     // The download will be cancelled if |path| is empty.
178     std::move(callback).Run(path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
179                             download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
180                             download::DownloadItem::MixedContentStatus::UNKNOWN,
181                             path, absl::nullopt /*download_schedule*/,
182                             download::DOWNLOAD_INTERRUPT_REASON_NONE);
183   }
184 
185   base::WeakPtr<DownloadManager> manager_;
186   uint32 download_id_;
187   base::FilePath suggested_name_;
188   content::DownloadTargetCallback callback_;
189 
190   IMPLEMENT_REFCOUNTING(CefBeforeDownloadCallbackImpl);
191 };
192 
193 // CefDownloadItemCallback implementation.
194 class CefDownloadItemCallbackImpl : public CefDownloadItemCallback {
195  public:
CefDownloadItemCallbackImpl(const base::WeakPtr<DownloadManager> & manager,uint32 download_id)196   explicit CefDownloadItemCallbackImpl(
197       const base::WeakPtr<DownloadManager>& manager,
198       uint32 download_id)
199       : manager_(manager), download_id_(download_id) {}
200 
201   CefDownloadItemCallbackImpl(const CefDownloadItemCallbackImpl&) = delete;
202   CefDownloadItemCallbackImpl& operator=(const CefDownloadItemCallbackImpl&) =
203       delete;
204 
Cancel()205   void Cancel() override {
206     CEF_POST_TASK(CEF_UIT,
207                   base::BindOnce(&CefDownloadItemCallbackImpl::DoCancel, this));
208   }
209 
Pause()210   void Pause() override {
211     CEF_POST_TASK(CEF_UIT,
212                   base::BindOnce(&CefDownloadItemCallbackImpl::DoPause, this));
213   }
214 
Resume()215   void Resume() override {
216     CEF_POST_TASK(CEF_UIT,
217                   base::BindOnce(&CefDownloadItemCallbackImpl::DoResume, this));
218   }
219 
220  private:
DoCancel()221   void DoCancel() {
222     if (download_id_ <= 0)
223       return;
224 
225     if (manager_) {
226       DownloadItem* item = manager_->GetDownload(download_id_);
227       if (item && item->GetState() == DownloadItem::IN_PROGRESS)
228         item->Cancel(true);
229     }
230 
231     download_id_ = 0;
232   }
233 
DoPause()234   void DoPause() {
235     if (download_id_ <= 0)
236       return;
237 
238     if (manager_) {
239       DownloadItem* item = manager_->GetDownload(download_id_);
240       if (item && item->GetState() == DownloadItem::IN_PROGRESS)
241         item->Pause();
242     }
243   }
244 
DoResume()245   void DoResume() {
246     if (download_id_ <= 0)
247       return;
248 
249     if (manager_) {
250       DownloadItem* item = manager_->GetDownload(download_id_);
251       if (item && item->CanResume())
252         item->Resume(true);
253     }
254   }
255 
256   base::WeakPtr<DownloadManager> manager_;
257   uint32 download_id_;
258 
259   IMPLEMENT_REFCOUNTING(CefDownloadItemCallbackImpl);
260 };
261 
262 }  // namespace
263 
CefDownloadManagerDelegate(DownloadManager * manager)264 CefDownloadManagerDelegate::CefDownloadManagerDelegate(DownloadManager* manager)
265     : manager_(manager), manager_ptr_factory_(manager) {
266   DCHECK(manager);
267   manager->AddObserver(this);
268 
269   DownloadManager::DownloadVector items;
270   manager->GetAllDownloads(&items);
271   DownloadManager::DownloadVector::const_iterator it = items.begin();
272   for (; it != items.end(); ++it)
273     OnDownloadCreated(manager, *it);
274 }
275 
~CefDownloadManagerDelegate()276 CefDownloadManagerDelegate::~CefDownloadManagerDelegate() {
277   if (manager_) {
278     manager_->SetDelegate(nullptr);
279     manager_->RemoveObserver(this);
280   }
281 
282   while (!item_browser_map_.empty())
283     OnDownloadDestroyed(item_browser_map_.begin()->first);
284 }
285 
OnDownloadUpdated(DownloadItem * download)286 void CefDownloadManagerDelegate::OnDownloadUpdated(DownloadItem* download) {
287   CefRefPtr<AlloyBrowserHostImpl> browser = GetBrowser(download);
288   CefRefPtr<CefDownloadHandler> handler;
289   if (browser.get())
290     handler = GetDownloadHandler(browser);
291 
292   if (handler.get()) {
293     CefRefPtr<CefDownloadItemImpl> download_item(
294         new CefDownloadItemImpl(download));
295     CefRefPtr<CefDownloadItemCallback> callback(new CefDownloadItemCallbackImpl(
296         manager_ptr_factory_.GetWeakPtr(), download->GetId()));
297 
298     handler->OnDownloadUpdated(browser.get(), download_item.get(), callback);
299 
300     std::ignore = download_item->Detach(nullptr);
301   }
302 }
303 
OnDownloadDestroyed(DownloadItem * item)304 void CefDownloadManagerDelegate::OnDownloadDestroyed(DownloadItem* item) {
305   item->RemoveObserver(this);
306 
307   AlloyBrowserHostImpl* browser = nullptr;
308 
309   ItemBrowserMap::iterator it = item_browser_map_.find(item);
310   DCHECK(it != item_browser_map_.end());
311   if (it != item_browser_map_.end()) {
312     browser = it->second;
313     item_browser_map_.erase(it);
314   }
315 
316   if (browser) {
317     // Determine if any remaining DownloadItems are associated with the same
318     // browser. If not, then unregister as an observer.
319     bool has_remaining = false;
320     ItemBrowserMap::const_iterator it2 = item_browser_map_.begin();
321     for (; it2 != item_browser_map_.end(); ++it2) {
322       if (it2->second == browser) {
323         has_remaining = true;
324         break;
325       }
326     }
327 
328     if (!has_remaining)
329       browser->RemoveObserver(this);
330   }
331 }
332 
OnDownloadCreated(DownloadManager * manager,DownloadItem * item)333 void CefDownloadManagerDelegate::OnDownloadCreated(DownloadManager* manager,
334                                                    DownloadItem* item) {
335   // This callback may arrive after DetermineDownloadTarget, so we allow
336   // association from either method.
337   CefRefPtr<AlloyBrowserHostImpl> browser = GetOrAssociateBrowser(item);
338   if (!browser) {
339     // If the download is rejected (e.g. ALT+click on an invalid protocol link)
340     // then an "interrupted" download will be started via DownloadManagerImpl::
341     // StartDownloadWithId (originating from CreateInterruptedDownload) with no
342     // associated WebContents and consequently no associated CEF browser. In
343     // that case DetermineDownloadTarget will be called before this method.
344     // TODO(cef): Figure out how to expose this via a client callback.
345     const std::vector<GURL>& url_chain = item->GetUrlChain();
346     if (!url_chain.empty()) {
347       LOG(INFO) << "Rejected download of " << url_chain.back().spec();
348     }
349     item->Cancel(true);
350   }
351 }
352 
ManagerGoingDown(DownloadManager * manager)353 void CefDownloadManagerDelegate::ManagerGoingDown(DownloadManager* manager) {
354   DCHECK_EQ(manager, manager_);
355   manager->SetDelegate(nullptr);
356   manager->RemoveObserver(this);
357   manager_ptr_factory_.InvalidateWeakPtrs();
358   manager_ = nullptr;
359 }
360 
DetermineDownloadTarget(DownloadItem * item,content::DownloadTargetCallback * callback)361 bool CefDownloadManagerDelegate::DetermineDownloadTarget(
362     DownloadItem* item,
363     content::DownloadTargetCallback* callback) {
364   if (!item->GetForcedFilePath().empty()) {
365     std::move(*callback).Run(
366         item->GetForcedFilePath(), DownloadItem::TARGET_DISPOSITION_OVERWRITE,
367         download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
368         download::DownloadItem::MixedContentStatus::UNKNOWN,
369         item->GetForcedFilePath(), absl::nullopt /*download_schedule*/,
370         download::DOWNLOAD_INTERRUPT_REASON_NONE);
371     return true;
372   }
373 
374   // This callback may arrive before OnDownloadCreated, so we allow association
375   // from either method.
376   CefRefPtr<AlloyBrowserHostImpl> browser = GetOrAssociateBrowser(item);
377   CefRefPtr<CefDownloadHandler> handler;
378   if (browser.get())
379     handler = GetDownloadHandler(browser);
380 
381   if (handler.get()) {
382     base::FilePath suggested_name = net::GenerateFileName(
383         item->GetURL(), item->GetContentDisposition(), std::string(),
384         item->GetSuggestedFilename(), item->GetMimeType(), "download");
385 
386     CefRefPtr<CefDownloadItemImpl> download_item(new CefDownloadItemImpl(item));
387     CefRefPtr<CefBeforeDownloadCallback> callbackObj(
388         new CefBeforeDownloadCallbackImpl(manager_ptr_factory_.GetWeakPtr(),
389                                           item->GetId(), suggested_name,
390                                           std::move(*callback)));
391 
392     handler->OnBeforeDownload(browser.get(), download_item.get(),
393                               suggested_name.value(), callbackObj);
394 
395     std::ignore = download_item->Detach(nullptr);
396   }
397 
398   return true;
399 }
400 
GetNextId(content::DownloadIdCallback callback)401 void CefDownloadManagerDelegate::GetNextId(
402     content::DownloadIdCallback callback) {
403   static uint32 next_id = DownloadItem::kInvalidId + 1;
404   std::move(callback).Run(next_id++);
405 }
406 
ApplicationClientIdForFileScanning()407 std::string CefDownloadManagerDelegate::ApplicationClientIdForFileScanning() {
408   const CefSettings& settings = CefContext::Get()->settings();
409   if (settings.application_client_id_for_file_scanning.length > 0) {
410     return CefString(&settings.application_client_id_for_file_scanning)
411         .ToString();
412   }
413   return std::string();
414 }
415 
OnBrowserDestroyed(CefBrowserHostBase * browser)416 void CefDownloadManagerDelegate::OnBrowserDestroyed(
417     CefBrowserHostBase* browser) {
418   ItemBrowserMap::iterator it = item_browser_map_.begin();
419   for (; it != item_browser_map_.end(); ++it) {
420     if (it->second == browser) {
421       // Don't call back into browsers that have been destroyed. We're not
422       // canceling the download so it will continue silently until it completes
423       // or until the associated browser context is destroyed.
424       it->second = nullptr;
425     }
426   }
427 }
428 
GetOrAssociateBrowser(download::DownloadItem * item)429 AlloyBrowserHostImpl* CefDownloadManagerDelegate::GetOrAssociateBrowser(
430     download::DownloadItem* item) {
431   ItemBrowserMap::const_iterator it = item_browser_map_.find(item);
432   if (it != item_browser_map_.end())
433     return it->second;
434 
435   AlloyBrowserHostImpl* browser = nullptr;
436   content::WebContents* contents =
437       content::DownloadItemUtils::GetWebContents(item);
438   if (contents) {
439     browser = AlloyBrowserHostImpl::GetBrowserForContents(contents).get();
440     DCHECK(browser);
441   }
442   if (!browser)
443     return nullptr;
444 
445   item->AddObserver(this);
446 
447   item_browser_map_.insert(std::make_pair(item, browser));
448 
449   // Register as an observer so that we can cancel associated DownloadItems when
450   // the browser is destroyed.
451   if (!browser->HasObserver(this))
452     browser->AddObserver(this);
453 
454   return browser;
455 }
456 
GetBrowser(DownloadItem * item)457 AlloyBrowserHostImpl* CefDownloadManagerDelegate::GetBrowser(
458     DownloadItem* item) {
459   ItemBrowserMap::const_iterator it = item_browser_map_.find(item);
460   if (it != item_browser_map_.end())
461     return it->second;
462 
463   // If the download is rejected (e.g. ALT+click on an invalid protocol link)
464   // then an "interrupted" download will be started via DownloadManagerImpl::
465   // StartDownloadWithId (originating from CreateInterruptedDownload) with no
466   // associated WebContents and consequently no associated CEF browser. In that
467   // case DetermineDownloadTarget will be called before OnDownloadCreated.
468   DCHECK(!content::DownloadItemUtils::GetWebContents(item));
469   return nullptr;
470 }
471