OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/download/download_target_determiner.h" | 5 #include "chrome/browser/download/download_target_determiner.h" |
6 | 6 |
7 #include "base/prefs/pref_service.h" | 7 #include "base/prefs/pref_service.h" |
8 #include "base/rand_util.h" | 8 #include "base/rand_util.h" |
9 #include "base/strings/stringprintf.h" | 9 #include "base/strings/stringprintf.h" |
10 #include "base/time/time.h" | 10 #include "base/time/time.h" |
11 #include "chrome/browser/download/chrome_download_manager_delegate.h" | 11 #include "chrome/browser/download/chrome_download_manager_delegate.h" |
12 #include "chrome/browser/download/download_crx_util.h" | 12 #include "chrome/browser/download/download_crx_util.h" |
13 #include "chrome/browser/download/download_extensions.h" | 13 #include "chrome/browser/download/download_extensions.h" |
14 #include "chrome/browser/download/download_prefs.h" | 14 #include "chrome/browser/download/download_prefs.h" |
15 #include "chrome/browser/extensions/webstore_installer.h" | 15 #include "chrome/browser/extensions/webstore_installer.h" |
16 #include "chrome/browser/history/history_service.h" | 16 #include "chrome/browser/history/history_service.h" |
17 #include "chrome/browser/history/history_service_factory.h" | 17 #include "chrome/browser/history/history_service_factory.h" |
18 #include "chrome/browser/profiles/profile.h" | 18 #include "chrome/browser/profiles/profile.h" |
19 #include "chrome/common/extensions/feature_switch.h" | 19 #include "chrome/common/extensions/feature_switch.h" |
20 #include "chrome/common/pref_names.h" | 20 #include "chrome/common/pref_names.h" |
21 #include "content/public/browser/browser_context.h" | 21 #include "content/public/browser/browser_context.h" |
22 #include "content/public/browser/browser_thread.h" | 22 #include "content/public/browser/browser_thread.h" |
23 #include "content/public/browser/download_interrupt_reasons.h" | 23 #include "content/public/browser/download_interrupt_reasons.h" |
24 #include "extensions/common/constants.h" | 24 #include "extensions/common/constants.h" |
25 #include "grit/generated_resources.h" | 25 #include "grit/generated_resources.h" |
| 26 #include "net/base/mime_util.h" |
26 #include "net/base/net_util.h" | 27 #include "net/base/net_util.h" |
27 #include "ui/base/l10n/l10n_util.h" | 28 #include "ui/base/l10n/l10n_util.h" |
28 | 29 |
| 30 #if defined(ENABLE_PLUGINS) |
| 31 #include "chrome/browser/plugins/plugin_prefs.h" |
| 32 #include "content/public/browser/plugin_service.h" |
| 33 #include "content/public/common/webplugininfo.h" |
| 34 #endif |
| 35 |
29 using content::BrowserThread; | 36 using content::BrowserThread; |
30 using content::DownloadItem; | 37 using content::DownloadItem; |
31 | 38 |
32 namespace { | 39 namespace { |
33 | 40 |
34 const base::FilePath::CharType kCrdownloadSuffix[] = | 41 const base::FilePath::CharType kCrdownloadSuffix[] = |
35 FILE_PATH_LITERAL(".crdownload"); | 42 FILE_PATH_LITERAL(".crdownload"); |
36 | 43 |
37 // Condenses the results from HistoryService::GetVisibleVisitCountToHost() to a | 44 // Condenses the results from HistoryService::GetVisibleVisitCountToHost() to a |
38 // single bool. A host is considered visited before if prior visible visits were | 45 // single bool. A host is considered visited before if prior visible visits were |
39 // found in history and the first such visit was earlier than the most recent | 46 // found in history and the first such visit was earlier than the most recent |
40 // midnight. | 47 // midnight. |
41 void VisitCountsToVisitedBefore( | 48 void VisitCountsToVisitedBefore( |
42 const base::Callback<void(bool)>& callback, | 49 const base::Callback<void(bool)>& callback, |
43 HistoryService::Handle unused_handle, | 50 HistoryService::Handle unused_handle, |
44 bool found_visits, | 51 bool found_visits, |
45 int count, | 52 int count, |
46 base::Time first_visit) { | 53 base::Time first_visit) { |
47 callback.Run( | 54 callback.Run( |
48 found_visits && count > 0 && | 55 found_visits && count > 0 && |
49 (first_visit.LocalMidnight() < base::Time::Now().LocalMidnight())); | 56 (first_visit.LocalMidnight() < base::Time::Now().LocalMidnight())); |
50 } | 57 } |
51 | 58 |
52 } // namespace | 59 } // namespace |
53 | 60 |
| 61 DownloadTargetInfo::DownloadTargetInfo() |
| 62 : is_filetype_handled_securely(false) {} |
| 63 |
| 64 DownloadTargetInfo::~DownloadTargetInfo() {} |
| 65 |
54 DownloadTargetDeterminerDelegate::~DownloadTargetDeterminerDelegate() { | 66 DownloadTargetDeterminerDelegate::~DownloadTargetDeterminerDelegate() { |
55 } | 67 } |
56 | 68 |
57 DownloadTargetDeterminer::DownloadTargetDeterminer( | 69 DownloadTargetDeterminer::DownloadTargetDeterminer( |
58 DownloadItem* download, | 70 DownloadItem* download, |
59 const base::FilePath& initial_virtual_path, | 71 const base::FilePath& initial_virtual_path, |
60 DownloadPrefs* download_prefs, | 72 DownloadPrefs* download_prefs, |
61 DownloadTargetDeterminerDelegate* delegate, | 73 DownloadTargetDeterminerDelegate* delegate, |
62 const content::DownloadTargetCallback& callback) | 74 const CompletionCallback& callback) |
63 : next_state_(STATE_GENERATE_TARGET_PATH), | 75 : next_state_(STATE_GENERATE_TARGET_PATH), |
64 should_prompt_(false), | 76 should_prompt_(false), |
65 should_notify_extensions_(false), | 77 should_notify_extensions_(false), |
66 create_target_directory_(false), | 78 create_target_directory_(false), |
67 conflict_action_(DownloadPathReservationTracker::OVERWRITE), | 79 conflict_action_(DownloadPathReservationTracker::OVERWRITE), |
68 danger_type_(download->GetDangerType()), | 80 danger_type_(download->GetDangerType()), |
69 virtual_path_(initial_virtual_path), | 81 virtual_path_(initial_virtual_path), |
| 82 is_filetype_handled_securely_(false), |
70 download_(download), | 83 download_(download), |
71 is_resumption_(download_->GetLastReason() != | 84 is_resumption_(download_->GetLastReason() != |
72 content::DOWNLOAD_INTERRUPT_REASON_NONE && | 85 content::DOWNLOAD_INTERRUPT_REASON_NONE && |
73 !initial_virtual_path.empty()), | 86 !initial_virtual_path.empty()), |
74 download_prefs_(download_prefs), | 87 download_prefs_(download_prefs), |
75 delegate_(delegate), | 88 delegate_(delegate), |
76 completion_callback_(callback), | 89 completion_callback_(callback), |
77 weak_ptr_factory_(this) { | 90 weak_ptr_factory_(this) { |
78 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 91 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
79 DCHECK(download_); | 92 DCHECK(download_); |
(...skipping 25 matching lines...) Expand all Loading... |
105 break; | 118 break; |
106 case STATE_RESERVE_VIRTUAL_PATH: | 119 case STATE_RESERVE_VIRTUAL_PATH: |
107 result = DoReserveVirtualPath(); | 120 result = DoReserveVirtualPath(); |
108 break; | 121 break; |
109 case STATE_PROMPT_USER_FOR_DOWNLOAD_PATH: | 122 case STATE_PROMPT_USER_FOR_DOWNLOAD_PATH: |
110 result = DoPromptUserForDownloadPath(); | 123 result = DoPromptUserForDownloadPath(); |
111 break; | 124 break; |
112 case STATE_DETERMINE_LOCAL_PATH: | 125 case STATE_DETERMINE_LOCAL_PATH: |
113 result = DoDetermineLocalPath(); | 126 result = DoDetermineLocalPath(); |
114 break; | 127 break; |
| 128 case STATE_DETERMINE_MIME_TYPE: |
| 129 result = DoDetermineMimeType(); |
| 130 break; |
| 131 case STATE_DETERMINE_IF_HANDLED_BY_BROWSER: |
| 132 result = DoDetermineIfHandledByBrowser(); |
| 133 break; |
115 case STATE_CHECK_DOWNLOAD_URL: | 134 case STATE_CHECK_DOWNLOAD_URL: |
116 result = DoCheckDownloadUrl(); | 135 result = DoCheckDownloadUrl(); |
117 break; | 136 break; |
118 case STATE_DETERMINE_INTERMEDIATE_PATH: | 137 case STATE_DETERMINE_INTERMEDIATE_PATH: |
119 result = DoDetermineIntermediatePath(); | 138 result = DoDetermineIntermediatePath(); |
120 break; | 139 break; |
121 case STATE_CHECK_VISITED_REFERRER_BEFORE: | 140 case STATE_CHECK_VISITED_REFERRER_BEFORE: |
122 result = DoCheckVisitedReferrerBefore(); | 141 result = DoCheckVisitedReferrerBefore(); |
123 break; | 142 break; |
124 case STATE_NONE: | 143 case STATE_NONE: |
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
298 download_prefs_->SetSaveFilePath(virtual_path_.DirName()); | 317 download_prefs_->SetSaveFilePath(virtual_path_.DirName()); |
299 DoLoop(); | 318 DoLoop(); |
300 } | 319 } |
301 | 320 |
302 DownloadTargetDeterminer::Result | 321 DownloadTargetDeterminer::Result |
303 DownloadTargetDeterminer::DoDetermineLocalPath() { | 322 DownloadTargetDeterminer::DoDetermineLocalPath() { |
304 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 323 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
305 DCHECK(!virtual_path_.empty()); | 324 DCHECK(!virtual_path_.empty()); |
306 DCHECK(local_path_.empty()); | 325 DCHECK(local_path_.empty()); |
307 | 326 |
308 next_state_ = STATE_CHECK_DOWNLOAD_URL; | 327 next_state_ = STATE_DETERMINE_MIME_TYPE; |
309 | 328 |
310 delegate_->DetermineLocalPath( | 329 delegate_->DetermineLocalPath( |
311 download_, | 330 download_, |
312 virtual_path_, | 331 virtual_path_, |
313 base::Bind(&DownloadTargetDeterminer::DetermineLocalPathDone, | 332 base::Bind(&DownloadTargetDeterminer::DetermineLocalPathDone, |
314 weak_ptr_factory_.GetWeakPtr())); | 333 weak_ptr_factory_.GetWeakPtr())); |
315 return QUIT_DOLOOP; | 334 return QUIT_DOLOOP; |
316 } | 335 } |
317 | 336 |
318 void DownloadTargetDeterminer::DetermineLocalPathDone( | 337 void DownloadTargetDeterminer::DetermineLocalPathDone( |
319 const base::FilePath& local_path) { | 338 const base::FilePath& local_path) { |
320 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 339 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
321 DVLOG(20) << "Local path: " << local_path.AsUTF8Unsafe(); | 340 DVLOG(20) << "Local path: " << local_path.AsUTF8Unsafe(); |
322 if (local_path.empty()) { | 341 if (local_path.empty()) { |
323 // Path subsitution failed. | 342 // Path subsitution failed. |
324 CancelOnFailureAndDeleteSelf(); | 343 CancelOnFailureAndDeleteSelf(); |
325 return; | 344 return; |
326 } | 345 } |
327 local_path_ = local_path; | 346 local_path_ = local_path; |
328 DoLoop(); | 347 DoLoop(); |
329 } | 348 } |
330 | 349 |
331 DownloadTargetDeterminer::Result | 350 DownloadTargetDeterminer::Result |
| 351 DownloadTargetDeterminer::DoDetermineMimeType() { |
| 352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 353 DCHECK(!virtual_path_.empty()); |
| 354 DCHECK(!local_path_.empty()); |
| 355 DCHECK(mime_type_.empty()); |
| 356 |
| 357 next_state_ = STATE_DETERMINE_IF_HANDLED_BY_BROWSER; |
| 358 |
| 359 if (virtual_path_ == local_path_) { |
| 360 delegate_->GetFileMimeType( |
| 361 local_path_, |
| 362 base::Bind(&DownloadTargetDeterminer::DetermineMimeTypeDone, |
| 363 weak_ptr_factory_.GetWeakPtr())); |
| 364 return QUIT_DOLOOP; |
| 365 } |
| 366 return CONTINUE; |
| 367 } |
| 368 |
| 369 void DownloadTargetDeterminer::DetermineMimeTypeDone( |
| 370 const std::string& mime_type) { |
| 371 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 372 DVLOG(20) << "MIME type: " << mime_type; |
| 373 mime_type_ = mime_type; |
| 374 DoLoop(); |
| 375 } |
| 376 |
| 377 #if defined(ENABLE_PLUGINS) |
| 378 // The code below is used by DoDetermineIfHandledByBrowser to determine if the |
| 379 // file type is handled by a sandboxed plugin. |
| 380 namespace { |
| 381 |
| 382 typedef std::vector<content::WebPluginInfo> PluginVector; |
| 383 |
| 384 // Returns true if there is a plugin in |plugins| that is sandboxed and enabled |
| 385 // for |profile|. |
| 386 bool IsSafePluginAvailableForProfile(scoped_ptr<PluginVector> plugins, |
| 387 Profile* profile) { |
| 388 using content::WebPluginInfo; |
| 389 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 390 |
| 391 if (plugins->size() == 0) |
| 392 return false; |
| 393 |
| 394 scoped_refptr<PluginPrefs> plugin_prefs = PluginPrefs::GetForProfile(profile); |
| 395 if (!plugin_prefs) |
| 396 return false; |
| 397 |
| 398 for (PluginVector::iterator plugin = plugins->begin(); |
| 399 plugin != plugins->end(); ++plugin) { |
| 400 if (plugin_prefs->IsPluginEnabled(*plugin) && |
| 401 (plugin->type == WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS || |
| 402 plugin->type == WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS)) |
| 403 return true; |
| 404 } |
| 405 return false; |
| 406 } |
| 407 |
| 408 // Returns a callback that determines if a sandboxed plugin is available to |
| 409 // handle |mime_type| for a specific profile. The returned callback must be |
| 410 // invoked on the UI thread, while this function should be called on the IO |
| 411 // thread. |
| 412 base::Callback<bool(Profile*)> GetSafePluginChecker( |
| 413 const GURL& url, |
| 414 const std::string& mime_type) { |
| 415 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 416 DCHECK(!mime_type.empty()); |
| 417 |
| 418 scoped_ptr<PluginVector> plugins(new PluginVector); |
| 419 content::PluginService* plugin_service = |
| 420 content::PluginService::GetInstance(); |
| 421 if (plugin_service) |
| 422 plugin_service->GetPluginInfoArray( |
| 423 url, mime_type, false, plugins.get(), NULL); |
| 424 return base::Bind(&IsSafePluginAvailableForProfile, base::Passed(&plugins)); |
| 425 } |
| 426 |
| 427 } // namespace |
| 428 #endif // ENABLE_PLUGINS |
| 429 |
| 430 DownloadTargetDeterminer::Result |
| 431 DownloadTargetDeterminer::DoDetermineIfHandledByBrowser() { |
| 432 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 433 DCHECK(!virtual_path_.empty()); |
| 434 DCHECK(!local_path_.empty()); |
| 435 DCHECK(!is_filetype_handled_securely_); |
| 436 |
| 437 next_state_ = STATE_CHECK_DOWNLOAD_URL; |
| 438 |
| 439 if (mime_type_.empty()) |
| 440 return CONTINUE; |
| 441 |
| 442 if (net::IsSupportedMimeType(mime_type_)) { |
| 443 is_filetype_handled_securely_ = true; |
| 444 return CONTINUE; |
| 445 } |
| 446 |
| 447 #if defined(ENABLE_PLUGINS) |
| 448 BrowserThread::PostTaskAndReplyWithResult( |
| 449 BrowserThread::IO, |
| 450 FROM_HERE, |
| 451 base::Bind(&GetSafePluginChecker, |
| 452 net::FilePathToFileURL(local_path_), mime_type_), |
| 453 base::Bind(&DownloadTargetDeterminer::DetermineIfHandledByBrowserDone, |
| 454 weak_ptr_factory_.GetWeakPtr())); |
| 455 return QUIT_DOLOOP; |
| 456 #else |
| 457 return CONTINUE; |
| 458 #endif |
| 459 } |
| 460 |
| 461 void DownloadTargetDeterminer::DetermineIfHandledByBrowserDone( |
| 462 const base::Callback<bool(Profile*)>& callback) { |
| 463 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 464 is_filetype_handled_securely_ = callback.Run(GetProfile()); |
| 465 DVLOG(20) << "Is file type handled securely: " |
| 466 << is_filetype_handled_securely_; |
| 467 DoLoop(); |
| 468 } |
| 469 |
| 470 DownloadTargetDeterminer::Result |
332 DownloadTargetDeterminer::DoCheckDownloadUrl() { | 471 DownloadTargetDeterminer::DoCheckDownloadUrl() { |
333 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 472 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
334 DCHECK(!virtual_path_.empty()); | 473 DCHECK(!virtual_path_.empty()); |
335 next_state_ = STATE_CHECK_VISITED_REFERRER_BEFORE; | 474 next_state_ = STATE_CHECK_VISITED_REFERRER_BEFORE; |
336 delegate_->CheckDownloadUrl( | 475 delegate_->CheckDownloadUrl( |
337 download_, | 476 download_, |
338 virtual_path_, | 477 virtual_path_, |
339 base::Bind(&DownloadTargetDeterminer::CheckDownloadUrlDone, | 478 base::Bind(&DownloadTargetDeterminer::CheckDownloadUrlDone, |
340 weak_ptr_factory_.GetWeakPtr())); | 479 weak_ptr_factory_.GetWeakPtr())); |
341 return QUIT_DOLOOP; | 480 return QUIT_DOLOOP; |
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
477 return COMPLETE; | 616 return COMPLETE; |
478 } | 617 } |
479 | 618 |
480 void DownloadTargetDeterminer::ScheduleCallbackAndDeleteSelf() { | 619 void DownloadTargetDeterminer::ScheduleCallbackAndDeleteSelf() { |
481 DCHECK(download_); | 620 DCHECK(download_); |
482 DVLOG(20) << "Scheduling callback. Virtual:" << virtual_path_.AsUTF8Unsafe() | 621 DVLOG(20) << "Scheduling callback. Virtual:" << virtual_path_.AsUTF8Unsafe() |
483 << " Local:" << local_path_.AsUTF8Unsafe() | 622 << " Local:" << local_path_.AsUTF8Unsafe() |
484 << " Intermediate:" << intermediate_path_.AsUTF8Unsafe() | 623 << " Intermediate:" << intermediate_path_.AsUTF8Unsafe() |
485 << " Should prompt:" << should_prompt_ | 624 << " Should prompt:" << should_prompt_ |
486 << " Danger type:" << danger_type_; | 625 << " Danger type:" << danger_type_; |
| 626 scoped_ptr<DownloadTargetInfo> target_info(new DownloadTargetInfo); |
| 627 |
| 628 target_info->target_path = local_path_; |
| 629 target_info->target_disposition = |
| 630 (HasPromptedForPath() || should_prompt_ |
| 631 ? DownloadItem::TARGET_DISPOSITION_PROMPT |
| 632 : DownloadItem::TARGET_DISPOSITION_OVERWRITE); |
| 633 target_info->danger_type = danger_type_; |
| 634 target_info->intermediate_path = intermediate_path_; |
| 635 target_info->mime_type = mime_type_; |
| 636 target_info->is_filetype_handled_securely = is_filetype_handled_securely_; |
| 637 |
487 base::MessageLoop::current()->PostTask( | 638 base::MessageLoop::current()->PostTask( |
488 FROM_HERE, | 639 FROM_HERE, base::Bind(completion_callback_, base::Passed(&target_info))); |
489 base::Bind(completion_callback_, | |
490 local_path_, | |
491 (HasPromptedForPath() || should_prompt_ | |
492 ? DownloadItem::TARGET_DISPOSITION_PROMPT | |
493 : DownloadItem::TARGET_DISPOSITION_OVERWRITE), | |
494 danger_type_, | |
495 intermediate_path_)); | |
496 completion_callback_.Reset(); | 640 completion_callback_.Reset(); |
497 delete this; | 641 delete this; |
498 } | 642 } |
499 | 643 |
500 void DownloadTargetDeterminer::CancelOnFailureAndDeleteSelf() { | 644 void DownloadTargetDeterminer::CancelOnFailureAndDeleteSelf() { |
501 // Path substitution failed. | 645 // Path substitution failed. |
502 virtual_path_.clear(); | 646 virtual_path_.clear(); |
503 local_path_.clear(); | 647 local_path_.clear(); |
504 intermediate_path_.clear(); | 648 intermediate_path_.clear(); |
505 ScheduleCallbackAndDeleteSelf(); | 649 ScheduleCallbackAndDeleteSelf(); |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
624 } | 768 } |
625 | 769 |
626 void DownloadTargetDeterminer::OnDownloadDestroyed( | 770 void DownloadTargetDeterminer::OnDownloadDestroyed( |
627 DownloadItem* download) { | 771 DownloadItem* download) { |
628 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 772 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
629 DCHECK_EQ(download_, download); | 773 DCHECK_EQ(download_, download); |
630 CancelOnFailureAndDeleteSelf(); | 774 CancelOnFailureAndDeleteSelf(); |
631 } | 775 } |
632 | 776 |
633 // static | 777 // static |
634 void DownloadTargetDeterminer::Start( | 778 void DownloadTargetDeterminer::Start(content::DownloadItem* download, |
635 content::DownloadItem* download, | 779 const base::FilePath& initial_virtual_path, |
636 const base::FilePath& initial_virtual_path, | 780 DownloadPrefs* download_prefs, |
637 DownloadPrefs* download_prefs, | 781 DownloadTargetDeterminerDelegate* delegate, |
638 DownloadTargetDeterminerDelegate* delegate, | 782 const CompletionCallback& callback) { |
639 const content::DownloadTargetCallback& callback) { | |
640 // DownloadTargetDeterminer owns itself and will self destruct when the job is | 783 // DownloadTargetDeterminer owns itself and will self destruct when the job is |
641 // complete or the download item is destroyed. The callback is always invoked | 784 // complete or the download item is destroyed. The callback is always invoked |
642 // asynchronously. | 785 // asynchronously. |
643 new DownloadTargetDeterminer(download, initial_virtual_path, download_prefs, | 786 new DownloadTargetDeterminer(download, initial_virtual_path, download_prefs, |
644 delegate, callback); | 787 delegate, callback); |
645 } | 788 } |
646 | 789 |
647 // static | 790 // static |
648 base::FilePath DownloadTargetDeterminer::GetCrDownloadPath( | 791 base::FilePath DownloadTargetDeterminer::GetCrDownloadPath( |
649 const base::FilePath& suggested_path) { | 792 const base::FilePath& suggested_path) { |
650 return base::FilePath(suggested_path.value() + kCrdownloadSuffix); | 793 return base::FilePath(suggested_path.value() + kCrdownloadSuffix); |
651 } | 794 } |
OLD | NEW |