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 "chrome/grit/generated_resources.h"
22#include "content/public/browser/download_manager.h"
23#include "content/public/browser/render_process_host.h"
24#include "content/public/browser/save_page_type.h"
25#include "content/public/browser/web_contents.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
33using content::RenderProcessHost;
34using content::SavePageType;
35using content::WebContents;
36
37namespace {
38
39// If false, we don't prompt the user as to where to save the file.  This
40// exists only for testing.
41bool g_should_prompt_for_filename = true;
42
43void OnSavePackageDownloadCreated(content::DownloadItem* download) {
44  ChromeDownloadManagerDelegate::DisableSafeBrowsing(download);
45}
46
47#if defined(OS_CHROMEOS)
48void 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|.
58void 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.
72void 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("html"));
80  extensions.push_back(FILE_PATH_LITERAL("htm"));
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.
87void 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.
100void 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
117bool 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
126SavePackageFilePicker::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
227SavePackageFilePicker::~SavePackageFilePicker() {
228}
229
230void SavePackageFilePicker::SetShouldPromptUser(bool should_prompt) {
231  g_should_prompt_for_filename = should_prompt;
232}
233
234void 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  base::i18n::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
287void SavePackageFilePicker::FileSelectionCanceled(void* unused_params) {
288  delete this;
289}
290