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