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