Home | History | Annotate | Download | only in download
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/download/save_package_file_picker.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/command_line.h"
      9 #include "base/i18n/file_util_icu.h"
     10 #include "base/metrics/histogram.h"
     11 #include "base/prefs/pref_member.h"
     12 #include "base/prefs/pref_service.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "chrome/browser/download/chrome_download_manager_delegate.h"
     15 #include "chrome/browser/download/download_prefs.h"
     16 #include "chrome/browser/platform_util.h"
     17 #include "chrome/browser/profiles/profile.h"
     18 #include "chrome/browser/ui/chrome_select_file_policy.h"
     19 #include "chrome/common/chrome_switches.h"
     20 #include "chrome/common/pref_names.h"
     21 #include "content/public/browser/download_manager.h"
     22 #include "content/public/browser/render_process_host.h"
     23 #include "content/public/browser/save_page_type.h"
     24 #include "content/public/browser/web_contents.h"
     25 #include "content/public/browser/web_contents_view.h"
     26 #include "grit/generated_resources.h"
     27 #include "ui/base/l10n/l10n_util.h"
     28 
     29 #if defined(OS_CHROMEOS)
     30 #include "chrome/browser/chromeos/drive/download_handler.h"
     31 #include "chrome/browser/chromeos/drive/file_system_util.h"
     32 #endif
     33 
     34 using content::RenderProcessHost;
     35 using content::SavePageType;
     36 using content::WebContents;
     37 
     38 namespace {
     39 
     40 // If false, we don't prompt the user as to where to save the file.  This
     41 // exists only for testing.
     42 bool g_should_prompt_for_filename = true;
     43 
     44 // Used for mapping between SavePageType constants and the indexes above.
     45 const SavePageType kIndexToSaveType[] = {
     46   content::SAVE_PAGE_TYPE_UNKNOWN,
     47   content::SAVE_PAGE_TYPE_AS_ONLY_HTML,
     48   content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML,
     49 };
     50 
     51 int SavePackageTypeToIndex(SavePageType type) {
     52   for (size_t i = 0; i < arraysize(kIndexToSaveType); ++i) {
     53     if (kIndexToSaveType[i] == type)
     54       return i;
     55   }
     56   NOTREACHED();
     57   return -1;
     58 }
     59 
     60 // Indexes used for specifying which element in the extensions dropdown
     61 // the user chooses when picking a save type.
     62 const int kSelectFileHtmlOnlyIndex = 1;
     63 const int kSelectFileCompleteIndex = 2;
     64 
     65 // Used for mapping between the IDS_ string identifiers and the indexes above.
     66 const int kIndexToIDS[] = {
     67   0, IDS_SAVE_PAGE_DESC_HTML_ONLY, IDS_SAVE_PAGE_DESC_COMPLETE,
     68 };
     69 
     70 void OnSavePackageDownloadCreated(content::DownloadItem* download) {
     71   ChromeDownloadManagerDelegate::DisableSafeBrowsing(download);
     72 }
     73 
     74 #if defined(OS_CHROMEOS)
     75 void OnSavePackageDownloadCreatedChromeOS(
     76     Profile* profile,
     77     const base::FilePath& drive_path,
     78     content::DownloadItem* download) {
     79   drive::DownloadHandler::GetForProfile(profile)->SetDownloadParams(
     80       drive_path, download);
     81   OnSavePackageDownloadCreated(download);
     82 }
     83 
     84 // Trampoline callback between SubstituteDriveDownloadPath() and |callback|.
     85 void ContinueSettingUpDriveDownload(
     86     const content::SavePackagePathPickedCallback& callback,
     87     content::SavePageType save_type,
     88     Profile* profile,
     89     const base::FilePath& drive_path,
     90     const base::FilePath& drive_tmp_download_path) {
     91   if (drive_tmp_download_path.empty())  // Substitution failed.
     92     return;
     93   callback.Run(drive_tmp_download_path, save_type, base::Bind(
     94       &OnSavePackageDownloadCreatedChromeOS, profile, drive_path));
     95 }
     96 #endif
     97 
     98 }  // anonymous namespace
     99 
    100 bool SavePackageFilePicker::ShouldSaveAsMHTML() const {
    101 #if !defined(OS_CHROMEOS)
    102   if (!CommandLine::ForCurrentProcess()->HasSwitch(
    103              switches::kSavePageAsMHTML))
    104     return false;
    105 #endif
    106   return can_save_as_complete_;
    107 }
    108 
    109 SavePackageFilePicker::SavePackageFilePicker(
    110     content::WebContents* web_contents,
    111     const base::FilePath& suggested_path,
    112     const base::FilePath::StringType& default_extension,
    113     bool can_save_as_complete,
    114     DownloadPrefs* download_prefs,
    115     const content::SavePackagePathPickedCallback& callback)
    116     : render_process_id_(web_contents->GetRenderProcessHost()->GetID()),
    117       can_save_as_complete_(can_save_as_complete),
    118       download_prefs_(download_prefs),
    119       callback_(callback) {
    120   base::FilePath suggested_path_copy = suggested_path;
    121   base::FilePath::StringType default_extension_copy = default_extension;
    122   int file_type_index = 0;
    123   ui::SelectFileDialog::FileTypeInfo file_type_info;
    124 
    125 #if defined(OS_CHROMEOS)
    126   file_type_info.support_drive = true;
    127 #else
    128   file_type_index = SavePackageTypeToIndex(
    129       static_cast<SavePageType>(download_prefs_->save_file_type()));
    130   DCHECK_NE(-1, file_type_index);
    131 #endif
    132 
    133   // TODO(benjhayden): Merge the first branch with the second when all of the
    134   // platform-specific file selection dialog implementations fully support
    135   // switching save-as file formats, and remove the flag/switch.
    136   if (ShouldSaveAsMHTML()) {
    137     default_extension_copy = FILE_PATH_LITERAL("mhtml");
    138     suggested_path_copy = suggested_path_copy.ReplaceExtension(
    139         default_extension_copy);
    140   } else if (can_save_as_complete_) {
    141     // NOTE: this branch will never run on chromeos because ShouldSaveAsHTML()
    142     // == can_save_as_complete_ on chromeos.
    143     bool add_extra_extension = false;
    144     base::FilePath::StringType extra_extension;
    145     if (!suggested_path_copy.Extension().empty() &&
    146         !suggested_path_copy.MatchesExtension(FILE_PATH_LITERAL(".htm")) &&
    147         !suggested_path_copy.MatchesExtension(FILE_PATH_LITERAL(".html"))) {
    148       add_extra_extension = true;
    149       extra_extension = suggested_path_copy.Extension().substr(1);
    150     }
    151 
    152     static const size_t kNumberExtensions = arraysize(kIndexToIDS) - 1;
    153     file_type_info.extensions.resize(kNumberExtensions);
    154     file_type_info.extension_description_overrides.resize(kNumberExtensions);
    155 
    156     // Indices into kIndexToIDS are 1-based whereas indices into
    157     // file_type_info.extensions are 0-based. Hence the '-1's.
    158     // If you switch these resize()/direct-assignment patterns to push_back(),
    159     // then you risk breaking FileSelected()'s use of |index|.
    160 
    161     file_type_info.extension_description_overrides[
    162       kSelectFileHtmlOnlyIndex - 1] = l10n_util::GetStringUTF16(kIndexToIDS[
    163           kSelectFileHtmlOnlyIndex]);
    164     file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1].push_back(
    165         FILE_PATH_LITERAL("htm"));
    166     file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1].push_back(
    167         FILE_PATH_LITERAL("html"));
    168     if (add_extra_extension) {
    169       file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1].push_back(
    170           extra_extension);
    171     }
    172 
    173     file_type_info.extension_description_overrides[
    174       kSelectFileCompleteIndex - 1] = l10n_util::GetStringUTF16(kIndexToIDS[
    175           kSelectFileCompleteIndex]);
    176     file_type_info.extensions[kSelectFileCompleteIndex - 1].push_back(
    177         FILE_PATH_LITERAL("htm"));
    178     file_type_info.extensions[kSelectFileCompleteIndex - 1].push_back(
    179         FILE_PATH_LITERAL("html"));
    180     if (add_extra_extension) {
    181       file_type_info.extensions[kSelectFileCompleteIndex - 1].push_back(
    182           extra_extension);
    183     }
    184 
    185     file_type_info.include_all_files = false;
    186   } else {
    187     // The contents can not be saved as complete-HTML, so do not show the file
    188     // filters.
    189     file_type_info.extensions.resize(1);
    190     file_type_info.extensions[0].push_back(suggested_path_copy.Extension());
    191 
    192     if (!file_type_info.extensions[0][0].empty()) {
    193       // Drop the .
    194       file_type_info.extensions[0][0].erase(0, 1);
    195     }
    196 
    197     file_type_info.include_all_files = true;
    198     file_type_index = 1;
    199   }
    200 
    201   if (g_should_prompt_for_filename) {
    202     select_file_dialog_ = ui::SelectFileDialog::Create(
    203         this, new ChromeSelectFilePolicy(web_contents));
    204     select_file_dialog_->SelectFile(
    205         ui::SelectFileDialog::SELECT_SAVEAS_FILE,
    206         string16(),
    207         suggested_path_copy,
    208         &file_type_info,
    209         file_type_index,
    210         default_extension_copy,
    211         platform_util::GetTopLevel(web_contents->GetView()->GetNativeView()),
    212         NULL);
    213   } else {
    214     // Just use 'suggested_path_copy' instead of opening the dialog prompt.
    215     // Go through FileSelected() for consistency.
    216     FileSelected(suggested_path_copy, file_type_index, NULL);
    217   }
    218 }
    219 
    220 SavePackageFilePicker::~SavePackageFilePicker() {
    221 }
    222 
    223 void SavePackageFilePicker::SetShouldPromptUser(bool should_prompt) {
    224   g_should_prompt_for_filename = should_prompt;
    225 }
    226 
    227 void SavePackageFilePicker::FileSelected(
    228     const base::FilePath& path, int index, void* unused_params) {
    229   scoped_ptr<SavePackageFilePicker> delete_this(this);
    230   RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
    231   if (!process)
    232     return;
    233   SavePageType save_type = content::SAVE_PAGE_TYPE_UNKNOWN;
    234 
    235   if (ShouldSaveAsMHTML()) {
    236     save_type = content::SAVE_PAGE_TYPE_AS_MHTML;
    237   } else {
    238 #if defined(OS_CHROMEOS)
    239     save_type = content::SAVE_PAGE_TYPE_AS_ONLY_HTML;
    240 #else
    241     // The option index is not zero-based.
    242     DCHECK(index >= kSelectFileHtmlOnlyIndex &&
    243            index <= kSelectFileCompleteIndex);
    244     save_type = kIndexToSaveType[index];
    245     if (select_file_dialog_.get() &&
    246         select_file_dialog_->HasMultipleFileTypeChoices())
    247       download_prefs_->SetSaveFileType(save_type);
    248 #endif
    249   }
    250 
    251   UMA_HISTOGRAM_ENUMERATION("Download.SavePageType",
    252                             save_type,
    253                             content::SAVE_PAGE_TYPE_MAX);
    254 
    255   base::FilePath path_copy(path);
    256   file_util::NormalizeFileNameEncoding(&path_copy);
    257 
    258   download_prefs_->SetSaveFilePath(path_copy.DirName());
    259 
    260 #if defined(OS_CHROMEOS)
    261   if (drive::util::IsUnderDriveMountPoint(path_copy)) {
    262     // Here's a map to the callback chain:
    263     // SubstituteDriveDownloadPath ->
    264     //   ContinueSettingUpDriveDownload ->
    265     //     callback_ = SavePackage::OnPathPicked ->
    266     //       download_created_callback = OnSavePackageDownloadCreatedChromeOS
    267     Profile* profile = Profile::FromBrowserContext(
    268         process->GetBrowserContext());
    269     drive::DownloadHandler* drive_download_handler =
    270         drive::DownloadHandler::GetForProfile(profile);
    271     drive_download_handler->SubstituteDriveDownloadPath(
    272         path_copy, NULL, base::Bind(&ContinueSettingUpDriveDownload,
    273                                     callback_,
    274                                     save_type,
    275                                     profile,
    276                                     path_copy));
    277     return;
    278   }
    279 #endif
    280 
    281   callback_.Run(path_copy, save_type,
    282                 base::Bind(&OnSavePackageDownloadCreated));
    283 }
    284 
    285 void SavePackageFilePicker::FileSelectionCanceled(void* unused_params) {
    286   delete this;
    287 }
    288