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