1 // Copyright 2019 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 "weblayer/browser/download_manager_delegate_impl.h"
6 
7 #include "base/files/file_util.h"
8 #include "base/task/post_task.h"
9 #include "base/task/thread_pool.h"
10 #include "base/threading/sequenced_task_runner_handle.h"
11 #include "build/build_config.h"
12 #include "components/download/public/common/download_item.h"
13 #include "components/prefs/pref_service.h"
14 #include "content/public/browser/browser_task_traits.h"
15 #include "content/public/browser/download_item_utils.h"
16 #include "content/public/browser/download_manager.h"
17 #include "net/base/filename_util.h"
18 #include "weblayer/browser/browser_context_impl.h"
19 #include "weblayer/browser/browser_process.h"
20 #include "weblayer/browser/download_impl.h"
21 #include "weblayer/browser/download_manager_delegate_impl.h"
22 #include "weblayer/browser/profile_impl.h"
23 #include "weblayer/browser/tab_impl.h"
24 #include "weblayer/public/download_delegate.h"
25 
26 namespace weblayer {
27 
28 namespace {
29 
GenerateFilename(const GURL & url,const std::string & content_disposition,const std::string & suggested_filename,const std::string & mime_type,const base::FilePath & suggested_directory,base::OnceCallback<void (const base::FilePath &)> callback)30 void GenerateFilename(
31     const GURL& url,
32     const std::string& content_disposition,
33     const std::string& suggested_filename,
34     const std::string& mime_type,
35     const base::FilePath& suggested_directory,
36     base::OnceCallback<void(const base::FilePath&)> callback) {
37   base::FilePath generated_name =
38       net::GenerateFileName(url, content_disposition, std::string(),
39                             suggested_filename, mime_type, "download");
40 
41   if (!base::PathExists(suggested_directory))
42     base::CreateDirectory(suggested_directory);
43 
44   base::FilePath suggested_path(suggested_directory.Append(generated_name));
45   base::PostTask(FROM_HERE, {content::BrowserThread::UI},
46                  base::BindOnce(std::move(callback), suggested_path));
47 }
48 
49 }  // namespace
50 
51 const char kDownloadNextIDPref[] = "weblayer_download_next_id";
52 
DownloadManagerDelegateImpl(content::DownloadManager * download_manager)53 DownloadManagerDelegateImpl::DownloadManagerDelegateImpl(
54     content::DownloadManager* download_manager)
55     : download_manager_(download_manager) {
56   download_manager_->AddObserver(this);
57 
58   // WebLayer doesn't use a history DB as the in-progress database maintained by
59   // the download component is enough. However the download code still depends
60   // this notification. TODO(jam): update download code to handle this.
61   download_manager_->PostInitialization(
62       content::DownloadManager::DOWNLOAD_INITIALIZATION_DEPENDENCY_HISTORY_DB);
63 }
64 
~DownloadManagerDelegateImpl()65 DownloadManagerDelegateImpl::~DownloadManagerDelegateImpl() {
66   download_manager_->RemoveObserver(this);
67   // Match the AddObserver calls added in OnDownloadCreated to avoid UaF.
68   download::SimpleDownloadManager::DownloadVector downloads;
69   download_manager_->GetAllDownloads(&downloads);
70   for (auto* download : downloads)
71     download->RemoveObserver(this);
72 }
73 
GetNextId(content::DownloadIdCallback callback)74 void DownloadManagerDelegateImpl::GetNextId(
75     content::DownloadIdCallback callback) {
76   // Need to return a unique id, even across crashes, to avoid notification
77   // intents with different data (e.g. notification GUID) getting dup'd. This is
78   // also persisted in the on-disk download database to support resumption.
79   auto* local_state = BrowserProcess::GetInstance()->GetLocalState();
80   std::move(callback).Run(local_state->GetInteger(kDownloadNextIDPref));
81 }
82 
DetermineDownloadTarget(download::DownloadItem * item,content::DownloadTargetCallback * callback)83 bool DownloadManagerDelegateImpl::DetermineDownloadTarget(
84     download::DownloadItem* item,
85     content::DownloadTargetCallback* callback) {
86   if (!item->GetForcedFilePath().empty()) {
87     std::move(*callback).Run(
88         item->GetForcedFilePath(),
89         download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
90         download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
91         download::DownloadItem::MixedContentStatus::UNKNOWN,
92         item->GetForcedFilePath(), download::DOWNLOAD_INTERRUPT_REASON_NONE);
93     return true;
94   }
95 
96   auto filename_determined_callback = base::BindOnce(
97       &DownloadManagerDelegateImpl::OnDownloadPathGenerated,
98       weak_ptr_factory_.GetWeakPtr(), item->GetId(), std::move(*callback));
99 
100   auto* browser_context = content::DownloadItemUtils::GetBrowserContext(item);
101   base::FilePath default_download_path;
102   GetSaveDir(browser_context, nullptr, &default_download_path);
103 
104   base::ThreadPool::PostTask(
105       FROM_HERE,
106       {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
107        base::TaskPriority::USER_VISIBLE},
108       base::BindOnce(
109           GenerateFilename, item->GetURL(), item->GetContentDisposition(),
110           item->GetSuggestedFilename(), item->GetMimeType(),
111           default_download_path, std::move(filename_determined_callback)));
112   return true;
113 }
114 
InterceptDownloadIfApplicable(const GURL & url,const std::string & user_agent,const std::string & content_disposition,const std::string & mime_type,const std::string & request_origin,int64_t content_length,bool is_transient,content::WebContents * web_contents)115 bool DownloadManagerDelegateImpl::InterceptDownloadIfApplicable(
116     const GURL& url,
117     const std::string& user_agent,
118     const std::string& content_disposition,
119     const std::string& mime_type,
120     const std::string& request_origin,
121     int64_t content_length,
122     bool is_transient,
123     content::WebContents* web_contents) {
124   // If there's no DownloadDelegate, the download is simply dropped.
125   auto* delegate = GetDelegate(web_contents);
126   if (!delegate)
127     return true;
128 
129   return delegate->InterceptDownload(url, user_agent, content_disposition,
130                                      mime_type, content_length);
131 }
132 
GetSaveDir(content::BrowserContext * browser_context,base::FilePath * website_save_dir,base::FilePath * download_save_dir)133 void DownloadManagerDelegateImpl::GetSaveDir(
134     content::BrowserContext* browser_context,
135     base::FilePath* website_save_dir,
136     base::FilePath* download_save_dir) {
137   auto* browser_context_impl =
138       static_cast<BrowserContextImpl*>(browser_context);
139   auto* profile = browser_context_impl->profile_impl();
140   if (!profile->download_directory().empty())
141     *download_save_dir = profile->download_directory();
142 }
143 
CheckDownloadAllowed(const content::WebContents::Getter & web_contents_getter,const GURL & url,const std::string & request_method,base::Optional<url::Origin> request_initiator,bool from_download_cross_origin_redirect,bool content_initiated,content::CheckDownloadAllowedCallback check_download_allowed_cb)144 void DownloadManagerDelegateImpl::CheckDownloadAllowed(
145     const content::WebContents::Getter& web_contents_getter,
146     const GURL& url,
147     const std::string& request_method,
148     base::Optional<url::Origin> request_initiator,
149     bool from_download_cross_origin_redirect,
150     bool content_initiated,
151     content::CheckDownloadAllowedCallback check_download_allowed_cb) {
152   // If there's no DownloadDelegate, the download is simply dropped.
153   auto* delegate = GetDelegate(web_contents_getter.Run());
154   if (!delegate) {
155     std::move(check_download_allowed_cb).Run(false);
156     return;
157   }
158 
159   delegate->AllowDownload(url, request_method, request_initiator,
160                           std::move(check_download_allowed_cb));
161 }
162 
OnDownloadCreated(content::DownloadManager * manager,download::DownloadItem * item)163 void DownloadManagerDelegateImpl::OnDownloadCreated(
164     content::DownloadManager* manager,
165     download::DownloadItem* item) {
166   item->AddObserver(this);
167   // Create a DownloadImpl which will be owned by |item|.
168   DownloadImpl::Create(item);
169 
170   auto* local_state = BrowserProcess::GetInstance()->GetLocalState();
171   int next_id = local_state->GetInteger(kDownloadNextIDPref);
172   if (item->GetId() >= static_cast<uint32_t>(next_id)) {
173     next_id = item->GetId();
174     // Reset the counter when it gets close to max value of unsigned 32 bit
175     // integer since that's what the download system persists.
176     if (++next_id == (std::numeric_limits<uint32_t>::max() / 2) - 1)
177       next_id = 0;
178     local_state->SetInteger(kDownloadNextIDPref, next_id);
179   }
180 
181   if (item->GetLastReason() == download::DOWNLOAD_INTERRUPT_REASON_CRASH &&
182       item->CanResume() &&
183       // Don't automatically resume downloads which were previously paused.
184       !item->IsPaused()) {
185     DownloadImpl::Get(item)->Resume();
186   }
187 
188   auto* delegate = GetDelegate(item);
189   if (delegate)
190     delegate->DownloadStarted(DownloadImpl::Get(item));
191 }
192 
OnDownloadDropped(content::DownloadManager * manager)193 void DownloadManagerDelegateImpl::OnDownloadDropped(
194     content::DownloadManager* manager) {
195   if (download_dropped_callback_)
196     download_dropped_callback_.Run();
197 }
198 
OnManagerInitialized()199 void DownloadManagerDelegateImpl::OnManagerInitialized() {
200   auto* browser_context_impl =
201       static_cast<BrowserContextImpl*>(download_manager_->GetBrowserContext());
202   auto* profile = browser_context_impl->profile_impl();
203   profile->DownloadsInitialized();
204 }
205 
OnDownloadUpdated(download::DownloadItem * item)206 void DownloadManagerDelegateImpl::OnDownloadUpdated(
207     download::DownloadItem* item) {
208   auto* delegate = GetDelegate(item);
209   if (item->GetState() == download::DownloadItem::COMPLETE ||
210       item->GetState() == download::DownloadItem::CANCELLED ||
211       item->GetState() == download::DownloadItem::INTERRUPTED) {
212     // Stop observing now to ensure we only send one complete/fail notification.
213     item->RemoveObserver(this);
214 
215     if (item->GetState() == download::DownloadItem::COMPLETE)
216       delegate->DownloadCompleted(DownloadImpl::Get(item));
217     else
218       delegate->DownloadFailed(DownloadImpl::Get(item));
219 
220     // Needs to happen asynchronously to avoid nested observer calls.
221     base::SequencedTaskRunnerHandle::Get()->PostTask(
222         FROM_HERE,
223         base::BindOnce(&DownloadManagerDelegateImpl::RemoveItem,
224                        weak_ptr_factory_.GetWeakPtr(), item->GetGuid()));
225     return;
226   }
227 
228   if (delegate)
229     delegate->DownloadProgressChanged(DownloadImpl::Get(item));
230 }
231 
OnDownloadPathGenerated(uint32_t download_id,content::DownloadTargetCallback callback,const base::FilePath & suggested_path)232 void DownloadManagerDelegateImpl::OnDownloadPathGenerated(
233     uint32_t download_id,
234     content::DownloadTargetCallback callback,
235     const base::FilePath& suggested_path) {
236   std::move(callback).Run(
237       suggested_path, download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
238       download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
239       download::DownloadItem::MixedContentStatus::UNKNOWN,
240       suggested_path.AddExtension(FILE_PATH_LITERAL(".crdownload")),
241       download::DOWNLOAD_INTERRUPT_REASON_NONE);
242 }
243 
RemoveItem(const std::string & guid)244 void DownloadManagerDelegateImpl::RemoveItem(const std::string& guid) {
245   auto* item = download_manager_->GetDownloadByGuid(guid);
246   if (item)
247     item->Remove();
248 }
249 
GetDelegate(content::WebContents * web_contents)250 DownloadDelegate* DownloadManagerDelegateImpl::GetDelegate(
251     content::WebContents* web_contents) {
252   if (!web_contents)
253     return nullptr;
254 
255   return GetDelegate(web_contents->GetBrowserContext());
256 }
257 
GetDelegate(content::BrowserContext * browser_context)258 DownloadDelegate* DownloadManagerDelegateImpl::GetDelegate(
259     content::BrowserContext* browser_context) {
260   auto* profile = ProfileImpl::FromBrowserContext(browser_context);
261   if (!profile)
262     return nullptr;
263 
264   return profile->download_delegate();
265 }
266 
GetDelegate(download::DownloadItem * item)267 DownloadDelegate* DownloadManagerDelegateImpl::GetDelegate(
268     download::DownloadItem* item) {
269   auto* browser_context = content::DownloadItemUtils::GetBrowserContext(item);
270   return GetDelegate(browser_context);
271 }
272 
273 }  // namespace weblayer
274