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 "grit/generated_resources.h"
     26 #include "ui/base/l10n/l10n_util.h"
     27 
     28 #if defined(OS_CHROMEOS)
     29 #include "chrome/browser/chromeos/drive/download_handler.h"
     30 #include "chrome/browser/chromeos/drive/file_system_util.h"
     31 #endif
     32 
     33 using content::RenderProcessHost;
     34 using content::SavePageType;
     35 using content::WebContents;
     36 
     37 namespace {
     38 
     39 // If false, we don't prompt the user as to where to save the file.  This
     40 // exists only for testing.
     41 bool g_should_prompt_for_filename = true;
     42 
     43 void OnSavePackageDownloadCreated(content::DownloadItem* download) {
     44   ChromeDownloadManagerDelegate::DisableSafeBrowsing(download);
     45 }
     46 
     47 #if defined(OS_CHROMEOS)
     48 void OnSavePackageDownloadCreatedChromeOS(
     49     Profile* profile,
     50     const base::FilePath& drive_path,
     51     content::DownloadItem* download) {
     52   drive::DownloadHandler::GetForProfile(profile)->SetDownloadParams(
     53       drive_path, download);
     54   OnSavePackageDownloadCreated(download);
     55 }
     56 
     57 // Trampoline callback between SubstituteDriveDownloadPath() and |callback|.
     58 void ContinueSettingUpDriveDownload(
     59     const content::SavePackagePathPickedCallback& callback,
     60     content::SavePageType save_type,
     61     Profile* profile,
     62     const base::FilePath& drive_path,
     63     const base::FilePath& drive_tmp_download_path) {
     64   if (drive_tmp_download_path.empty())  // Substitution failed.
     65     return;
     66   callback.Run(drive_tmp_download_path, save_type, base::Bind(
     67       &OnSavePackageDownloadCreatedChromeOS, profile, drive_path));
     68 }
     69 #endif
     70 
     71 // Adds "Webpage, HTML Only" type to FileTypeInfo.
     72 void AddHtmlOnlyFileTypeInfo(
     73     ui::SelectFileDialog::FileTypeInfo* file_type_info,
     74     const base::FilePath::StringType& extra_extension) {
     75   file_type_info->extension_description_overrides.push_back(
     76       l10n_util::GetStringUTF16(IDS_SAVE_PAGE_DESC_HTML_ONLY));
     77 
     78   std::vector<base::FilePath::StringType> extensions;
     79   extensions.push_back(FILE_PATH_LITERAL("htm"));
     80   extensions.push_back(FILE_PATH_LITERAL("html"));
     81   if (!extra_extension.empty())
     82     extensions.push_back(extra_extension);
     83   file_type_info->extensions.push_back(extensions);
     84 }
     85 
     86 // Adds "Web Archive, Single File" type to FileTypeInfo.
     87 void AddSingleFileFileTypeInfo(
     88     ui::SelectFileDialog::FileTypeInfo* file_type_info) {
     89   file_type_info->extension_description_overrides.push_back(
     90       l10n_util::GetStringUTF16(IDS_SAVE_PAGE_DESC_SINGLE_FILE));
     91 
     92   std::vector<base::FilePath::StringType> extensions;
     93   extensions.push_back(FILE_PATH_LITERAL("mhtml"));
     94   file_type_info->extensions.push_back(extensions);
     95 }
     96 
     97 // Chrome OS doesn't support HTML-Complete. crbug.com/154823
     98 #if !defined(OS_CHROMEOS)
     99 // Adds "Webpage, Complete" type to FileTypeInfo.
    100 void AddCompleteFileTypeInfo(
    101     ui::SelectFileDialog::FileTypeInfo* file_type_info,
    102     const base::FilePath::StringType& extra_extension) {
    103   file_type_info->extension_description_overrides.push_back(
    104       l10n_util::GetStringUTF16(IDS_SAVE_PAGE_DESC_COMPLETE));
    105 
    106   std::vector<base::FilePath::StringType> extensions;
    107   extensions.push_back(FILE_PATH_LITERAL("htm"));
    108   extensions.push_back(FILE_PATH_LITERAL("html"));
    109   if (!extra_extension.empty())
    110     extensions.push_back(extra_extension);
    111   file_type_info->extensions.push_back(extensions);
    112 }
    113 #endif
    114 
    115 }  // anonymous namespace
    116 
    117 bool SavePackageFilePicker::ShouldSaveAsMHTML() const {
    118 #if !defined(OS_CHROMEOS)
    119   if (!CommandLine::ForCurrentProcess()->HasSwitch(
    120              switches::kSavePageAsMHTML))
    121     return false;
    122 #endif
    123   return can_save_as_complete_;
    124 }
    125 
    126 SavePackageFilePicker::SavePackageFilePicker(
    127     content::WebContents* web_contents,
    128     const base::FilePath& suggested_path,
    129     const base::FilePath::StringType& default_extension,
    130     bool can_save_as_complete,
    131     DownloadPrefs* download_prefs,
    132     const content::SavePackagePathPickedCallback& callback)
    133     : render_process_id_(web_contents->GetRenderProcessHost()->GetID()),
    134       can_save_as_complete_(can_save_as_complete),
    135       download_prefs_(download_prefs),
    136       callback_(callback) {
    137   base::FilePath suggested_path_copy = suggested_path;
    138   base::FilePath::StringType default_extension_copy = default_extension;
    139   int file_type_index = 0;
    140   ui::SelectFileDialog::FileTypeInfo file_type_info;
    141 
    142   file_type_info.support_drive = true;
    143 
    144   if (can_save_as_complete_) {
    145     // The option index is not zero-based. Put a dummy entry.
    146     save_types_.push_back(content::SAVE_PAGE_TYPE_UNKNOWN);
    147 
    148     base::FilePath::StringType extra_extension;
    149     if (ShouldSaveAsMHTML()) {
    150       default_extension_copy = FILE_PATH_LITERAL("mhtml");
    151       suggested_path_copy = suggested_path_copy.ReplaceExtension(
    152           default_extension_copy);
    153     } else {
    154       if (!suggested_path_copy.FinalExtension().empty() &&
    155           !suggested_path_copy.MatchesExtension(FILE_PATH_LITERAL(".htm")) &&
    156           !suggested_path_copy.MatchesExtension(FILE_PATH_LITERAL(".html"))) {
    157         extra_extension = suggested_path_copy.FinalExtension().substr(1);
    158       }
    159     }
    160 
    161     AddHtmlOnlyFileTypeInfo(&file_type_info, extra_extension);
    162     save_types_.push_back(content::SAVE_PAGE_TYPE_AS_ONLY_HTML);
    163 
    164     if (ShouldSaveAsMHTML()) {
    165       AddSingleFileFileTypeInfo(&file_type_info);
    166       save_types_.push_back(content::SAVE_PAGE_TYPE_AS_MHTML);
    167     }
    168 
    169 #if !defined(OS_CHROMEOS)
    170     AddCompleteFileTypeInfo(&file_type_info, extra_extension);
    171     save_types_.push_back(content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML);
    172 #endif
    173 
    174     file_type_info.include_all_files = false;
    175 
    176     content::SavePageType preferred_save_type =
    177         static_cast<content::SavePageType>(download_prefs_->save_file_type());
    178     if (ShouldSaveAsMHTML())
    179       preferred_save_type = content::SAVE_PAGE_TYPE_AS_MHTML;
    180 
    181     // Select the item saved in the pref.
    182     for (size_t i = 0; i < save_types_.size(); ++i) {
    183       if (save_types_[i] == preferred_save_type) {
    184         file_type_index = i;
    185         break;
    186       }
    187     }
    188 
    189     // If the item saved in the pref was not found, use the last item.
    190     if (!file_type_index)
    191       file_type_index = save_types_.size() - 1;
    192   } else {
    193     // The contents can not be saved as complete-HTML, so do not show the file
    194     // filters.
    195     file_type_info.extensions.resize(1);
    196     file_type_info.extensions[0].push_back(
    197         suggested_path_copy.FinalExtension());
    198 
    199     if (!file_type_info.extensions[0][0].empty()) {
    200       // Drop the .
    201       file_type_info.extensions[0][0].erase(0, 1);
    202     }
    203 
    204     file_type_info.include_all_files = true;
    205     file_type_index = 1;
    206   }
    207 
    208   if (g_should_prompt_for_filename) {
    209     select_file_dialog_ = ui::SelectFileDialog::Create(
    210         this, new ChromeSelectFilePolicy(web_contents));
    211     select_file_dialog_->SelectFile(
    212         ui::SelectFileDialog::SELECT_SAVEAS_FILE,
    213         base::string16(),
    214         suggested_path_copy,
    215         &file_type_info,
    216         file_type_index,
    217         default_extension_copy,
    218         platform_util::GetTopLevel(web_contents->GetNativeView()),
    219         NULL);
    220   } else {
    221     // Just use 'suggested_path_copy' instead of opening the dialog prompt.
    222     // Go through FileSelected() for consistency.
    223     FileSelected(suggested_path_copy, file_type_index, NULL);
    224   }
    225 }
    226 
    227 SavePackageFilePicker::~SavePackageFilePicker() {
    228 }
    229 
    230 void SavePackageFilePicker::SetShouldPromptUser(bool should_prompt) {
    231   g_should_prompt_for_filename = should_prompt;
    232 }
    233 
    234 void SavePackageFilePicker::FileSelected(
    235     const base::FilePath& path, int index, void* unused_params) {
    236   scoped_ptr<SavePackageFilePicker> delete_this(this);
    237   RenderProcessHost* process = RenderProcessHost::FromID(render_process_id_);
    238   if (!process)
    239     return;
    240   SavePageType save_type = content::SAVE_PAGE_TYPE_UNKNOWN;
    241 
    242   if (can_save_as_complete_) {
    243     DCHECK_LT(index, static_cast<int>(save_types_.size()));
    244     save_type = save_types_[index];
    245     if (select_file_dialog_.get() &&
    246         select_file_dialog_->HasMultipleFileTypeChoices())
    247       download_prefs_->SetSaveFileType(save_type);
    248 
    249     UMA_HISTOGRAM_ENUMERATION("Download.SavePageType",
    250                               save_type,
    251                               content::SAVE_PAGE_TYPE_MAX);
    252   } else {
    253     // Use "HTML Only" type as a dummy.
    254     save_type = content::SAVE_PAGE_TYPE_AS_ONLY_HTML;
    255   }
    256 
    257   base::FilePath path_copy(path);
    258   file_util::NormalizeFileNameEncoding(&path_copy);
    259 
    260   download_prefs_->SetSaveFilePath(path_copy.DirName());
    261 
    262 #if defined(OS_CHROMEOS)
    263   if (drive::util::IsUnderDriveMountPoint(path_copy)) {
    264     // Here's a map to the callback chain:
    265     // SubstituteDriveDownloadPath ->
    266     //   ContinueSettingUpDriveDownload ->
    267     //     callback_ = SavePackage::OnPathPicked ->
    268     //       download_created_callback = OnSavePackageDownloadCreatedChromeOS
    269     Profile* profile = Profile::FromBrowserContext(
    270         process->GetBrowserContext());
    271     drive::DownloadHandler* drive_download_handler =
    272         drive::DownloadHandler::GetForProfile(profile);
    273     drive_download_handler->SubstituteDriveDownloadPath(
    274         path_copy, NULL, base::Bind(&ContinueSettingUpDriveDownload,
    275                                     callback_,
    276                                     save_type,
    277                                     profile,
    278                                     path_copy));
    279     return;
    280   }
    281 #endif
    282 
    283   callback_.Run(path_copy, save_type,
    284                 base::Bind(&OnSavePackageDownloadCreated));
    285 }
    286 
    287 void SavePackageFilePicker::FileSelectionCanceled(void* unused_params) {
    288   delete this;
    289 }
    290