blob: d83ddc26fcddfc9820e55917600dd53c90b34c7f [file] [log] [blame]
license.botbf09a502008-08-24 00:55:551// 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.
initial.commit09911bf2008-07-26 23:55:294
5#include <time.h>
6
[email protected]cdaa8652008-09-13 02:48:597#include "chrome/browser/download/download_manager.h"
initial.commit09911bf2008-07-26 23:55:298
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"
[email protected]cdaa8652008-09-13 02:48:5921#include "chrome/browser/download/download_file.h"
22#include "chrome/browser/download/download_util.h"
initial.commit09911bf2008-07-26 23:55:2923#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"
[email protected]46072d42008-07-28 14:49:3536#include "googleurl/src/gurl.h"
initial.commit09911bf2008-07-26 23:55:2937#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.
44class 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).
54static 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.
60static 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.
64static 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
[email protected]f052118e2008-09-05 02:25:3285static 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
initial.commit09911bf2008-07-26 23:55:2994// DownloadItem implementation -------------------------------------------------
95
96// Constructor for reading from the history service.
97DownloadItem::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),
initial.commit09911bf2008-07-26 23:55:29107 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.
118DownloadItem::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),
initial.commit09911bf2008-07-26 23:55:29134 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
142void DownloadItem::Init(bool start_timer) {
143 file_name_ = file_util::GetFilenameFromPath(full_path_);
144 if (start_timer)
145 StartProgressTimer();
146}
147
148DownloadItem::~DownloadItem() {
initial.commit09911bf2008-07-26 23:55:29149 state_ = REMOVING;
150 UpdateObservers();
151}
152
153void DownloadItem::AddObserver(Observer* observer) {
154 observers_.AddObserver(observer);
155}
156
157void DownloadItem::RemoveObserver(Observer* observer) {
158 observers_.RemoveObserver(observer);
159}
160
161void 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'.
167void 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.
176void 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
186void 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
198void DownloadItem::Finished(int64 size) {
199 state_ = COMPLETE;
200 UpdateSize(size);
201 UpdateObservers();
202 StopProgressTimer();
203}
204
205void DownloadItem::Remove() {
206 Cancel(true);
207 state_ = REMOVING;
208 manager_->RemoveDownload(db_handle_);
209}
210
211void DownloadItem::StartProgressTimer() {
[email protected]2d316662008-09-03 18:18:14212 update_timer_.Start(TimeDelta::FromMilliseconds(kUpdateTimeMs), this,
213 &DownloadItem::UpdateObservers);
initial.commit09911bf2008-07-26 23:55:29214}
215
216void DownloadItem::StopProgressTimer() {
[email protected]2d316662008-09-03 18:18:14217 update_timer_.Stop();
initial.commit09911bf2008-07-26 23:55:29218}
219
220bool 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
233int64 DownloadItem::CurrentSpeed() const {
234 uintptr_t diff = GetTickCount() - start_tick_;
235 return diff == 0 ? 0 : received_bytes_ * 1000 / diff;
236}
237
238int 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
245void 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
251void 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
261void DownloadManager::RegisterUserPrefs(PrefService* prefs) {
262 prefs->RegisterBooleanPref(prefs::kPromptForDownload, false);
263 prefs->RegisterStringPref(prefs::kDownloadExtensionsToOpen, L"");
[email protected]f052118e2008-09-05 02:25:32264 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 }
initial.commit09911bf2008-07-26 23:55:29289}
290
291DownloadManager::DownloadManager()
292 : shutdown_needed_(false),
293 profile_(NULL),
294 file_manager_(NULL),
295 ui_loop_(MessageLoop::current()),
296 file_loop_(NULL) {
297}
298
299DownloadManager::~DownloadManager() {
300 if (shutdown_needed_)
301 Shutdown();
302}
303
304void 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.
347void 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.
389bool 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
initial.commit09911bf2008-07-26 23:55:29424 download_path_.Init(prefs::kDownloadDefaultDirectory, prefs, NULL);
425
[email protected]bb69e9b32008-08-14 23:08:14426 // 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
initial.commit09911bf2008-07-26 23:55:29430 // 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
446void 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.
462void 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
484void 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
507void 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
529void 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.
587void 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
604void 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
612void 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
620void 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
629void 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).
670void DownloadManager::CancelDownloadRequest(int render_process_id,
671 int request_id) {
672 ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host();
[email protected]ab820df2008-08-26 05:55:10673 base::Thread* io_thread = g_browser_process->io_thread();
initial.commit09911bf2008-07-26 23:55:29674 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
684void DownloadManager::OnCancelDownloadRequest(ResourceDispatcherHost* rdh,
685 int render_process_id,
686 int request_id) {
687 rdh->CancelRequest(render_process_id, request_id, false);
688}
689
690void 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
714void 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.
[email protected]ab820df2008-08-26 05:55:10722 base::Thread* io_thread = g_browser_process->io_thread();
initial.commit09911bf2008-07-26 23:55:29723 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
737void 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
744void 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
761int 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
792int 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.
799void 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
810void DownloadManager::NotifyAboutDownloadStart() {
811 NotificationService::current()->
812 Notify(NOTIFY_DOWNLOAD_START, NotificationService::AllSources(),
813 NotificationService::NoDetails());
814}
815
816void DownloadManager::NotifyAboutDownloadStop() {
817 NotificationService::current()->
818 Notify(NOTIFY_DOWNLOAD_STOP, NotificationService::AllSources(),
819 NotificationService::NoDetails());
820}
821
822void 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;
[email protected]a9bb6f692008-07-30 16:40:10850 net::GetMimeTypeFromFile(file_name, &mime_type_from_extension);
initial.commit09911bf2008-07-26 23:55:29851 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.
[email protected]a9bb6f692008-07-30 16:40:10861 if (!net::GetPreferredExtensionForMimeType(mime_type, &extension)) {
initial.commit09911bf2008-07-26 23:55:29862 // 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()) {
[email protected]a9bb6f692008-07-30 16:40:10869 net::GetPreferredExtensionForMimeType(mime_type, &extension);
initial.commit09911bf2008-07-26 23:55:29870 } 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;
[email protected]a9bb6f692008-07-30 16:40:10879 if (net::GetPreferredExtensionForMimeType(mime_type, &append_extension)) {
initial.commit09911bf2008-07-26 23:55:29880 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
889void DownloadManager::GenerateFilename(DownloadCreateInfo* info,
890 std::wstring* generated_name) {
891 std::wstring file_name =
[email protected]8ac1a752008-07-31 19:40:37892 net::GetSuggestedFilename(GURL(info->url),
893 info->content_disposition,
894 L"download");
initial.commit09911bf2008-07-26 23:55:29895 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
909void DownloadManager::AddObserver(Observer* observer) {
910 observers_.AddObserver(observer);
911 observer->ModelChanged();
912}
913
914void 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.
920void 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
928void 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
937void 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
946bool 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
954bool DownloadManager::IsExecutableMimeType(const std::string& mime_type) {
955 // JavaScript is just as powerful as EXE.
[email protected]a9bb6f692008-07-30 16:40:10956 if (net::MatchesMimeType("text/javascript", mime_type))
initial.commit09911bf2008-07-26 23:55:29957 return true;
[email protected]a9bb6f692008-07-30 16:40:10958 if (net::MatchesMimeType("text/javascript;version=*", mime_type))
initial.commit09911bf2008-07-26 23:55:29959 return true;
960
961 // We don't consider other non-application types to be executable.
[email protected]a9bb6f692008-07-30 16:40:10962 if (!net::MatchesMimeType("application/*", mime_type))
initial.commit09911bf2008-07-26 23:55:29963 return false;
964
965 // These application types are not executable.
[email protected]a9bb6f692008-07-30 16:40:10966 if (net::MatchesMimeType("application/*+xml", mime_type))
initial.commit09911bf2008-07-26 23:55:29967 return false;
[email protected]a9bb6f692008-07-30 16:40:10968 if (net::MatchesMimeType("application/xml", mime_type))
initial.commit09911bf2008-07-26 23:55:29969 return false;
970
971 return true;
972}
973
974bool DownloadManager::IsExecutable(const std::wstring& extension) {
975 return exe_types_.find(extension) != exe_types_.end();
976}
977
978void DownloadManager::ResetAutoOpenFiles() {
979 auto_open_.clear();
980 SaveAutoOpens();
981}
982
983bool DownloadManager::HasAutoOpenFileTypesRegistered() const {
984 return !auto_open_.empty();
985}
986
987void 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
1001void 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
1008void 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).
1021void 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.
1036void 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.
1087void 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}
license.botbf09a502008-08-24 00:55:551104