OLD | NEW |
| (Empty) |
1 // Copyright (c) 2006-2008 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 <time.h> | |
6 | |
7 #include "chrome/browser/download_manager.h" | |
8 | |
9 #include "base/file_util.h" | |
10 #include "base/logging.h" | |
11 #include "base/message_loop.h" | |
12 #include "base/path_service.h" | |
13 #include "base/registry.h" | |
14 #include "base/string_util.h" | |
15 #include "base/task.h" | |
16 #include "base/thread.h" | |
17 #include "base/timer.h" | |
18 #include "base/win_util.h" | |
19 #include "chrome/browser/browser_list.h" | |
20 #include "chrome/browser/browser_process.h" | |
21 #include "chrome/browser/download_file.h" | |
22 #include "chrome/browser/download_util.h" | |
23 #include "chrome/browser/profile.h" | |
24 #include "chrome/browser/render_process_host.h" | |
25 #include "chrome/browser/render_view_host.h" | |
26 #include "chrome/browser/resource_dispatcher_host.h" | |
27 #include "chrome/browser/tab_util.h" | |
28 #include "chrome/browser/web_contents.h" | |
29 #include "chrome/common/chrome_paths.h" | |
30 #include "chrome/common/l10n_util.h" | |
31 #include "chrome/common/notification_service.h" | |
32 #include "chrome/common/pref_names.h" | |
33 #include "chrome/common/pref_service.h" | |
34 #include "chrome/common/stl_util-inl.h" | |
35 #include "chrome/common/win_util.h" | |
36 #include "googleurl/src/gurl.h" | |
37 #include "net/base/mime_util.h" | |
38 #include "net/base/net_util.h" | |
39 #include "net/url_request/url_request_context.h" | |
40 | |
41 #include "generated_resources.h" | |
42 | |
43 // Periodically update our observers. | |
44 class DownloadItemUpdateTask : public Task { | |
45 public: | |
46 explicit DownloadItemUpdateTask(DownloadItem* item) : item_(item) {} | |
47 void Run() { if (item_) item_->UpdateObservers(); } | |
48 | |
49 private: | |
50 DownloadItem* item_; | |
51 }; | |
52 | |
53 // Update frequency (milliseconds). | |
54 static const int kUpdateTimeMs = 1000; | |
55 | |
56 // Our download table ID starts at 1, so we use 0 to represent a download that | |
57 // has started, but has not yet had its data persisted in the table. We use fake | |
58 // database handles in incognito mode starting at -1 and progressly getting more | |
59 // negative. | |
60 static const int kUninitializedHandle = 0; | |
61 | |
62 // Attempts to modify |path| to be a non-existing path. | |
63 // Returns true if |path| points to a non-existing path upon return. | |
64 static bool UniquifyPath(std::wstring* path) { | |
65 DCHECK(path); | |
66 const int kMaxAttempts = 100; | |
67 | |
68 if (!file_util::PathExists(*path)) | |
69 return true; | |
70 | |
71 std::wstring new_path; | |
72 for (int count = 1; count <= kMaxAttempts; ++count) { | |
73 new_path.assign(*path); | |
74 file_util::InsertBeforeExtension(&new_path, StringPrintf(L" (%d)", count)); | |
75 | |
76 if (!file_util::PathExists(new_path)) { | |
77 path->swap(new_path); | |
78 return true; | |
79 } | |
80 } | |
81 | |
82 return false; | |
83 } | |
84 | |
85 static bool DownloadPathIsDangerous(const std::wstring& download_path) { | |
86 std::wstring desktop_dir; | |
87 if (!PathService::Get(chrome::DIR_USER_DESKTOP, &desktop_dir)) { | |
88 NOTREACHED(); | |
89 return false; | |
90 } | |
91 return (download_path == desktop_dir); | |
92 } | |
93 | |
94 // DownloadItem implementation ------------------------------------------------- | |
95 | |
96 // Constructor for reading from the history service. | |
97 DownloadItem::DownloadItem(const DownloadCreateInfo& info) | |
98 : id_(-1), | |
99 full_path_(info.path), | |
100 url_(info.url), | |
101 total_bytes_(info.total_bytes), | |
102 received_bytes_(info.received_bytes), | |
103 start_tick_(0), | |
104 state_(static_cast<DownloadState>(info.state)), | |
105 start_time_(info.start_time), | |
106 db_handle_(info.db_handle), | |
107 manager_(NULL), | |
108 is_paused_(false), | |
109 open_when_complete_(false), | |
110 render_process_id_(-1), | |
111 request_id_(-1) { | |
112 if (state_ == IN_PROGRESS) | |
113 state_ = CANCELLED; | |
114 Init(false /* don't start progress timer */); | |
115 } | |
116 | |
117 // Constructor for DownloadItem created via user action in the main thread. | |
118 DownloadItem::DownloadItem(int32 download_id, | |
119 const std::wstring& path, | |
120 const std::wstring& url, | |
121 const Time start_time, | |
122 int64 download_size, | |
123 int render_process_id, | |
124 int request_id) | |
125 : id_(download_id), | |
126 full_path_(path), | |
127 url_(url), | |
128 total_bytes_(download_size), | |
129 received_bytes_(0), | |
130 start_tick_(GetTickCount()), | |
131 state_(IN_PROGRESS), | |
132 start_time_(start_time), | |
133 db_handle_(kUninitializedHandle), | |
134 manager_(NULL), | |
135 is_paused_(false), | |
136 open_when_complete_(false), | |
137 render_process_id_(render_process_id), | |
138 request_id_(request_id) { | |
139 Init(true /* start progress timer */); | |
140 } | |
141 | |
142 void DownloadItem::Init(bool start_timer) { | |
143 file_name_ = file_util::GetFilenameFromPath(full_path_); | |
144 if (start_timer) | |
145 StartProgressTimer(); | |
146 } | |
147 | |
148 DownloadItem::~DownloadItem() { | |
149 state_ = REMOVING; | |
150 UpdateObservers(); | |
151 } | |
152 | |
153 void DownloadItem::AddObserver(Observer* observer) { | |
154 observers_.AddObserver(observer); | |
155 } | |
156 | |
157 void DownloadItem::RemoveObserver(Observer* observer) { | |
158 observers_.RemoveObserver(observer); | |
159 } | |
160 | |
161 void DownloadItem::UpdateObservers() { | |
162 FOR_EACH_OBSERVER(Observer, observers_, OnDownloadUpdated(this)); | |
163 } | |
164 | |
165 // If we've received more data than we were expecting (bad server info?), revert | |
166 // to 'unknown size mode'. | |
167 void DownloadItem::UpdateSize(int64 bytes_so_far) { | |
168 received_bytes_ = bytes_so_far; | |
169 if (received_bytes_ > total_bytes_) | |
170 total_bytes_ = 0; | |
171 } | |
172 | |
173 // Updates from the download thread may have been posted while this download | |
174 // was being cancelled in the UI thread, so we'll accept them unless we're | |
175 // complete. | |
176 void DownloadItem::Update(int64 bytes_so_far) { | |
177 if (state_ == COMPLETE) { | |
178 NOTREACHED(); | |
179 return; | |
180 } | |
181 UpdateSize(bytes_so_far); | |
182 UpdateObservers(); | |
183 } | |
184 | |
185 // Triggered by a user action | |
186 void DownloadItem::Cancel(bool update_history) { | |
187 if (state_ != IN_PROGRESS) { | |
188 // Small downloads might be complete before this method has a chance to run. | |
189 return; | |
190 } | |
191 state_ = CANCELLED; | |
192 UpdateObservers(); | |
193 StopProgressTimer(); | |
194 if (update_history) | |
195 manager_->DownloadCancelled(id_); | |
196 } | |
197 | |
198 void DownloadItem::Finished(int64 size) { | |
199 state_ = COMPLETE; | |
200 UpdateSize(size); | |
201 UpdateObservers(); | |
202 StopProgressTimer(); | |
203 } | |
204 | |
205 void DownloadItem::Remove() { | |
206 Cancel(true); | |
207 state_ = REMOVING; | |
208 manager_->RemoveDownload(db_handle_); | |
209 } | |
210 | |
211 void DownloadItem::StartProgressTimer() { | |
212 update_timer_.Start(TimeDelta::FromMilliseconds(kUpdateTimeMs), this, | |
213 &DownloadItem::UpdateObservers); | |
214 } | |
215 | |
216 void DownloadItem::StopProgressTimer() { | |
217 update_timer_.Stop(); | |
218 } | |
219 | |
220 bool DownloadItem::TimeRemaining(TimeDelta* remaining) const { | |
221 if (total_bytes_ <= 0) | |
222 return false; // We never received the content_length for this download. | |
223 | |
224 int64 speed = CurrentSpeed(); | |
225 if (speed == 0) | |
226 return false; | |
227 | |
228 *remaining = | |
229 TimeDelta::FromSeconds((total_bytes_ - received_bytes_) / speed); | |
230 return true; | |
231 } | |
232 | |
233 int64 DownloadItem::CurrentSpeed() const { | |
234 uintptr_t diff = GetTickCount() - start_tick_; | |
235 return diff == 0 ? 0 : received_bytes_ * 1000 / diff; | |
236 } | |
237 | |
238 int DownloadItem::PercentComplete() const { | |
239 int percent = -1; | |
240 if (total_bytes_ > 0) | |
241 percent = static_cast<int>(received_bytes_ * 100.0 / total_bytes_); | |
242 return percent; | |
243 } | |
244 | |
245 void DownloadItem::Rename(const std::wstring& full_path) { | |
246 DCHECK(!full_path.empty()); | |
247 full_path_ = full_path; | |
248 file_name_ = file_util::GetFilenameFromPath(full_path_); | |
249 } | |
250 | |
251 void DownloadItem::TogglePause() { | |
252 DCHECK(state_ == IN_PROGRESS); | |
253 manager_->PauseDownload(id_, !is_paused_); | |
254 is_paused_ = !is_paused_; | |
255 UpdateObservers(); | |
256 } | |
257 | |
258 // DownloadManager implementation ---------------------------------------------- | |
259 | |
260 // static | |
261 void DownloadManager::RegisterUserPrefs(PrefService* prefs) { | |
262 prefs->RegisterBooleanPref(prefs::kPromptForDownload, false); | |
263 prefs->RegisterStringPref(prefs::kDownloadExtensionsToOpen, L""); | |
264 prefs->RegisterBooleanPref(prefs::kDownloadDirUpgraded, false); | |
265 | |
266 // The default download path is userprofile\download. | |
267 std::wstring default_download_path; | |
268 if (!PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_download_path)) { | |
269 NOTREACHED(); | |
270 } | |
271 file_util::AppendToPath(&default_download_path, | |
272 l10n_util::GetString(IDS_DOWNLOAD_DIRECTORY)); | |
273 prefs->RegisterStringPref(prefs::kDownloadDefaultDirectory, | |
274 default_download_path); | |
275 | |
276 // If the download path is dangerous we forcefully reset it. But if we do | |
277 // so we set a flag to make sure we only do it once, to avoid fighting | |
278 // the user if he really wants it on an unsafe place such as the desktop. | |
279 | |
280 if (!prefs->GetBoolean(prefs::kDownloadDirUpgraded)) { | |
281 std::wstring current_download_dir = | |
282 prefs->GetString(prefs::kDownloadDefaultDirectory); | |
283 if (DownloadPathIsDangerous(current_download_dir)) { | |
284 prefs->SetString(prefs::kDownloadDefaultDirectory, | |
285 default_download_path); | |
286 } | |
287 prefs->SetBoolean(prefs::kDownloadDirUpgraded, true); | |
288 } | |
289 } | |
290 | |
291 DownloadManager::DownloadManager() | |
292 : shutdown_needed_(false), | |
293 profile_(NULL), | |
294 file_manager_(NULL), | |
295 ui_loop_(MessageLoop::current()), | |
296 file_loop_(NULL) { | |
297 } | |
298 | |
299 DownloadManager::~DownloadManager() { | |
300 if (shutdown_needed_) | |
301 Shutdown(); | |
302 } | |
303 | |
304 void DownloadManager::Shutdown() { | |
305 DCHECK(shutdown_needed_) << "Shutdown called when not needed."; | |
306 | |
307 // Stop receiving download updates | |
308 file_manager_->RemoveDownloadManager(this); | |
309 | |
310 // Stop making history service requests | |
311 cancelable_consumer_.CancelAllRequests(); | |
312 | |
313 // 'in_progress_' may contain DownloadItems that have not finished the start | |
314 // complete (from the history service) and thus aren't in downloads_. | |
315 DownloadMap::iterator it = in_progress_.begin(); | |
316 for (; it != in_progress_.end(); ++it) { | |
317 DownloadItem* download = it->second; | |
318 if (download->state() == DownloadItem::IN_PROGRESS) { | |
319 download->Cancel(false); | |
320 UpdateHistoryForDownload(download); | |
321 } | |
322 if (download->db_handle() == kUninitializedHandle) { | |
323 // An invalid handle means that 'download' does not yet exist in | |
324 // 'downloads_', so we have to delete it here. | |
325 delete download; | |
326 } | |
327 } | |
328 | |
329 in_progress_.clear(); | |
330 STLDeleteValues(&downloads_); | |
331 | |
332 file_manager_ = NULL; | |
333 | |
334 // Save our file extensions to auto open. | |
335 SaveAutoOpens(); | |
336 | |
337 // Make sure the save as dialog doesn't notify us back if we're gone before | |
338 // it returns. | |
339 if (select_file_dialog_.get()) | |
340 select_file_dialog_->ListenerDestroyed(); | |
341 | |
342 shutdown_needed_ = false; | |
343 } | |
344 | |
345 // Issue a history query for downloads matching 'search_text'. If 'search_text' | |
346 // is empty, return all downloads that we know about. | |
347 void DownloadManager::GetDownloads(Observer* observer, | |
348 const std::wstring& search_text) { | |
349 DCHECK(observer); | |
350 | |
351 // Return a empty list if we've not yet received the set of downloads from the | |
352 // history system (we'll update all observers once we get that list in | |
353 // OnQueryDownloadEntriesComplete), or if there are no downloads at all. | |
354 std::vector<DownloadItem*> download_copy; | |
355 if (downloads_.empty()) { | |
356 observer->SetDownloads(download_copy); | |
357 return; | |
358 } | |
359 | |
360 // We already know all the downloads and there is no filter, so just return a | |
361 // copy to the observer. | |
362 if (search_text.empty()) { | |
363 download_copy.reserve(downloads_.size()); | |
364 for (DownloadMap::iterator it = downloads_.begin(); | |
365 it != downloads_.end(); ++it) { | |
366 download_copy.push_back(it->second); | |
367 } | |
368 | |
369 // We retain ownership of the DownloadItems. | |
370 observer->SetDownloads(download_copy); | |
371 return; | |
372 } | |
373 | |
374 // Issue a request to the history service for a list of downloads matching | |
375 // our search text. | |
376 HistoryService* hs = | |
377 profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); | |
378 if (hs) { | |
379 HistoryService::Handle h = | |
380 hs->SearchDownloads(search_text, | |
381 &cancelable_consumer_, | |
382 NewCallback(this, | |
383 &DownloadManager::OnSearchComplete)); | |
384 cancelable_consumer_.SetClientData(hs, h, observer); | |
385 } | |
386 } | |
387 | |
388 // Query the history service for information about all persisted downloads. | |
389 bool DownloadManager::Init(Profile* profile) { | |
390 DCHECK(profile); | |
391 DCHECK(!shutdown_needed_) << "DownloadManager already initialized."; | |
392 shutdown_needed_ = true; | |
393 | |
394 profile_ = profile; | |
395 request_context_ = profile_->GetRequestContext(); | |
396 | |
397 // 'incognito mode' will have access to past downloads, but we won't store | |
398 // information about new downloads while in that mode. | |
399 QueryHistoryForDownloads(); | |
400 | |
401 ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host(); | |
402 if (!rdh) { | |
403 NOTREACHED(); | |
404 return false; | |
405 } | |
406 | |
407 file_manager_ = rdh->download_file_manager(); | |
408 if (!file_manager_) { | |
409 NOTREACHED(); | |
410 return false; | |
411 } | |
412 | |
413 file_loop_ = g_browser_process->file_thread()->message_loop(); | |
414 if (!file_loop_) { | |
415 NOTREACHED(); | |
416 return false; | |
417 } | |
418 | |
419 // Get our user preference state. | |
420 PrefService* prefs = profile_->GetPrefs(); | |
421 DCHECK(prefs); | |
422 prompt_for_download_.Init(prefs::kPromptForDownload, prefs, NULL); | |
423 | |
424 download_path_.Init(prefs::kDownloadDefaultDirectory, prefs, NULL); | |
425 | |
426 // Ensure that the download directory specified in the preferences exists. | |
427 file_loop_->PostTask(FROM_HERE, NewRunnableMethod( | |
428 file_manager_, &DownloadFileManager::CreateDirectory, *download_path_)); | |
429 | |
430 // We store any file extension that should be opened automatically at | |
431 // download completion in this pref. | |
432 download_util::InitializeExeTypes(&exe_types_); | |
433 | |
434 std::wstring extensions_to_open = | |
435 prefs->GetString(prefs::kDownloadExtensionsToOpen); | |
436 std::vector<std::wstring> extensions; | |
437 SplitString(extensions_to_open, L':', &extensions); | |
438 for (size_t i = 0; i < extensions.size(); ++i) { | |
439 if (!extensions[i].empty() && !IsExecutable(extensions[i])) | |
440 auto_open_.insert(extensions[i]); | |
441 } | |
442 | |
443 return true; | |
444 } | |
445 | |
446 void DownloadManager::QueryHistoryForDownloads() { | |
447 HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); | |
448 if (hs) { | |
449 hs->QueryDownloads( | |
450 &cancelable_consumer_, | |
451 NewCallback(this, &DownloadManager::OnQueryDownloadEntriesComplete)); | |
452 } | |
453 } | |
454 | |
455 // We have received a message from DownloadFileManager about a new download. We | |
456 // create a download item and store it in our download map, and inform the | |
457 // history system of a new download. Since this method can be called while the | |
458 // history service thread is still reading the persistent state, we do not | |
459 // insert the new DownloadItem into 'downloads_' or inform our observers at this | |
460 // point. OnCreateDatabaseEntryComplete() handles that finalization of the the | |
461 // download creation as a callback from the history thread. | |
462 void DownloadManager::StartDownload(DownloadCreateInfo* info) { | |
463 DCHECK(MessageLoop::current() == ui_loop_); | |
464 DCHECK(info); | |
465 | |
466 // Determine the proper path for a download, by choosing either the default | |
467 // download directory, or prompting the user. | |
468 std::wstring generated_name; | |
469 GenerateFilename(info, &generated_name); | |
470 if (*prompt_for_download_ && !last_download_path_.empty()) | |
471 info->suggested_path = last_download_path_; | |
472 else | |
473 info->suggested_path = *download_path_; | |
474 file_util::AppendToPath(&info->suggested_path, generated_name); | |
475 | |
476 // We need to move over to the download thread because we don't want to stat | |
477 // the suggested path on the UI thread. | |
478 file_loop_->PostTask(FROM_HERE, | |
479 NewRunnableMethod(this, | |
480 &DownloadManager::CheckIfSuggestedPathExists, | |
481 info)); | |
482 } | |
483 | |
484 void DownloadManager::CheckIfSuggestedPathExists(DownloadCreateInfo* info) { | |
485 DCHECK(info); | |
486 | |
487 // Check writability of the suggested path. If we can't write to it, default | |
488 // to the user's "My Documents" directory. We'll prompt them in this case. | |
489 std::wstring path = file_util::GetDirectoryFromPath(info->suggested_path); | |
490 if (!file_util::PathIsWritable(path)) { | |
491 info->save_as = true; | |
492 const std::wstring filename = | |
493 file_util::GetFilenameFromPath(info->suggested_path); | |
494 PathService::Get(chrome::DIR_USER_DOCUMENTS, &info->suggested_path); | |
495 file_util::AppendToPath(&info->suggested_path, filename); | |
496 } | |
497 | |
498 info->suggested_path_exists = !UniquifyPath(&info->suggested_path); | |
499 | |
500 // Now we return to the UI thread. | |
501 ui_loop_->PostTask(FROM_HERE, | |
502 NewRunnableMethod(this, | |
503 &DownloadManager::OnPathExistenceAvailable, | |
504 info)); | |
505 } | |
506 | |
507 void DownloadManager::OnPathExistenceAvailable(DownloadCreateInfo* info) { | |
508 DCHECK(MessageLoop::current() == ui_loop_); | |
509 DCHECK(info); | |
510 | |
511 if (*prompt_for_download_ || info->save_as || info->suggested_path_exists) { | |
512 // We must ask the user for the place to put the download. | |
513 if (!select_file_dialog_.get()) | |
514 select_file_dialog_ = SelectFileDialog::Create(this); | |
515 | |
516 TabContents* contents = tab_util::GetTabContentsByID( | |
517 info->render_process_id, info->render_view_id); | |
518 HWND owning_hwnd = | |
519 contents ? GetAncestor(contents->GetContainerHWND(), GA_ROOT) : NULL; | |
520 select_file_dialog_->SelectFile(SelectFileDialog::SELECT_SAVEAS_FILE, | |
521 std::wstring(), info->suggested_path, | |
522 owning_hwnd, info); | |
523 } else { | |
524 // No prompting for download, just continue with the suggested name. | |
525 ContinueStartDownload(info, info->suggested_path); | |
526 } | |
527 } | |
528 | |
529 void DownloadManager::ContinueStartDownload(DownloadCreateInfo* info, | |
530 const std::wstring& target_path) { | |
531 scoped_ptr<DownloadCreateInfo> infop(info); | |
532 info->path = target_path; | |
533 | |
534 DownloadItem* download = NULL; | |
535 DownloadMap::iterator it = in_progress_.find(info->download_id); | |
536 if (it == in_progress_.end()) { | |
537 download = new DownloadItem(info->download_id, | |
538 info->path, | |
539 info->url, | |
540 info->start_time, | |
541 info->total_bytes, | |
542 info->render_process_id, | |
543 info->request_id); | |
544 download->set_manager(this); | |
545 in_progress_[info->download_id] = download; | |
546 } else { | |
547 NOTREACHED(); // Should not exist! | |
548 return; | |
549 } | |
550 | |
551 // If the download already completed by the time we reached this point, then | |
552 // notify observers that it did. | |
553 PendingFinishedMap::iterator pending_it = | |
554 pending_finished_downloads_.find(info->download_id); | |
555 if (pending_it != pending_finished_downloads_.end()) | |
556 DownloadFinished(pending_it->first, pending_it->second); | |
557 | |
558 download->Rename(target_path); | |
559 | |
560 file_loop_->PostTask(FROM_HERE, | |
561 NewRunnableMethod(file_manager_, | |
562 &DownloadFileManager::OnFinalDownloadName, | |
563 download->id(), | |
564 target_path)); | |
565 | |
566 if (profile_->IsOffTheRecord()) { | |
567 // Fake a db handle for incognito mode, since nothing is actually stored in | |
568 // the database in this mode. We have to make sure that these handles don't | |
569 // collide with normal db handles, so we use a negative value. Eventually, | |
570 // they could overlap, but you'd have to do enough downloading that your ISP | |
571 // would likely stab you in the neck first. YMMV. | |
572 static int64 fake_db_handle = kUninitializedHandle - 1; | |
573 OnCreateDownloadEntryComplete(*info, fake_db_handle--); | |
574 } else { | |
575 // Update the history system with the new download. | |
576 // FIXME(acw|paulg) see bug 958058. EXPLICIT_ACCESS below is wrong. | |
577 HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); | |
578 if (hs) { | |
579 hs->CreateDownload( | |
580 *info, &cancelable_consumer_, | |
581 NewCallback(this, &DownloadManager::OnCreateDownloadEntryComplete)); | |
582 } | |
583 } | |
584 } | |
585 | |
586 // Convenience function for updating the history service for a download. | |
587 void DownloadManager::UpdateHistoryForDownload(DownloadItem* download) { | |
588 DCHECK(download); | |
589 | |
590 // Don't store info in the database if the download was initiated while in | |
591 // incognito mode or if it hasn't been initialized in our database table. | |
592 if (download->db_handle() <= kUninitializedHandle) | |
593 return; | |
594 | |
595 // FIXME(acw|paulg) see bug 958058. EXPLICIT_ACCESS below is wrong. | |
596 HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); | |
597 if (hs) { | |
598 hs->UpdateDownload(download->received_bytes(), | |
599 download->state(), | |
600 download->db_handle()); | |
601 } | |
602 } | |
603 | |
604 void DownloadManager::RemoveDownloadFromHistory(DownloadItem* download) { | |
605 DCHECK(download); | |
606 // FIXME(acw|paulg) see bug 958058. EXPLICIT_ACCESS below is wrong. | |
607 HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); | |
608 if (download->db_handle() > kUninitializedHandle && hs) | |
609 hs->RemoveDownload(download->db_handle()); | |
610 } | |
611 | |
612 void DownloadManager::RemoveDownloadsFromHistoryBetween(const Time remove_begin, | |
613 const Time remove_end) { | |
614 // FIXME(acw|paulg) see bug 958058. EXPLICIT_ACCESS below is wrong. | |
615 HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); | |
616 if (hs) | |
617 hs->RemoveDownloadsBetween(remove_begin, remove_end); | |
618 } | |
619 | |
620 void DownloadManager::UpdateDownload(int32 download_id, int64 size) { | |
621 DownloadMap::iterator it = in_progress_.find(download_id); | |
622 if (it != in_progress_.end()) { | |
623 DownloadItem* download = it->second; | |
624 download->Update(size); | |
625 UpdateHistoryForDownload(download); | |
626 } | |
627 } | |
628 | |
629 void DownloadManager::DownloadFinished(int32 download_id, int64 size) { | |
630 DownloadMap::iterator it = in_progress_.find(download_id); | |
631 if (it != in_progress_.end()) { | |
632 // Remove the id from the list of pending ids. | |
633 PendingFinishedMap::iterator erase_it = | |
634 pending_finished_downloads_.find(download_id); | |
635 if (erase_it != pending_finished_downloads_.end()) | |
636 pending_finished_downloads_.erase(erase_it); | |
637 | |
638 DownloadItem* download = it->second; | |
639 download->Finished(size); | |
640 | |
641 // Open the download if the user or user prefs indicate it should be. | |
642 const std::wstring extension = | |
643 file_util::GetFileExtensionFromPath(download->full_path()); | |
644 if (download->open_when_complete() || ShouldOpenFileExtension(extension)) | |
645 OpenDownloadInShell(download, NULL); | |
646 | |
647 // Clean up will happen when the history system create callback runs if we | |
648 // don't have a valid db_handle yet. | |
649 if (download->db_handle() != kUninitializedHandle) { | |
650 in_progress_.erase(it); | |
651 NotifyAboutDownloadStop(); | |
652 UpdateHistoryForDownload(download); | |
653 } | |
654 } else { | |
655 // The download is done, but the user hasn't selected a final location for | |
656 // it yet (the Save As dialog box is probably still showing), so just keep | |
657 // track of the fact that this download id is complete, when the | |
658 // DownloadItem is constructed later we'll notify its completion then. | |
659 PendingFinishedMap::iterator erase_it = | |
660 pending_finished_downloads_.find(download_id); | |
661 DCHECK(erase_it == pending_finished_downloads_.end()); | |
662 pending_finished_downloads_[download_id] = size; | |
663 } | |
664 } | |
665 | |
666 // static | |
667 // We have to tell the ResourceDispatcherHost to cancel the download from this | |
668 // thread, since we can't forward tasks from the file thread to the io thread | |
669 // reliably (crash on shutdown race condition). | |
670 void DownloadManager::CancelDownloadRequest(int render_process_id, | |
671 int request_id) { | |
672 ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host(); | |
673 base::Thread* io_thread = g_browser_process->io_thread(); | |
674 if (!io_thread || !rdh) | |
675 return; | |
676 io_thread->message_loop()->PostTask(FROM_HERE, | |
677 NewRunnableFunction(&DownloadManager::OnCancelDownloadRequest, | |
678 rdh, | |
679 render_process_id, | |
680 request_id)); | |
681 } | |
682 | |
683 // static | |
684 void DownloadManager::OnCancelDownloadRequest(ResourceDispatcherHost* rdh, | |
685 int render_process_id, | |
686 int request_id) { | |
687 rdh->CancelRequest(render_process_id, request_id, false); | |
688 } | |
689 | |
690 void DownloadManager::DownloadCancelled(int32 download_id) { | |
691 DownloadMap::iterator it = in_progress_.find(download_id); | |
692 if (it == in_progress_.end()) | |
693 return; | |
694 DownloadItem* download = it->second; | |
695 | |
696 CancelDownloadRequest(download->render_process_id(), download->request_id()); | |
697 | |
698 // Clean up will happen when the history system create callback runs if we | |
699 // don't have a valid db_handle yet. | |
700 if (download->db_handle() != kUninitializedHandle) { | |
701 in_progress_.erase(it); | |
702 NotifyAboutDownloadStop(); | |
703 UpdateHistoryForDownload(download); | |
704 } | |
705 | |
706 // Tell the file manager to cancel the download. | |
707 file_manager_->RemoveDownload(download->id(), this); // On the UI thread | |
708 file_loop_->PostTask(FROM_HERE, | |
709 NewRunnableMethod(file_manager_, | |
710 &DownloadFileManager::CancelDownload, | |
711 download->id())); | |
712 } | |
713 | |
714 void DownloadManager::PauseDownload(int32 download_id, bool pause) { | |
715 DownloadMap::iterator it = in_progress_.find(download_id); | |
716 if (it != in_progress_.end()) { | |
717 DownloadItem* download = it->second; | |
718 if (pause == download->is_paused()) | |
719 return; | |
720 | |
721 // Inform the ResourceDispatcherHost of the new pause state. | |
722 base::Thread* io_thread = g_browser_process->io_thread(); | |
723 ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host(); | |
724 if (!io_thread || !rdh) | |
725 return; | |
726 | |
727 io_thread->message_loop()->PostTask(FROM_HERE, | |
728 NewRunnableFunction(&DownloadManager::OnPauseDownloadRequest, | |
729 rdh, | |
730 download->render_process_id(), | |
731 download->request_id(), | |
732 pause)); | |
733 } | |
734 } | |
735 | |
736 // static | |
737 void DownloadManager::OnPauseDownloadRequest(ResourceDispatcherHost* rdh, | |
738 int render_process_id, | |
739 int request_id, | |
740 bool pause) { | |
741 rdh->PauseRequest(render_process_id, request_id, pause); | |
742 } | |
743 | |
744 void DownloadManager::RemoveDownload(int64 download_handle) { | |
745 DownloadMap::iterator it = downloads_.find(download_handle); | |
746 if (it == downloads_.end()) | |
747 return; | |
748 | |
749 // Make history update. | |
750 DownloadItem* download = it->second; | |
751 RemoveDownloadFromHistory(download); | |
752 | |
753 // Remove from our tables and delete. | |
754 downloads_.erase(it); | |
755 delete download; | |
756 | |
757 // Tell observers to refresh their views. | |
758 FOR_EACH_OBSERVER(Observer, observers_, ModelChanged()); | |
759 } | |
760 | |
761 int DownloadManager::RemoveDownloadsBetween(const Time remove_begin, | |
762 const Time remove_end) { | |
763 RemoveDownloadsFromHistoryBetween(remove_begin, remove_end); | |
764 | |
765 int num_deleted = 0; | |
766 DownloadMap::iterator it = downloads_.begin(); | |
767 while (it != downloads_.end()) { | |
768 DownloadItem* download = it->second; | |
769 DownloadItem::DownloadState state = download->state(); | |
770 if (download->start_time() >= remove_begin && | |
771 (remove_end.is_null() || download->start_time() < remove_end) && | |
772 (state == DownloadItem::COMPLETE || | |
773 state == DownloadItem::CANCELLED)) { | |
774 // Remove from the map and move to the next in the list. | |
775 it = downloads_.erase(it); | |
776 delete download; | |
777 | |
778 ++num_deleted; | |
779 continue; | |
780 } | |
781 | |
782 ++it; | |
783 } | |
784 | |
785 // Tell observers to refresh their views. | |
786 if (num_deleted > 0) | |
787 FOR_EACH_OBSERVER(Observer, observers_, ModelChanged()); | |
788 | |
789 return num_deleted; | |
790 } | |
791 | |
792 int DownloadManager::RemoveDownloads(const Time remove_begin) { | |
793 return RemoveDownloadsBetween(remove_begin, Time()); | |
794 } | |
795 | |
796 // Initiate a download of a specific URL. We send the request to the | |
797 // ResourceDispatcherHost, and let it send us responses like a regular | |
798 // download. | |
799 void DownloadManager::DownloadUrl(const GURL& url, | |
800 const GURL& referrer, | |
801 WebContents* web_contents) { | |
802 DCHECK(web_contents); | |
803 file_manager_->DownloadUrl(url, | |
804 referrer, | |
805 web_contents->process()->host_id(), | |
806 web_contents->render_view_host()->routing_id(), | |
807 request_context_.get()); | |
808 } | |
809 | |
810 void DownloadManager::NotifyAboutDownloadStart() { | |
811 NotificationService::current()-> | |
812 Notify(NOTIFY_DOWNLOAD_START, NotificationService::AllSources(), | |
813 NotificationService::NoDetails()); | |
814 } | |
815 | |
816 void DownloadManager::NotifyAboutDownloadStop() { | |
817 NotificationService::current()-> | |
818 Notify(NOTIFY_DOWNLOAD_STOP, NotificationService::AllSources(), | |
819 NotificationService::NoDetails()); | |
820 } | |
821 | |
822 void DownloadManager::GenerateExtension(const std::wstring& file_name, | |
823 const std::string& mime_type, | |
824 std::wstring* generated_extension) { | |
825 // We're worried about three things here: | |
826 // | |
827 // 1) Security. Many sites let users upload content, such as buddy icons, to | |
828 // their web sites. We want to mitigate the case where an attacker | |
829 // supplies a malicious executable with an executable file extension but an | |
830 // honest site serves the content with a benign content type, such as | |
831 // image/jpeg. | |
832 // | |
833 // 2) Usability. If the site fails to provide a file extension, we want to | |
834 // guess a reasonable file extension based on the content type. | |
835 // | |
836 // 3) Shell integration. Some file extensions automatically integrate with | |
837 // the shell. We block these extensions to prevent a malicious web site | |
838 // from integrating with the user's shell. | |
839 | |
840 static const wchar_t default_extension[] = L"download"; | |
841 | |
842 // See if our file name already contains an extension. | |
843 std::wstring extension(file_util::GetFileExtensionFromPath(file_name)); | |
844 | |
845 // Rename shell-integrated extensions. | |
846 if (win_util::IsShellIntegratedExtension(extension)) | |
847 extension.assign(default_extension); | |
848 | |
849 std::string mime_type_from_extension; | |
850 net::GetMimeTypeFromFile(file_name, &mime_type_from_extension); | |
851 if (mime_type == mime_type_from_extension) { | |
852 // The hinted extension matches the mime type. It looks like a winner. | |
853 generated_extension->swap(extension); | |
854 return; | |
855 } | |
856 | |
857 if (IsExecutable(extension) && !IsExecutableMimeType(mime_type)) { | |
858 // We want to be careful about executable extensions. The worry here is | |
859 // that a trusted web site could be tricked into dropping an executable file | |
860 // on the user's filesystem. | |
861 if (!net::GetPreferredExtensionForMimeType(mime_type, &extension)) { | |
862 // We couldn't find a good extension for this content type. Use a dummy | |
863 // extension instead. | |
864 extension.assign(default_extension); | |
865 } | |
866 } | |
867 | |
868 if (extension.empty()) { | |
869 net::GetPreferredExtensionForMimeType(mime_type, &extension); | |
870 } else { | |
871 // Append entension generated from the mime type if: | |
872 // 1. New extension is not ".txt" | |
873 // 2. New extension is not the same as the already existing extension. | |
874 // 3. New extension is not executable. This action mitigates the case when | |
875 // an execuatable is hidden in a benign file extension; | |
876 // E.g. my-cat.jpg becomes my-cat.jpg.js if content type is | |
877 // application/x-javascript. | |
878 std::wstring append_extension; | |
879 if (net::GetPreferredExtensionForMimeType(mime_type, &append_extension)) { | |
880 if (append_extension != L".txt" && append_extension != extension && | |
881 !IsExecutable(append_extension)) | |
882 extension += append_extension; | |
883 } | |
884 } | |
885 | |
886 generated_extension->swap(extension); | |
887 } | |
888 | |
889 void DownloadManager::GenerateFilename(DownloadCreateInfo* info, | |
890 std::wstring* generated_name) { | |
891 std::wstring file_name = | |
892 net::GetSuggestedFilename(GURL(info->url), | |
893 info->content_disposition, | |
894 L"download"); | |
895 DCHECK(!file_name.empty()); | |
896 | |
897 // Make sure we get the right file extension. | |
898 std::wstring extension; | |
899 GenerateExtension(file_name, info->mime_type, &extension); | |
900 file_util::ReplaceExtension(&file_name, extension); | |
901 | |
902 // Prepend "_" to the file name if it's a reserved name | |
903 if (win_util::IsReservedName(file_name)) | |
904 file_name = std::wstring(L"_") + file_name; | |
905 | |
906 generated_name->assign(file_name); | |
907 } | |
908 | |
909 void DownloadManager::AddObserver(Observer* observer) { | |
910 observers_.AddObserver(observer); | |
911 observer->ModelChanged(); | |
912 } | |
913 | |
914 void DownloadManager::RemoveObserver(Observer* observer) { | |
915 observers_.RemoveObserver(observer); | |
916 } | |
917 | |
918 // Post Windows Shell operations to the Download thread, to avoid blocking the | |
919 // user interface. | |
920 void DownloadManager::ShowDownloadInShell(const DownloadItem* download) { | |
921 DCHECK(file_manager_); | |
922 file_loop_->PostTask(FROM_HERE, | |
923 NewRunnableMethod(file_manager_, | |
924 &DownloadFileManager::OnShowDownloadInShell, | |
925 download->full_path())); | |
926 } | |
927 | |
928 void DownloadManager::OpenDownloadInShell(const DownloadItem* download, | |
929 HWND parent_window) { | |
930 DCHECK(file_manager_); | |
931 file_loop_->PostTask(FROM_HERE, | |
932 NewRunnableMethod(file_manager_, | |
933 &DownloadFileManager::OnOpenDownloadInShell, | |
934 download->full_path(), download->url(), parent_window)); | |
935 } | |
936 | |
937 void DownloadManager::OpenFilesOfExtension(const std::wstring& extension, | |
938 bool open) { | |
939 if (open && !IsExecutable(extension)) | |
940 auto_open_.insert(extension); | |
941 else | |
942 auto_open_.erase(extension); | |
943 SaveAutoOpens(); | |
944 } | |
945 | |
946 bool DownloadManager::ShouldOpenFileExtension(const std::wstring& extension) { | |
947 if (!IsExecutable(extension) && | |
948 auto_open_.find(extension) != auto_open_.end()) | |
949 return true; | |
950 return false; | |
951 } | |
952 | |
953 // static | |
954 bool DownloadManager::IsExecutableMimeType(const std::string& mime_type) { | |
955 // JavaScript is just as powerful as EXE. | |
956 if (net::MatchesMimeType("text/javascript", mime_type)) | |
957 return true; | |
958 if (net::MatchesMimeType("text/javascript;version=*", mime_type)) | |
959 return true; | |
960 | |
961 // We don't consider other non-application types to be executable. | |
962 if (!net::MatchesMimeType("application/*", mime_type)) | |
963 return false; | |
964 | |
965 // These application types are not executable. | |
966 if (net::MatchesMimeType("application/*+xml", mime_type)) | |
967 return false; | |
968 if (net::MatchesMimeType("application/xml", mime_type)) | |
969 return false; | |
970 | |
971 return true; | |
972 } | |
973 | |
974 bool DownloadManager::IsExecutable(const std::wstring& extension) { | |
975 return exe_types_.find(extension) != exe_types_.end(); | |
976 } | |
977 | |
978 void DownloadManager::ResetAutoOpenFiles() { | |
979 auto_open_.clear(); | |
980 SaveAutoOpens(); | |
981 } | |
982 | |
983 bool DownloadManager::HasAutoOpenFileTypesRegistered() const { | |
984 return !auto_open_.empty(); | |
985 } | |
986 | |
987 void DownloadManager::SaveAutoOpens() { | |
988 PrefService* prefs = profile_->GetPrefs(); | |
989 if (prefs) { | |
990 std::wstring extensions; | |
991 for (std::set<std::wstring>::iterator it = auto_open_.begin(); | |
992 it != auto_open_.end(); ++it) { | |
993 extensions += *it + L":"; | |
994 } | |
995 if (!extensions.empty()) | |
996 extensions.erase(extensions.size() - 1); | |
997 prefs->SetString(prefs::kDownloadExtensionsToOpen, extensions); | |
998 } | |
999 } | |
1000 | |
1001 void DownloadManager::FileSelected(const std::wstring& path, void* params) { | |
1002 DownloadCreateInfo* info = reinterpret_cast<DownloadCreateInfo*>(params); | |
1003 if (*prompt_for_download_) | |
1004 last_download_path_ = file_util::GetDirectoryFromPath(path); | |
1005 ContinueStartDownload(info, path); | |
1006 } | |
1007 | |
1008 void DownloadManager::FileSelectionCanceled(void* params) { | |
1009 // The user didn't pick a place to save the file, so need to cancel the | |
1010 // download that's already in progress to the temporary location. | |
1011 DownloadCreateInfo* info = reinterpret_cast<DownloadCreateInfo*>(params); | |
1012 file_loop_->PostTask(FROM_HERE, | |
1013 NewRunnableMethod(file_manager_, &DownloadFileManager::CancelDownload, | |
1014 info->download_id)); | |
1015 } | |
1016 | |
1017 // Operations posted to us from the history service ---------------------------- | |
1018 | |
1019 // The history service has retrieved all download entries. 'entries' contains | |
1020 // 'DownloadCreateInfo's in sorted order (by ascending start_time). | |
1021 void DownloadManager::OnQueryDownloadEntriesComplete( | |
1022 std::vector<DownloadCreateInfo>* entries) { | |
1023 for (size_t i = 0; i < entries->size(); ++i) { | |
1024 DownloadItem* download = new DownloadItem(entries->at(i)); | |
1025 DCHECK(downloads_.find(download->db_handle()) == downloads_.end()); | |
1026 downloads_[download->db_handle()] = download; | |
1027 download->set_manager(this); | |
1028 } | |
1029 FOR_EACH_OBSERVER(Observer, observers_, ModelChanged()); | |
1030 } | |
1031 | |
1032 | |
1033 // Once the new DownloadItem's creation info has been committed to the history | |
1034 // service, we associate the DownloadItem with the db handle, update our | |
1035 // 'downloads_' map and inform observers. | |
1036 void DownloadManager::OnCreateDownloadEntryComplete(DownloadCreateInfo info, | |
1037 int64 db_handle) { | |
1038 DownloadMap::iterator it = in_progress_.find(info.download_id); | |
1039 DCHECK(it != in_progress_.end()); | |
1040 | |
1041 DownloadItem* download = it->second; | |
1042 DCHECK(download->db_handle() == kUninitializedHandle); | |
1043 download->set_db_handle(db_handle); | |
1044 | |
1045 // Insert into our full map. | |
1046 DCHECK(downloads_.find(download->db_handle()) == downloads_.end()); | |
1047 downloads_[download->db_handle()] = download; | |
1048 | |
1049 // The 'contents' may no longer exist if the user closed the tab before we get | |
1050 // this start completion event. If it does, tell the origin WebContents to | |
1051 // display its download shelf. | |
1052 TabContents* contents = | |
1053 tab_util::GetTabContentsByID(info.render_process_id, info.render_view_id); | |
1054 | |
1055 // If the contents no longer exists or is no longer active, we start the | |
1056 // download in the last active browser. This is not ideal but better than | |
1057 // fully hiding the download from the user. Note: non active means that the | |
1058 // user navigated away from the tab contents. This has nothing to do with | |
1059 // tab selection. | |
1060 if (!contents || !contents->is_active()) { | |
1061 Browser* last_active = BrowserList::GetLastActive(); | |
1062 if (last_active) | |
1063 contents = last_active->GetSelectedTabContents(); | |
1064 } | |
1065 | |
1066 if (contents) | |
1067 contents->OnStartDownload(download); | |
1068 | |
1069 // Inform interested objects about the new download. | |
1070 FOR_EACH_OBSERVER(Observer, observers_, ModelChanged()); | |
1071 NotifyAboutDownloadStart(); | |
1072 | |
1073 // If this download has been completed before we've received the db handle, | |
1074 // post one final message to the history service so that it can be properly | |
1075 // in sync with the DownloadItem's completion status, and also inform any | |
1076 // observers so that they get more than just the start notification. | |
1077 if (download->state() != DownloadItem::IN_PROGRESS) { | |
1078 in_progress_.erase(it); | |
1079 NotifyAboutDownloadStop(); | |
1080 UpdateHistoryForDownload(download); | |
1081 download->UpdateObservers(); | |
1082 } | |
1083 } | |
1084 | |
1085 // Called when the history service has retrieved the list of downloads that | |
1086 // match the search text. | |
1087 void DownloadManager::OnSearchComplete(HistoryService::Handle handle, | |
1088 std::vector<int64>* results) { | |
1089 HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); | |
1090 Observer* requestor = cancelable_consumer_.GetClientData(hs, handle); | |
1091 if (!requestor) | |
1092 return; | |
1093 | |
1094 std::vector<DownloadItem*> searched_downloads; | |
1095 for (std::vector<int64>::iterator it = results->begin(); | |
1096 it != results->end(); ++it) { | |
1097 DownloadMap::iterator dit = downloads_.find(*it); | |
1098 if (dit != downloads_.end()) | |
1099 searched_downloads.push_back(dit->second); | |
1100 } | |
1101 | |
1102 requestor->SetDownloads(searched_downloads); | |
1103 } | |
1104 | |
OLD | NEW |