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" | |
27 #include "net/base/net_util.h" | 26 #include "net/base/net_util.h" |
28 #include "ui/base/l10n/l10n_util.h" | 27 #include "ui/base/l10n/l10n_util.h" |
29 | 28 |
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 | |
36 using content::BrowserThread; | 29 using content::BrowserThread; |
37 using content::DownloadItem; | 30 using content::DownloadItem; |
38 | 31 |
39 namespace { | 32 namespace { |
40 | 33 |
41 const base::FilePath::CharType kCrdownloadSuffix[] = | 34 const base::FilePath::CharType kCrdownloadSuffix[] = |
42 FILE_PATH_LITERAL(".crdownload"); | 35 FILE_PATH_LITERAL(".crdownload"); |
43 | 36 |
44 // Condenses the results from HistoryService::GetVisibleVisitCountToHost() to a | 37 // Condenses the results from HistoryService::GetVisibleVisitCountToHost() to a |
45 // single bool. A host is considered visited before if prior visible visits were | 38 // single bool. A host is considered visited before if prior visible visits were |
46 // found in history and the first such visit was earlier than the most recent | 39 // found in history and the first such visit was earlier than the most recent |
47 // midnight. | 40 // midnight. |
48 void VisitCountsToVisitedBefore( | 41 void VisitCountsToVisitedBefore( |
49 const base::Callback<void(bool)>& callback, | 42 const base::Callback<void(bool)>& callback, |
50 HistoryService::Handle unused_handle, | 43 HistoryService::Handle unused_handle, |
51 bool found_visits, | 44 bool found_visits, |
52 int count, | 45 int count, |
53 base::Time first_visit) { | 46 base::Time first_visit) { |
54 callback.Run( | 47 callback.Run( |
55 found_visits && count > 0 && | 48 found_visits && count > 0 && |
56 (first_visit.LocalMidnight() < base::Time::Now().LocalMidnight())); | 49 (first_visit.LocalMidnight() < base::Time::Now().LocalMidnight())); |
57 } | 50 } |
58 | 51 |
59 } // namespace | 52 } // namespace |
60 | 53 |
61 DownloadTargetInfo::DownloadTargetInfo() | |
62 : is_filetype_handled_securely(false) {} | |
63 | |
64 DownloadTargetInfo::~DownloadTargetInfo() {} | |
65 | |
66 DownloadTargetDeterminerDelegate::~DownloadTargetDeterminerDelegate() { | 54 DownloadTargetDeterminerDelegate::~DownloadTargetDeterminerDelegate() { |
67 } | 55 } |
68 | 56 |
69 DownloadTargetDeterminer::DownloadTargetDeterminer( | 57 DownloadTargetDeterminer::DownloadTargetDeterminer( |
70 DownloadItem* download, | 58 DownloadItem* download, |
71 const base::FilePath& initial_virtual_path, | 59 const base::FilePath& initial_virtual_path, |
72 DownloadPrefs* download_prefs, | 60 DownloadPrefs* download_prefs, |
73 DownloadTargetDeterminerDelegate* delegate, | 61 DownloadTargetDeterminerDelegate* delegate, |
74 const CompletionCallback& callback) | 62 const content::DownloadTargetCallback& callback) |
75 : next_state_(STATE_GENERATE_TARGET_PATH), | 63 : next_state_(STATE_GENERATE_TARGET_PATH), |
76 should_prompt_(false), | 64 should_prompt_(false), |
77 should_notify_extensions_(false), | 65 should_notify_extensions_(false), |
78 create_target_directory_(false), | 66 create_target_directory_(false), |
79 conflict_action_(DownloadPathReservationTracker::OVERWRITE), | 67 conflict_action_(DownloadPathReservationTracker::OVERWRITE), |
80 danger_type_(download->GetDangerType()), | 68 danger_type_(download->GetDangerType()), |
81 virtual_path_(initial_virtual_path), | 69 virtual_path_(initial_virtual_path), |
82 is_filetype_handled_securely_(false), | |
83 download_(download), | 70 download_(download), |
84 is_resumption_(download_->GetLastReason() != | 71 is_resumption_(download_->GetLastReason() != |
85 content::DOWNLOAD_INTERRUPT_REASON_NONE && | 72 content::DOWNLOAD_INTERRUPT_REASON_NONE && |
86 !initial_virtual_path.empty()), | 73 !initial_virtual_path.empty()), |
87 download_prefs_(download_prefs), | 74 download_prefs_(download_prefs), |
88 delegate_(delegate), | 75 delegate_(delegate), |
89 completion_callback_(callback), | 76 completion_callback_(callback), |
90 weak_ptr_factory_(this) { | 77 weak_ptr_factory_(this) { |
91 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 78 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
92 DCHECK(download_); | 79 DCHECK(download_); |
(...skipping 25 matching lines...) Expand all Loading... |
118 break; | 105 break; |
119 case STATE_RESERVE_VIRTUAL_PATH: | 106 case STATE_RESERVE_VIRTUAL_PATH: |
120 result = DoReserveVirtualPath(); | 107 result = DoReserveVirtualPath(); |
121 break; | 108 break; |
122 case STATE_PROMPT_USER_FOR_DOWNLOAD_PATH: | 109 case STATE_PROMPT_USER_FOR_DOWNLOAD_PATH: |
123 result = DoPromptUserForDownloadPath(); | 110 result = DoPromptUserForDownloadPath(); |
124 break; | 111 break; |
125 case STATE_DETERMINE_LOCAL_PATH: | 112 case STATE_DETERMINE_LOCAL_PATH: |
126 result = DoDetermineLocalPath(); | 113 result = DoDetermineLocalPath(); |
127 break; | 114 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; | |
134 case STATE_CHECK_DOWNLOAD_URL: | 115 case STATE_CHECK_DOWNLOAD_URL: |
135 result = DoCheckDownloadUrl(); | 116 result = DoCheckDownloadUrl(); |
136 break; | 117 break; |
137 case STATE_DETERMINE_INTERMEDIATE_PATH: | 118 case STATE_DETERMINE_INTERMEDIATE_PATH: |
138 result = DoDetermineIntermediatePath(); | 119 result = DoDetermineIntermediatePath(); |
139 break; | 120 break; |
140 case STATE_CHECK_VISITED_REFERRER_BEFORE: | 121 case STATE_CHECK_VISITED_REFERRER_BEFORE: |
141 result = DoCheckVisitedReferrerBefore(); | 122 result = DoCheckVisitedReferrerBefore(); |
142 break; | 123 break; |
143 case STATE_NONE: | 124 case STATE_NONE: |
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
317 download_prefs_->SetSaveFilePath(virtual_path_.DirName()); | 298 download_prefs_->SetSaveFilePath(virtual_path_.DirName()); |
318 DoLoop(); | 299 DoLoop(); |
319 } | 300 } |
320 | 301 |
321 DownloadTargetDeterminer::Result | 302 DownloadTargetDeterminer::Result |
322 DownloadTargetDeterminer::DoDetermineLocalPath() { | 303 DownloadTargetDeterminer::DoDetermineLocalPath() { |
323 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 304 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
324 DCHECK(!virtual_path_.empty()); | 305 DCHECK(!virtual_path_.empty()); |
325 DCHECK(local_path_.empty()); | 306 DCHECK(local_path_.empty()); |
326 | 307 |
327 next_state_ = STATE_DETERMINE_MIME_TYPE; | 308 next_state_ = STATE_CHECK_DOWNLOAD_URL; |
328 | 309 |
329 delegate_->DetermineLocalPath( | 310 delegate_->DetermineLocalPath( |
330 download_, | 311 download_, |
331 virtual_path_, | 312 virtual_path_, |
332 base::Bind(&DownloadTargetDeterminer::DetermineLocalPathDone, | 313 base::Bind(&DownloadTargetDeterminer::DetermineLocalPathDone, |
333 weak_ptr_factory_.GetWeakPtr())); | 314 weak_ptr_factory_.GetWeakPtr())); |
334 return QUIT_DOLOOP; | 315 return QUIT_DOLOOP; |
335 } | 316 } |
336 | 317 |
337 void DownloadTargetDeterminer::DetermineLocalPathDone( | 318 void DownloadTargetDeterminer::DetermineLocalPathDone( |
338 const base::FilePath& local_path) { | 319 const base::FilePath& local_path) { |
339 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 320 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
340 DVLOG(20) << "Local path: " << local_path.AsUTF8Unsafe(); | 321 DVLOG(20) << "Local path: " << local_path.AsUTF8Unsafe(); |
341 if (local_path.empty()) { | 322 if (local_path.empty()) { |
342 // Path subsitution failed. | 323 // Path subsitution failed. |
343 CancelOnFailureAndDeleteSelf(); | 324 CancelOnFailureAndDeleteSelf(); |
344 return; | 325 return; |
345 } | 326 } |
346 local_path_ = local_path; | 327 local_path_ = local_path; |
347 DoLoop(); | 328 DoLoop(); |
348 } | 329 } |
349 | 330 |
350 DownloadTargetDeterminer::Result | 331 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 | |
471 DownloadTargetDeterminer::DoCheckDownloadUrl() { | 332 DownloadTargetDeterminer::DoCheckDownloadUrl() { |
472 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 333 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
473 DCHECK(!virtual_path_.empty()); | 334 DCHECK(!virtual_path_.empty()); |
474 next_state_ = STATE_CHECK_VISITED_REFERRER_BEFORE; | 335 next_state_ = STATE_CHECK_VISITED_REFERRER_BEFORE; |
475 delegate_->CheckDownloadUrl( | 336 delegate_->CheckDownloadUrl( |
476 download_, | 337 download_, |
477 virtual_path_, | 338 virtual_path_, |
478 base::Bind(&DownloadTargetDeterminer::CheckDownloadUrlDone, | 339 base::Bind(&DownloadTargetDeterminer::CheckDownloadUrlDone, |
479 weak_ptr_factory_.GetWeakPtr())); | 340 weak_ptr_factory_.GetWeakPtr())); |
480 return QUIT_DOLOOP; | 341 return QUIT_DOLOOP; |
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
616 return COMPLETE; | 477 return COMPLETE; |
617 } | 478 } |
618 | 479 |
619 void DownloadTargetDeterminer::ScheduleCallbackAndDeleteSelf() { | 480 void DownloadTargetDeterminer::ScheduleCallbackAndDeleteSelf() { |
620 DCHECK(download_); | 481 DCHECK(download_); |
621 DVLOG(20) << "Scheduling callback. Virtual:" << virtual_path_.AsUTF8Unsafe() | 482 DVLOG(20) << "Scheduling callback. Virtual:" << virtual_path_.AsUTF8Unsafe() |
622 << " Local:" << local_path_.AsUTF8Unsafe() | 483 << " Local:" << local_path_.AsUTF8Unsafe() |
623 << " Intermediate:" << intermediate_path_.AsUTF8Unsafe() | 484 << " Intermediate:" << intermediate_path_.AsUTF8Unsafe() |
624 << " Should prompt:" << should_prompt_ | 485 << " Should prompt:" << should_prompt_ |
625 << " Danger type:" << danger_type_; | 486 << " 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 | |
638 base::MessageLoop::current()->PostTask( | 487 base::MessageLoop::current()->PostTask( |
639 FROM_HERE, base::Bind(completion_callback_, base::Passed(&target_info))); | 488 FROM_HERE, |
| 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_)); |
640 completion_callback_.Reset(); | 496 completion_callback_.Reset(); |
641 delete this; | 497 delete this; |
642 } | 498 } |
643 | 499 |
644 void DownloadTargetDeterminer::CancelOnFailureAndDeleteSelf() { | 500 void DownloadTargetDeterminer::CancelOnFailureAndDeleteSelf() { |
645 // Path substitution failed. | 501 // Path substitution failed. |
646 virtual_path_.clear(); | 502 virtual_path_.clear(); |
647 local_path_.clear(); | 503 local_path_.clear(); |
648 intermediate_path_.clear(); | 504 intermediate_path_.clear(); |
649 ScheduleCallbackAndDeleteSelf(); | 505 ScheduleCallbackAndDeleteSelf(); |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
768 } | 624 } |
769 | 625 |
770 void DownloadTargetDeterminer::OnDownloadDestroyed( | 626 void DownloadTargetDeterminer::OnDownloadDestroyed( |
771 DownloadItem* download) { | 627 DownloadItem* download) { |
772 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 628 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
773 DCHECK_EQ(download_, download); | 629 DCHECK_EQ(download_, download); |
774 CancelOnFailureAndDeleteSelf(); | 630 CancelOnFailureAndDeleteSelf(); |
775 } | 631 } |
776 | 632 |
777 // static | 633 // static |
778 void DownloadTargetDeterminer::Start(content::DownloadItem* download, | 634 void DownloadTargetDeterminer::Start( |
779 const base::FilePath& initial_virtual_path, | 635 content::DownloadItem* download, |
780 DownloadPrefs* download_prefs, | 636 const base::FilePath& initial_virtual_path, |
781 DownloadTargetDeterminerDelegate* delegate, | 637 DownloadPrefs* download_prefs, |
782 const CompletionCallback& callback) { | 638 DownloadTargetDeterminerDelegate* delegate, |
| 639 const content::DownloadTargetCallback& callback) { |
783 // DownloadTargetDeterminer owns itself and will self destruct when the job is | 640 // DownloadTargetDeterminer owns itself and will self destruct when the job is |
784 // complete or the download item is destroyed. The callback is always invoked | 641 // complete or the download item is destroyed. The callback is always invoked |
785 // asynchronously. | 642 // asynchronously. |
786 new DownloadTargetDeterminer(download, initial_virtual_path, download_prefs, | 643 new DownloadTargetDeterminer(download, initial_virtual_path, download_prefs, |
787 delegate, callback); | 644 delegate, callback); |
788 } | 645 } |
789 | 646 |
790 // static | 647 // static |
791 base::FilePath DownloadTargetDeterminer::GetCrDownloadPath( | 648 base::FilePath DownloadTargetDeterminer::GetCrDownloadPath( |
792 const base::FilePath& suggested_path) { | 649 const base::FilePath& suggested_path) { |
793 return base::FilePath(suggested_path.value() + kCrdownloadSuffix); | 650 return base::FilePath(suggested_path.value() + kCrdownloadSuffix); |
794 } | 651 } |
OLD | NEW |