1// Copyright (c) 2011 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.h"
6
7#include <algorithm>
8
9#include "base/file_path.h"
10#include "base/file_util.h"
11#include "base/i18n/file_util_icu.h"
12#include "base/logging.h"
13#include "base/message_loop.h"
14#include "base/stl_util-inl.h"
15#include "base/string_piece.h"
16#include "base/string_split.h"
17#include "base/sys_string_conversions.h"
18#include "base/task.h"
19#include "base/threading/thread.h"
20#include "base/utf_string_conversions.h"
21#include "chrome/browser/browser_process.h"
22#include "chrome/browser/download/download_item.h"
23#include "chrome/browser/download/download_item_model.h"
24#include "chrome/browser/download/download_manager.h"
25#include "chrome/browser/download/download_prefs.h"
26#include "chrome/browser/download/download_shelf.h"
27#include "chrome/browser/download/download_util.h"
28#include "chrome/browser/download/save_file.h"
29#include "chrome/browser/download/save_file_manager.h"
30#include "chrome/browser/download/save_item.h"
31#include "chrome/browser/net/url_fixer_upper.h"
32#include "chrome/browser/platform_util.h"
33#include "chrome/browser/prefs/pref_member.h"
34#include "chrome/browser/prefs/pref_service.h"
35#include "chrome/browser/profiles/profile.h"
36#include "chrome/browser/tab_contents/tab_util.h"
37#include "chrome/common/chrome_paths.h"
38#include "chrome/common/pref_names.h"
39#include "chrome/common/render_messages.h"
40#include "chrome/common/url_constants.h"
41#include "content/browser/browser_thread.h"
42#include "content/browser/renderer_host/render_process_host.h"
43#include "content/browser/renderer_host/render_view_host.h"
44#include "content/browser/renderer_host/render_view_host_delegate.h"
45#include "content/browser/renderer_host/resource_dispatcher_host.h"
46#include "content/browser/tab_contents/tab_contents.h"
47#include "content/common/notification_service.h"
48#include "content/common/notification_type.h"
49#include "grit/generated_resources.h"
50#include "net/base/io_buffer.h"
51#include "net/base/mime_util.h"
52#include "net/base/net_util.h"
53#include "net/url_request/url_request_context.h"
54#include "third_party/WebKit/Source/WebKit/chromium/public/WebPageSerializerClient.h"
55#include "ui/base/l10n/l10n_util.h"
56#include "net/url_request/url_request_context_getter.h"
57
58using base::Time;
59using WebKit::WebPageSerializerClient;
60
61namespace {
62
63// A counter for uniquely identifying each save package.
64int g_save_package_id = 0;
65
66// Default name which will be used when we can not get proper name from
67// resource URL.
68const char kDefaultSaveName[] = "saved_resource";
69
70const FilePath::CharType kDefaultHtmlExtension[] =
71#if defined(OS_WIN)
72    FILE_PATH_LITERAL("htm");
73#else
74    FILE_PATH_LITERAL("html");
75#endif
76
77// Maximum number of file ordinal number. I think it's big enough for resolving
78// name-conflict files which has same base file name.
79const int32 kMaxFileOrdinalNumber = 9999;
80
81// Maximum length for file path. Since Windows have MAX_PATH limitation for
82// file path, we need to make sure length of file path of every saved file
83// is less than MAX_PATH
84#if defined(OS_WIN)
85const uint32 kMaxFilePathLength = MAX_PATH - 1;
86#elif defined(OS_POSIX)
87const uint32 kMaxFilePathLength = PATH_MAX - 1;
88#endif
89
90// Maximum length for file ordinal number part. Since we only support the
91// maximum 9999 for ordinal number, which means maximum file ordinal number part
92// should be "(9998)", so the value is 6.
93const uint32 kMaxFileOrdinalNumberPartLength = 6;
94
95// If false, we don't prompt the user as to where to save the file.  This
96// exists only for testing.
97bool g_should_prompt_for_filename = true;
98
99// Indexes used for specifying which element in the extensions dropdown
100// the user chooses when picking a save type.
101const int kSelectFileHtmlOnlyIndex = 1;
102const int kSelectFileCompleteIndex = 2;
103
104// Used for mapping between SavePackageType constants and the indexes above.
105const SavePackage::SavePackageType kIndexToSaveType[] = {
106  SavePackage::SAVE_TYPE_UNKNOWN,
107  SavePackage::SAVE_AS_ONLY_HTML,
108  SavePackage::SAVE_AS_COMPLETE_HTML,
109};
110
111// Used for mapping between the IDS_ string identifiers and the indexes above.
112const int kIndexToIDS[] = {
113  0, IDS_SAVE_PAGE_DESC_HTML_ONLY, IDS_SAVE_PAGE_DESC_COMPLETE,
114};
115
116int SavePackageTypeToIndex(SavePackage::SavePackageType type) {
117  for (size_t i = 0; i < arraysize(kIndexToSaveType); ++i) {
118    if (kIndexToSaveType[i] == type)
119      return i;
120  }
121  NOTREACHED();
122  return -1;
123}
124
125// Strip current ordinal number, if any. Should only be used on pure
126// file names, i.e. those stripped of their extensions.
127// TODO(estade): improve this to not choke on alternate encodings.
128FilePath::StringType StripOrdinalNumber(
129    const FilePath::StringType& pure_file_name) {
130  FilePath::StringType::size_type r_paren_index =
131      pure_file_name.rfind(FILE_PATH_LITERAL(')'));
132  FilePath::StringType::size_type l_paren_index =
133      pure_file_name.rfind(FILE_PATH_LITERAL('('));
134  if (l_paren_index >= r_paren_index)
135    return pure_file_name;
136
137  for (FilePath::StringType::size_type i = l_paren_index + 1;
138       i != r_paren_index; ++i) {
139    if (!IsAsciiDigit(pure_file_name[i]))
140      return pure_file_name;
141  }
142
143  return pure_file_name.substr(0, l_paren_index);
144}
145
146// Check whether we can save page as complete-HTML for the contents which
147// have specified a MIME type. Now only contents which have the MIME type
148// "text/html" can be saved as complete-HTML.
149bool CanSaveAsComplete(const std::string& contents_mime_type) {
150  return contents_mime_type == "text/html" ||
151         contents_mime_type == "application/xhtml+xml";
152}
153
154}  // namespace
155
156SavePackage::SavePackage(TabContents* tab_contents,
157                         SavePackageType save_type,
158                         const FilePath& file_full_path,
159                         const FilePath& directory_full_path)
160    : TabContentsObserver(tab_contents),
161      file_manager_(NULL),
162      download_(NULL),
163      page_url_(GetUrlToBeSaved()),
164      saved_main_file_path_(file_full_path),
165      saved_main_directory_path_(directory_full_path),
166      title_(tab_contents->GetTitle()),
167      finished_(false),
168      user_canceled_(false),
169      disk_error_occurred_(false),
170      save_type_(save_type),
171      all_save_items_count_(0),
172      wait_state_(INITIALIZE),
173      tab_id_(tab_contents->GetRenderProcessHost()->id()),
174      unique_id_(g_save_package_id++),
175      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
176  DCHECK(page_url_.is_valid());
177  DCHECK(save_type_ == SAVE_AS_ONLY_HTML ||
178         save_type_ == SAVE_AS_COMPLETE_HTML);
179  DCHECK(!saved_main_file_path_.empty() &&
180         saved_main_file_path_.value().length() <= kMaxFilePathLength);
181  DCHECK(!saved_main_directory_path_.empty() &&
182         saved_main_directory_path_.value().length() < kMaxFilePathLength);
183  InternalInit();
184}
185
186SavePackage::SavePackage(TabContents* tab_contents)
187    : TabContentsObserver(tab_contents),
188      file_manager_(NULL),
189      download_(NULL),
190      page_url_(GetUrlToBeSaved()),
191      title_(tab_contents->GetTitle()),
192      finished_(false),
193      user_canceled_(false),
194      disk_error_occurred_(false),
195      save_type_(SAVE_TYPE_UNKNOWN),
196      all_save_items_count_(0),
197      wait_state_(INITIALIZE),
198      tab_id_(tab_contents->GetRenderProcessHost()->id()),
199      unique_id_(g_save_package_id++),
200      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
201  DCHECK(page_url_.is_valid());
202  InternalInit();
203}
204
205// This is for testing use. Set |finished_| as true because we don't want
206// method Cancel to be be called in destructor in test mode.
207// We also don't call InternalInit().
208SavePackage::SavePackage(TabContents* tab_contents,
209                         const FilePath& file_full_path,
210                         const FilePath& directory_full_path)
211    : TabContentsObserver(tab_contents),
212      file_manager_(NULL),
213      download_(NULL),
214      saved_main_file_path_(file_full_path),
215      saved_main_directory_path_(directory_full_path),
216      finished_(true),
217      user_canceled_(false),
218      disk_error_occurred_(false),
219      save_type_(SAVE_TYPE_UNKNOWN),
220      all_save_items_count_(0),
221      wait_state_(INITIALIZE),
222      tab_id_(0),
223      unique_id_(g_save_package_id++),
224      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
225}
226
227SavePackage::~SavePackage() {
228  // Stop receiving saving job's updates
229  if (!finished_ && !canceled()) {
230    // Unexpected quit.
231    Cancel(true);
232  }
233
234  DCHECK(all_save_items_count_ == (waiting_item_queue_.size() +
235                                   completed_count() +
236                                   in_process_count()));
237  // Free all SaveItems.
238  while (!waiting_item_queue_.empty()) {
239    // We still have some items which are waiting for start to save.
240    SaveItem* save_item = waiting_item_queue_.front();
241    waiting_item_queue_.pop();
242    delete save_item;
243  }
244
245  STLDeleteValues(&saved_success_items_);
246  STLDeleteValues(&in_progress_items_);
247  STLDeleteValues(&saved_failed_items_);
248
249  // The DownloadItem is owned by DownloadManager.
250  download_ = NULL;
251
252  file_manager_ = NULL;
253
254  // If there's an outstanding save dialog, make sure it doesn't call us back
255  // now that we're gone.
256  if (select_file_dialog_.get())
257    select_file_dialog_->ListenerDestroyed();
258}
259
260// Retrieves the URL to be saved from tab_contents_ variable.
261GURL SavePackage::GetUrlToBeSaved() {
262  // Instead of using tab_contents_.GetURL here, we use url()
263  // (which is the "real" url of the page)
264  // from the NavigationEntry because it reflects its' origin
265  // rather than the displayed one (returned by GetURL) which may be
266  // different (like having "view-source:" on the front).
267  NavigationEntry* active_entry =
268      tab_contents()->controller().GetActiveEntry();
269  return active_entry->url();
270}
271
272// Cancel all in progress request, might be called by user or internal error.
273void SavePackage::Cancel(bool user_action) {
274  if (!canceled()) {
275    if (user_action)
276      user_canceled_ = true;
277    else
278      disk_error_occurred_ = true;
279    Stop();
280  }
281}
282
283// Init() can be called directly, or indirectly via GetSaveInfo(). In both
284// cases, we need file_manager_ to be initialized, so we do this first.
285void SavePackage::InternalInit() {
286  ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host();
287  if (!rdh) {
288    NOTREACHED();
289    return;
290  }
291
292  file_manager_ = rdh->save_file_manager();
293  if (!file_manager_) {
294    NOTREACHED();
295    return;
296  }
297}
298
299// Initialize the SavePackage.
300bool SavePackage::Init() {
301  // Set proper running state.
302  if (wait_state_ != INITIALIZE)
303    return false;
304
305  wait_state_ = START_PROCESS;
306
307  // Initialize the request context and resource dispatcher.
308  Profile* profile = tab_contents()->profile();
309  if (!profile) {
310    NOTREACHED();
311    return false;
312  }
313
314  request_context_getter_ = profile->GetRequestContext();
315
316  // Create the fake DownloadItem and display the view.
317  DownloadManager* download_manager =
318      tab_contents()->profile()->GetDownloadManager();
319  download_ = new DownloadItem(download_manager,
320                               saved_main_file_path_,
321                               page_url_,
322                               profile->IsOffTheRecord());
323
324  // Transfer the ownership to the download manager. We need the DownloadItem
325  // to be alive as long as the Profile is alive.
326  download_manager->SavePageAsDownloadStarted(download_);
327
328  tab_contents()->OnStartDownload(download_);
329
330  // Check save type and process the save page job.
331  if (save_type_ == SAVE_AS_COMPLETE_HTML) {
332    // Get directory
333    DCHECK(!saved_main_directory_path_.empty());
334    GetAllSavableResourceLinksForCurrentPage();
335  } else {
336    wait_state_ = NET_FILES;
337    SaveFileCreateInfo::SaveFileSource save_source = page_url_.SchemeIsFile() ?
338        SaveFileCreateInfo::SAVE_FILE_FROM_FILE :
339        SaveFileCreateInfo::SAVE_FILE_FROM_NET;
340    SaveItem* save_item = new SaveItem(page_url_,
341                                       GURL(),
342                                       this,
343                                       save_source);
344    // Add this item to waiting list.
345    waiting_item_queue_.push(save_item);
346    all_save_items_count_ = 1;
347    download_->set_total_bytes(1);
348
349    DoSavingProcess();
350  }
351
352  return true;
353}
354
355// On POSIX, the length of |pure_file_name| + |file_name_ext| is further
356// restricted by NAME_MAX. The maximum allowed path looks like:
357// '/path/to/save_dir' + '/' + NAME_MAX.
358uint32 SavePackage::GetMaxPathLengthForDirectory(const FilePath& base_dir) {
359#if defined(OS_POSIX)
360  return std::min(kMaxFilePathLength,
361                  static_cast<uint32>(base_dir.value().length()) +
362                  NAME_MAX + 1);
363#else
364  return kMaxFilePathLength;
365#endif
366}
367
368// File name is considered being consist of pure file name, dot and file
369// extension name. File name might has no dot and file extension, or has
370// multiple dot inside file name. The dot, which separates the pure file
371// name and file extension name, is last dot in the whole file name.
372// This function is for making sure the length of specified file path is not
373// great than the specified maximum length of file path and getting safe pure
374// file name part if the input pure file name is too long.
375// The parameter |dir_path| specifies directory part of the specified
376// file path. The parameter |file_name_ext| specifies file extension
377// name part of the specified file path (including start dot). The parameter
378// |max_file_path_len| specifies maximum length of the specified file path.
379// The parameter |pure_file_name| input pure file name part of the specified
380// file path. If the length of specified file path is great than
381// |max_file_path_len|, the |pure_file_name| will output new pure file name
382// part for making sure the length of specified file path is less than
383// specified maximum length of file path. Return false if the function can
384// not get a safe pure file name, otherwise it returns true.
385bool SavePackage::GetSafePureFileName(const FilePath& dir_path,
386                                      const FilePath::StringType& file_name_ext,
387                                      uint32 max_file_path_len,
388                                      FilePath::StringType* pure_file_name) {
389  DCHECK(!pure_file_name->empty());
390  int available_length = static_cast<int>(max_file_path_len -
391                                          dir_path.value().length() -
392                                          file_name_ext.length());
393  // Need an extra space for the separator.
394  if (!file_util::EndsWithSeparator(dir_path))
395    --available_length;
396
397  // Plenty of room.
398  if (static_cast<int>(pure_file_name->length()) <= available_length)
399    return true;
400
401  // Limited room. Truncate |pure_file_name| to fit.
402  if (available_length > 0) {
403    *pure_file_name = pure_file_name->substr(0, available_length);
404    return true;
405  }
406
407  // Not enough room to even use a shortened |pure_file_name|.
408  pure_file_name->clear();
409  return false;
410}
411
412// Generate name for saving resource.
413bool SavePackage::GenerateFileName(const std::string& disposition,
414                                   const GURL& url,
415                                   bool need_html_ext,
416                                   FilePath::StringType* generated_name) {
417  // TODO(jungshik): Figure out the referrer charset when having one
418  // makes sense and pass it to GetSuggestedFilename.
419  string16 suggested_name =
420      net::GetSuggestedFilename(url, disposition, "",
421                                ASCIIToUTF16(kDefaultSaveName));
422
423  // TODO(evan): this code is totally wrong -- we should just generate
424  // Unicode filenames and do all this encoding switching at the end.
425  // However, I'm just shuffling wrong code around, at least not adding
426  // to it.
427#if defined(OS_WIN)
428  FilePath file_path = FilePath(suggested_name);
429#else
430  FilePath file_path = FilePath(
431      base::SysWideToNativeMB(UTF16ToWide(suggested_name)));
432#endif
433
434  DCHECK(!file_path.empty());
435  FilePath::StringType pure_file_name =
436      file_path.RemoveExtension().BaseName().value();
437  FilePath::StringType file_name_ext = file_path.Extension();
438
439  // If it is HTML resource, use ".htm{l,}" as its extension.
440  if (need_html_ext) {
441    file_name_ext = FILE_PATH_LITERAL(".");
442    file_name_ext.append(kDefaultHtmlExtension);
443  }
444
445  // Need to make sure the suggested file name is not too long.
446  uint32 max_path = GetMaxPathLengthForDirectory(saved_main_directory_path_);
447
448  // Get safe pure file name.
449  if (!GetSafePureFileName(saved_main_directory_path_, file_name_ext,
450                           max_path, &pure_file_name))
451    return false;
452
453  FilePath::StringType file_name = pure_file_name + file_name_ext;
454
455  // Check whether we already have same name.
456  if (file_name_set_.find(file_name) == file_name_set_.end()) {
457    file_name_set_.insert(file_name);
458  } else {
459    // Found same name, increase the ordinal number for the file name.
460    FilePath::StringType base_file_name = StripOrdinalNumber(pure_file_name);
461
462    // We need to make sure the length of base file name plus maximum ordinal
463    // number path will be less than or equal to kMaxFilePathLength.
464    if (!GetSafePureFileName(saved_main_directory_path_, file_name_ext,
465        max_path - kMaxFileOrdinalNumberPartLength, &base_file_name))
466      return false;
467
468    // Prepare the new ordinal number.
469    uint32 ordinal_number;
470    FileNameCountMap::iterator it = file_name_count_map_.find(base_file_name);
471    if (it == file_name_count_map_.end()) {
472      // First base-name-conflict resolving, use 1 as initial ordinal number.
473      file_name_count_map_[base_file_name] = 1;
474      ordinal_number = 1;
475    } else {
476      // We have met same base-name conflict, use latest ordinal number.
477      ordinal_number = it->second;
478    }
479
480    if (ordinal_number > (kMaxFileOrdinalNumber - 1)) {
481      // Use a random file from temporary file.
482      FilePath temp_file;
483      file_util::CreateTemporaryFile(&temp_file);
484      file_name = temp_file.RemoveExtension().BaseName().value();
485      // Get safe pure file name.
486      if (!GetSafePureFileName(saved_main_directory_path_,
487                               FilePath::StringType(),
488                               max_path, &file_name))
489        return false;
490    } else {
491      for (int i = ordinal_number; i < kMaxFileOrdinalNumber; ++i) {
492        FilePath::StringType new_name = base_file_name +
493            StringPrintf(FILE_PATH_LITERAL("(%d)"), i) + file_name_ext;
494        if (file_name_set_.find(new_name) == file_name_set_.end()) {
495          // Resolved name conflict.
496          file_name = new_name;
497          file_name_count_map_[base_file_name] = ++i;
498          break;
499        }
500      }
501    }
502
503    file_name_set_.insert(file_name);
504  }
505
506  DCHECK(!file_name.empty());
507  generated_name->assign(file_name);
508
509  return true;
510}
511
512// We have received a message from SaveFileManager about a new saving job. We
513// create a SaveItem and store it in our in_progress list.
514void SavePackage::StartSave(const SaveFileCreateInfo* info) {
515  DCHECK(info && !info->url.is_empty());
516
517  SaveUrlItemMap::iterator it = in_progress_items_.find(info->url.spec());
518  if (it == in_progress_items_.end()) {
519    // If not found, we must have cancel action.
520    DCHECK(canceled());
521    return;
522  }
523  SaveItem* save_item = it->second;
524
525  DCHECK(!saved_main_file_path_.empty());
526
527  save_item->SetSaveId(info->save_id);
528  save_item->SetTotalBytes(info->total_bytes);
529
530  // Determine the proper path for a saving job, by choosing either the default
531  // save directory, or prompting the user.
532  DCHECK(!save_item->has_final_name());
533  if (info->url != page_url_) {
534    FilePath::StringType generated_name;
535    // For HTML resource file, make sure it will have .htm as extension name,
536    // otherwise, when you open the saved page in Chrome again, download
537    // file manager will treat it as downloadable resource, and download it
538    // instead of opening it as HTML.
539    bool need_html_ext =
540        info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_DOM;
541    if (!GenerateFileName(info->content_disposition,
542                          GURL(info->url),
543                          need_html_ext,
544                          &generated_name)) {
545      // We can not generate file name for this SaveItem, so we cancel the
546      // saving page job if the save source is from serialized DOM data.
547      // Otherwise, it means this SaveItem is sub-resource type, we treat it
548      // as an error happened on saving. We can ignore this type error for
549      // sub-resource links which will be resolved as absolute links instead
550      // of local links in final saved contents.
551      if (info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_DOM)
552        Cancel(true);
553      else
554        SaveFinished(save_item->save_id(), 0, false);
555      return;
556    }
557
558    // When saving page as only-HTML, we only have a SaveItem whose url
559    // must be page_url_.
560    DCHECK(save_type_ == SAVE_AS_COMPLETE_HTML);
561    DCHECK(!saved_main_directory_path_.empty());
562
563    // Now we get final name retrieved from GenerateFileName, we will use it
564    // rename the SaveItem.
565    FilePath final_name = saved_main_directory_path_.Append(generated_name);
566    save_item->Rename(final_name);
567  } else {
568    // It is the main HTML file, use the name chosen by the user.
569    save_item->Rename(saved_main_file_path_);
570  }
571
572  // If the save source is from file system, inform SaveFileManager to copy
573  // corresponding file to the file path which this SaveItem specifies.
574  if (info->save_source == SaveFileCreateInfo::SAVE_FILE_FROM_FILE) {
575    BrowserThread::PostTask(
576        BrowserThread::FILE, FROM_HERE,
577        NewRunnableMethod(file_manager_,
578                          &SaveFileManager::SaveLocalFile,
579                          save_item->url(),
580                          save_item->save_id(),
581                          tab_id()));
582    return;
583  }
584
585  // Check whether we begin to require serialized HTML data.
586  if (save_type_ == SAVE_AS_COMPLETE_HTML && wait_state_ == HTML_DATA) {
587    // Inform backend to serialize the all frames' DOM and send serialized
588    // HTML data back.
589    GetSerializedHtmlDataForCurrentPageWithLocalLinks();
590  }
591}
592
593// Look up SaveItem by save id from in progress map.
594SaveItem* SavePackage::LookupItemInProcessBySaveId(int32 save_id) {
595  if (in_process_count()) {
596    for (SaveUrlItemMap::iterator it = in_progress_items_.begin();
597        it != in_progress_items_.end(); ++it) {
598      SaveItem* save_item = it->second;
599      DCHECK(save_item->state() == SaveItem::IN_PROGRESS);
600      if (save_item->save_id() == save_id)
601        return save_item;
602    }
603  }
604  return NULL;
605}
606
607// Remove SaveItem from in progress map and put it to saved map.
608void SavePackage::PutInProgressItemToSavedMap(SaveItem* save_item) {
609  SaveUrlItemMap::iterator it = in_progress_items_.find(
610      save_item->url().spec());
611  DCHECK(it != in_progress_items_.end());
612  DCHECK(save_item == it->second);
613  in_progress_items_.erase(it);
614
615  if (save_item->success()) {
616    // Add it to saved_success_items_.
617    DCHECK(saved_success_items_.find(save_item->save_id()) ==
618           saved_success_items_.end());
619    saved_success_items_[save_item->save_id()] = save_item;
620  } else {
621    // Add it to saved_failed_items_.
622    DCHECK(saved_failed_items_.find(save_item->url().spec()) ==
623           saved_failed_items_.end());
624    saved_failed_items_[save_item->url().spec()] = save_item;
625  }
626}
627
628// Called for updating saving state.
629bool SavePackage::UpdateSaveProgress(int32 save_id,
630                                     int64 size,
631                                     bool write_success) {
632  // Because we might have canceled this saving job before,
633  // so we might not find corresponding SaveItem.
634  SaveItem* save_item = LookupItemInProcessBySaveId(save_id);
635  if (!save_item)
636    return false;
637
638  save_item->Update(size);
639
640  // If we got disk error, cancel whole save page job.
641  if (!write_success) {
642    // Cancel job with reason of disk error.
643    Cancel(false);
644  }
645  return true;
646}
647
648// Stop all page saving jobs that are in progress and instruct the file thread
649// to delete all saved  files.
650void SavePackage::Stop() {
651  // If we haven't moved out of the initial state, there's nothing to cancel and
652  // there won't be valid pointers for file_manager_ or download_.
653  if (wait_state_ == INITIALIZE)
654    return;
655
656  // When stopping, if it still has some items in in_progress, cancel them.
657  DCHECK(canceled());
658  if (in_process_count()) {
659    SaveUrlItemMap::iterator it = in_progress_items_.begin();
660    for (; it != in_progress_items_.end(); ++it) {
661      SaveItem* save_item = it->second;
662      DCHECK(save_item->state() == SaveItem::IN_PROGRESS);
663      save_item->Cancel();
664    }
665    // Remove all in progress item to saved map. For failed items, they will
666    // be put into saved_failed_items_, for successful item, they will be put
667    // into saved_success_items_.
668    while (in_process_count())
669      PutInProgressItemToSavedMap(in_progress_items_.begin()->second);
670  }
671
672  // This vector contains the save ids of the save files which SaveFileManager
673  // needs to remove from its save_file_map_.
674  SaveIDList save_ids;
675  for (SavedItemMap::iterator it = saved_success_items_.begin();
676      it != saved_success_items_.end(); ++it)
677    save_ids.push_back(it->first);
678  for (SaveUrlItemMap::iterator it = saved_failed_items_.begin();
679      it != saved_failed_items_.end(); ++it)
680    save_ids.push_back(it->second->save_id());
681
682  BrowserThread::PostTask(
683      BrowserThread::FILE, FROM_HERE,
684      NewRunnableMethod(file_manager_,
685                        &SaveFileManager::RemoveSavedFileFromFileMap,
686                        save_ids));
687
688  finished_ = true;
689  wait_state_ = FAILED;
690
691  // Inform the DownloadItem we have canceled whole save page job.
692  download_->Cancel(false);
693}
694
695void SavePackage::CheckFinish() {
696  if (in_process_count() || finished_)
697    return;
698
699  FilePath dir = (save_type_ == SAVE_AS_COMPLETE_HTML &&
700                  saved_success_items_.size() > 1) ?
701                  saved_main_directory_path_ : FilePath();
702
703  // This vector contains the final names of all the successfully saved files
704  // along with their save ids. It will be passed to SaveFileManager to do the
705  // renaming job.
706  FinalNameList final_names;
707  for (SavedItemMap::iterator it = saved_success_items_.begin();
708      it != saved_success_items_.end(); ++it)
709    final_names.push_back(std::make_pair(it->first,
710                                         it->second->full_path()));
711
712  BrowserThread::PostTask(
713      BrowserThread::FILE, FROM_HERE,
714      NewRunnableMethod(file_manager_,
715                        &SaveFileManager::RenameAllFiles,
716                        final_names,
717                        dir,
718                        tab_contents()->GetRenderProcessHost()->id(),
719                        tab_contents()->render_view_host()->routing_id(),
720                        id()));
721}
722
723// Successfully finished all items of this SavePackage.
724void SavePackage::Finish() {
725  // User may cancel the job when we're moving files to the final directory.
726  if (canceled())
727    return;
728
729  wait_state_ = SUCCESSFUL;
730  finished_ = true;
731
732  // This vector contains the save ids of the save files which SaveFileManager
733  // needs to remove from its save_file_map_.
734  SaveIDList save_ids;
735  for (SaveUrlItemMap::iterator it = saved_failed_items_.begin();
736       it != saved_failed_items_.end(); ++it)
737    save_ids.push_back(it->second->save_id());
738
739  BrowserThread::PostTask(
740      BrowserThread::FILE, FROM_HERE,
741      NewRunnableMethod(file_manager_,
742                        &SaveFileManager::RemoveSavedFileFromFileMap,
743                        save_ids));
744
745  download_->OnAllDataSaved(all_save_items_count_);
746  download_->MarkAsComplete();
747
748  NotificationService::current()->Notify(
749      NotificationType::SAVE_PACKAGE_SUCCESSFULLY_FINISHED,
750      Source<SavePackage>(this),
751      Details<GURL>(&page_url_));
752}
753
754// Called for updating end state.
755void SavePackage::SaveFinished(int32 save_id, int64 size, bool is_success) {
756  // Because we might have canceled this saving job before,
757  // so we might not find corresponding SaveItem. Just ignore it.
758  SaveItem* save_item = LookupItemInProcessBySaveId(save_id);
759  if (!save_item)
760    return;
761
762  // Let SaveItem set end state.
763  save_item->Finish(size, is_success);
764  // Remove the associated save id and SavePackage.
765  file_manager_->RemoveSaveFile(save_id, save_item->url(), this);
766
767  PutInProgressItemToSavedMap(save_item);
768
769  // Inform the DownloadItem to update UI.
770  // We use the received bytes as number of saved files.
771  download_->Update(completed_count());
772
773  if (save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM &&
774      save_item->url() == page_url_ && !save_item->received_bytes()) {
775    // If size of main HTML page is 0, treat it as disk error.
776    Cancel(false);
777    return;
778  }
779
780  if (canceled()) {
781    DCHECK(finished_);
782    return;
783  }
784
785  // Continue processing the save page job.
786  DoSavingProcess();
787
788  // Check whether we can successfully finish whole job.
789  CheckFinish();
790}
791
792// Sometimes, the net io will only call SaveFileManager::SaveFinished with
793// save id -1 when it encounters error. Since in this case, save id will be
794// -1, so we can only use URL to find which SaveItem is associated with
795// this error.
796// Saving an item failed. If it's a sub-resource, ignore it. If the error comes
797// from serializing HTML data, then cancel saving page.
798void SavePackage::SaveFailed(const GURL& save_url) {
799  SaveUrlItemMap::iterator it = in_progress_items_.find(save_url.spec());
800  if (it == in_progress_items_.end()) {
801    NOTREACHED();  // Should not exist!
802    return;
803  }
804  SaveItem* save_item = it->second;
805
806  save_item->Finish(0, false);
807
808  PutInProgressItemToSavedMap(save_item);
809
810  // Inform the DownloadItem to update UI.
811  // We use the received bytes as number of saved files.
812  download_->Update(completed_count());
813
814  if (save_type_ == SAVE_AS_ONLY_HTML ||
815      save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM) {
816    // We got error when saving page. Treat it as disk error.
817    Cancel(true);
818  }
819
820  if (canceled()) {
821    DCHECK(finished_);
822    return;
823  }
824
825  // Continue processing the save page job.
826  DoSavingProcess();
827
828  CheckFinish();
829}
830
831void SavePackage::SaveCanceled(SaveItem* save_item) {
832  // Call the RemoveSaveFile in UI thread.
833  file_manager_->RemoveSaveFile(save_item->save_id(),
834                                save_item->url(),
835                                this);
836  if (save_item->save_id() != -1)
837    BrowserThread::PostTask(
838        BrowserThread::FILE, FROM_HERE,
839        NewRunnableMethod(file_manager_,
840                          &SaveFileManager::CancelSave,
841                          save_item->save_id()));
842}
843
844// Initiate a saving job of a specific URL. We send the request to
845// SaveFileManager, which will dispatch it to different approach according to
846// the save source. Parameter process_all_remaining_items indicates whether
847// we need to save all remaining items.
848void SavePackage::SaveNextFile(bool process_all_remaining_items) {
849  DCHECK(tab_contents());
850  DCHECK(waiting_item_queue_.size());
851
852  do {
853    // Pop SaveItem from waiting list.
854    SaveItem* save_item = waiting_item_queue_.front();
855    waiting_item_queue_.pop();
856
857    // Add the item to in_progress_items_.
858    SaveUrlItemMap::iterator it = in_progress_items_.find(
859        save_item->url().spec());
860    DCHECK(it == in_progress_items_.end());
861    in_progress_items_[save_item->url().spec()] = save_item;
862    save_item->Start();
863    file_manager_->SaveURL(save_item->url(),
864                           save_item->referrer(),
865                           tab_contents()->GetRenderProcessHost()->id(),
866                           routing_id(),
867                           save_item->save_source(),
868                           save_item->full_path(),
869                           request_context_getter_.get(),
870                           this);
871  } while (process_all_remaining_items && waiting_item_queue_.size());
872}
873
874
875// Open download page in windows explorer on file thread, to avoid blocking the
876// user interface.
877void SavePackage::ShowDownloadInShell() {
878  DCHECK(file_manager_);
879  DCHECK(finished_ && !canceled() && !saved_main_file_path_.empty());
880  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
881#if defined(OS_MACOSX)
882  // Mac OS X requires opening downloads on the UI thread.
883  platform_util::ShowItemInFolder(saved_main_file_path_);
884#else
885  BrowserThread::PostTask(
886      BrowserThread::FILE, FROM_HERE,
887      NewRunnableMethod(file_manager_,
888                        &SaveFileManager::OnShowSavedFileInShell,
889                        saved_main_file_path_));
890#endif
891}
892
893// Calculate the percentage of whole save page job.
894int SavePackage::PercentComplete() {
895  if (!all_save_items_count_)
896    return 0;
897  else if (!in_process_count())
898    return 100;
899  else
900    return completed_count() / all_save_items_count_;
901}
902
903// Continue processing the save page job after one SaveItem has been
904// finished.
905void SavePackage::DoSavingProcess() {
906  if (save_type_ == SAVE_AS_COMPLETE_HTML) {
907    // We guarantee that images and JavaScripts must be downloaded first.
908    // So when finishing all those sub-resources, we will know which
909    // sub-resource's link can be replaced with local file path, which
910    // sub-resource's link need to be replaced with absolute URL which
911    // point to its internet address because it got error when saving its data.
912    SaveItem* save_item = NULL;
913    // Start a new SaveItem job if we still have job in waiting queue.
914    if (waiting_item_queue_.size()) {
915      DCHECK(wait_state_ == NET_FILES);
916      save_item = waiting_item_queue_.front();
917      if (save_item->save_source() != SaveFileCreateInfo::SAVE_FILE_FROM_DOM) {
918        SaveNextFile(false);
919      } else if (!in_process_count()) {
920        // If there is no in-process SaveItem, it means all sub-resources
921        // have been processed. Now we need to start serializing HTML DOM
922        // for the current page to get the generated HTML data.
923        wait_state_ = HTML_DATA;
924        // All non-HTML resources have been finished, start all remaining
925        // HTML files.
926        SaveNextFile(true);
927      }
928    } else if (in_process_count()) {
929      // Continue asking for HTML data.
930      DCHECK(wait_state_ == HTML_DATA);
931    }
932  } else {
933    // Save as HTML only.
934    DCHECK(wait_state_ == NET_FILES);
935    DCHECK(save_type_ == SAVE_AS_ONLY_HTML);
936    if (waiting_item_queue_.size()) {
937      DCHECK(all_save_items_count_ == waiting_item_queue_.size());
938      SaveNextFile(false);
939    }
940  }
941}
942
943bool SavePackage::OnMessageReceived(const IPC::Message& message) {
944  bool handled = true;
945  IPC_BEGIN_MESSAGE_MAP(SavePackage, message)
946    IPC_MESSAGE_HANDLER(ViewHostMsg_SendCurrentPageAllSavableResourceLinks,
947                        OnReceivedSavableResourceLinksForCurrentPage)
948    IPC_MESSAGE_HANDLER(ViewHostMsg_SendSerializedHtmlData,
949                        OnReceivedSerializedHtmlData)
950    IPC_MESSAGE_UNHANDLED(handled = false)
951  IPC_END_MESSAGE_MAP()
952  return handled;
953}
954
955// After finishing all SaveItems which need to get data from net.
956// We collect all URLs which have local storage and send the
957// map:(originalURL:currentLocalPath) to render process (backend).
958// Then render process will serialize DOM and send data to us.
959void SavePackage::GetSerializedHtmlDataForCurrentPageWithLocalLinks() {
960  if (wait_state_ != HTML_DATA)
961    return;
962  std::vector<GURL> saved_links;
963  std::vector<FilePath> saved_file_paths;
964  int successful_started_items_count = 0;
965
966  // Collect all saved items which have local storage.
967  // First collect the status of all the resource files and check whether they
968  // have created local files although they have not been completely saved.
969  // If yes, the file can be saved. Otherwise, there is a disk error, so we
970  // need to cancel the page saving job.
971  for (SaveUrlItemMap::iterator it = in_progress_items_.begin();
972       it != in_progress_items_.end(); ++it) {
973    DCHECK(it->second->save_source() ==
974           SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
975    if (it->second->has_final_name())
976      successful_started_items_count++;
977    saved_links.push_back(it->second->url());
978    saved_file_paths.push_back(it->second->file_name());
979  }
980
981  // If not all file of HTML resource have been started, then wait.
982  if (successful_started_items_count != in_process_count())
983    return;
984
985  // Collect all saved success items.
986  for (SavedItemMap::iterator it = saved_success_items_.begin();
987       it != saved_success_items_.end(); ++it) {
988    DCHECK(it->second->has_final_name());
989    saved_links.push_back(it->second->url());
990    saved_file_paths.push_back(it->second->file_name());
991  }
992
993  // Get the relative directory name.
994  FilePath relative_dir_name = saved_main_directory_path_.BaseName();
995
996  tab_contents()->render_view_host()->
997      GetSerializedHtmlDataForCurrentPageWithLocalLinks(
998      saved_links, saved_file_paths, relative_dir_name);
999}
1000
1001// Process the serialized HTML content data of a specified web page
1002// retrieved from render process.
1003void SavePackage::OnReceivedSerializedHtmlData(const GURL& frame_url,
1004                                               const std::string& data,
1005                                               int32 status) {
1006  WebPageSerializerClient::PageSerializationStatus flag =
1007      static_cast<WebPageSerializerClient::PageSerializationStatus>(status);
1008  // Check current state.
1009  if (wait_state_ != HTML_DATA)
1010    return;
1011
1012  int id = tab_id();
1013  // If the all frames are finished saving, we need to close the
1014  // remaining SaveItems.
1015  if (flag == WebPageSerializerClient::AllFramesAreFinished) {
1016    for (SaveUrlItemMap::iterator it = in_progress_items_.begin();
1017         it != in_progress_items_.end(); ++it) {
1018      VLOG(20) << " " << __FUNCTION__ << "()"
1019               << " save_id = " << it->second->save_id()
1020               << " url = \"" << it->second->url().spec() << "\"";
1021      BrowserThread::PostTask(
1022          BrowserThread::FILE, FROM_HERE,
1023          NewRunnableMethod(file_manager_,
1024                            &SaveFileManager::SaveFinished,
1025                            it->second->save_id(),
1026                            it->second->url(),
1027                            id,
1028                            true));
1029    }
1030    return;
1031  }
1032
1033  SaveUrlItemMap::iterator it = in_progress_items_.find(frame_url.spec());
1034  if (it == in_progress_items_.end())
1035    return;
1036  SaveItem* save_item = it->second;
1037  DCHECK(save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
1038
1039  if (!data.empty()) {
1040    // Prepare buffer for saving HTML data.
1041    scoped_refptr<net::IOBuffer> new_data(new net::IOBuffer(data.size()));
1042    memcpy(new_data->data(), data.data(), data.size());
1043
1044    // Call write file functionality in file thread.
1045    BrowserThread::PostTask(
1046        BrowserThread::FILE, FROM_HERE,
1047        NewRunnableMethod(file_manager_,
1048                          &SaveFileManager::UpdateSaveProgress,
1049                          save_item->save_id(),
1050                          new_data,
1051                          static_cast<int>(data.size())));
1052  }
1053
1054  // Current frame is completed saving, call finish in file thread.
1055  if (flag == WebPageSerializerClient::CurrentFrameIsFinished) {
1056    VLOG(20) << " " << __FUNCTION__ << "()"
1057             << " save_id = " << save_item->save_id()
1058             << " url = \"" << save_item->url().spec() << "\"";
1059    BrowserThread::PostTask(
1060        BrowserThread::FILE, FROM_HERE,
1061        NewRunnableMethod(file_manager_,
1062                          &SaveFileManager::SaveFinished,
1063                          save_item->save_id(),
1064                          save_item->url(),
1065                          id,
1066                          true));
1067  }
1068}
1069
1070// Ask for all savable resource links from backend, include main frame and
1071// sub-frame.
1072void SavePackage::GetAllSavableResourceLinksForCurrentPage() {
1073  if (wait_state_ != START_PROCESS)
1074    return;
1075
1076  wait_state_ = RESOURCES_LIST;
1077  tab_contents()->render_view_host()->
1078      GetAllSavableResourceLinksForCurrentPage(page_url_);
1079}
1080
1081// Give backend the lists which contain all resource links that have local
1082// storage, after which, render process will serialize DOM for generating
1083// HTML data.
1084void SavePackage::OnReceivedSavableResourceLinksForCurrentPage(
1085    const std::vector<GURL>& resources_list,
1086    const std::vector<GURL>& referrers_list,
1087    const std::vector<GURL>& frames_list) {
1088  if (wait_state_ != RESOURCES_LIST)
1089    return;
1090
1091  DCHECK(resources_list.size() == referrers_list.size());
1092  all_save_items_count_ = static_cast<int>(resources_list.size()) +
1093                           static_cast<int>(frames_list.size());
1094
1095  // We use total bytes as the total number of files we want to save.
1096  download_->set_total_bytes(all_save_items_count_);
1097
1098  if (all_save_items_count_) {
1099    // Put all sub-resources to wait list.
1100    for (int i = 0; i < static_cast<int>(resources_list.size()); ++i) {
1101      const GURL& u = resources_list[i];
1102      DCHECK(u.is_valid());
1103      SaveFileCreateInfo::SaveFileSource save_source = u.SchemeIsFile() ?
1104          SaveFileCreateInfo::SAVE_FILE_FROM_FILE :
1105          SaveFileCreateInfo::SAVE_FILE_FROM_NET;
1106      SaveItem* save_item = new SaveItem(u, referrers_list[i],
1107                                         this, save_source);
1108      waiting_item_queue_.push(save_item);
1109    }
1110    // Put all HTML resources to wait list.
1111    for (int i = 0; i < static_cast<int>(frames_list.size()); ++i) {
1112      const GURL& u = frames_list[i];
1113      DCHECK(u.is_valid());
1114      SaveItem* save_item = new SaveItem(u, GURL(),
1115          this, SaveFileCreateInfo::SAVE_FILE_FROM_DOM);
1116      waiting_item_queue_.push(save_item);
1117    }
1118    wait_state_ = NET_FILES;
1119    DoSavingProcess();
1120  } else {
1121    // No resource files need to be saved, treat it as user cancel.
1122    Cancel(true);
1123  }
1124}
1125
1126void SavePackage::SetShouldPromptUser(bool should_prompt) {
1127  g_should_prompt_for_filename = should_prompt;
1128}
1129
1130FilePath SavePackage::GetSuggestedNameForSaveAs(
1131    bool can_save_as_complete,
1132    const std::string& contents_mime_type) {
1133  FilePath name_with_proper_ext =
1134      FilePath::FromWStringHack(UTF16ToWideHack(title_));
1135
1136  // If the page's title matches its URL, use the URL. Try to use the last path
1137  // component or if there is none, the domain as the file name.
1138  // Normally we want to base the filename on the page title, or if it doesn't
1139  // exist, on the URL. It's not easy to tell if the page has no title, because
1140  // if the page has no title, TabContents::GetTitle() will return the page's
1141  // URL (adjusted for display purposes). Therefore, we convert the "title"
1142  // back to a URL, and if it matches the original page URL, we know the page
1143  // had no title (or had a title equal to its URL, which is fine to treat
1144  // similarly).
1145  GURL fixed_up_title_url =
1146      URLFixerUpper::FixupURL(UTF16ToUTF8(title_), std::string());
1147
1148  if (page_url_ == fixed_up_title_url) {
1149    std::string url_path;
1150    std::vector<std::string> url_parts;
1151    base::SplitString(page_url_.path(), '/', &url_parts);
1152    if (!url_parts.empty()) {
1153      for (int i = static_cast<int>(url_parts.size()) - 1; i >= 0; --i) {
1154        url_path = url_parts[i];
1155        if (!url_path.empty())
1156          break;
1157      }
1158    }
1159    if (url_path.empty())
1160      url_path = page_url_.host();
1161    name_with_proper_ext = FilePath::FromWStringHack(UTF8ToWide(url_path));
1162  }
1163
1164  // Ask user for getting final saving name.
1165  name_with_proper_ext = EnsureMimeExtension(name_with_proper_ext,
1166                                             contents_mime_type);
1167  // Adjust extension for complete types.
1168  if (can_save_as_complete)
1169    name_with_proper_ext = EnsureHtmlExtension(name_with_proper_ext);
1170
1171  FilePath::StringType file_name = name_with_proper_ext.value();
1172  file_util::ReplaceIllegalCharactersInPath(&file_name, ' ');
1173  return FilePath(file_name);
1174}
1175
1176FilePath SavePackage::EnsureHtmlExtension(const FilePath& name) {
1177  // If the file name doesn't have an extension suitable for HTML files,
1178  // append one.
1179  FilePath::StringType ext = name.Extension();
1180  if (!ext.empty())
1181    ext.erase(ext.begin());  // Erase preceding '.'.
1182  std::string mime_type;
1183  if (!net::GetMimeTypeFromExtension(ext, &mime_type) ||
1184      !CanSaveAsComplete(mime_type)) {
1185    return FilePath(name.value() + FILE_PATH_LITERAL(".") +
1186                    kDefaultHtmlExtension);
1187  }
1188  return name;
1189}
1190
1191FilePath SavePackage::EnsureMimeExtension(const FilePath& name,
1192    const std::string& contents_mime_type) {
1193  // Start extension at 1 to skip over period if non-empty.
1194  FilePath::StringType ext = name.Extension().length() ?
1195      name.Extension().substr(1) : name.Extension();
1196  FilePath::StringType suggested_extension =
1197      ExtensionForMimeType(contents_mime_type);
1198  std::string mime_type;
1199  if (!suggested_extension.empty() &&
1200      (!net::GetMimeTypeFromExtension(ext, &mime_type) ||
1201      !IsSavableContents(mime_type))) {
1202    // Extension is absent or needs to be updated.
1203    return FilePath(name.value() + FILE_PATH_LITERAL(".") +
1204                    suggested_extension);
1205  }
1206  return name;
1207}
1208
1209const FilePath::CharType* SavePackage::ExtensionForMimeType(
1210    const std::string& contents_mime_type) {
1211  static const struct {
1212    const FilePath::CharType *mime_type;
1213    const FilePath::CharType *suggested_extension;
1214  } extensions[] = {
1215    { FILE_PATH_LITERAL("text/html"), kDefaultHtmlExtension },
1216    { FILE_PATH_LITERAL("text/xml"), FILE_PATH_LITERAL("xml") },
1217    { FILE_PATH_LITERAL("application/xhtml+xml"), FILE_PATH_LITERAL("xhtml") },
1218    { FILE_PATH_LITERAL("text/plain"), FILE_PATH_LITERAL("txt") },
1219    { FILE_PATH_LITERAL("text/css"), FILE_PATH_LITERAL("css") },
1220  };
1221#if defined(OS_POSIX)
1222  FilePath::StringType mime_type(contents_mime_type);
1223#elif defined(OS_WIN)
1224  FilePath::StringType mime_type(UTF8ToWide(contents_mime_type));
1225#endif  // OS_WIN
1226  for (uint32 i = 0; i < ARRAYSIZE_UNSAFE(extensions); ++i) {
1227    if (mime_type == extensions[i].mime_type)
1228      return extensions[i].suggested_extension;
1229  }
1230  return FILE_PATH_LITERAL("");
1231}
1232
1233
1234
1235// static.
1236// Check whether the preference has the preferred directory for saving file. If
1237// not, initialize it with default directory.
1238FilePath SavePackage::GetSaveDirPreference(PrefService* prefs) {
1239  DCHECK(prefs);
1240
1241  if (!prefs->FindPreference(prefs::kSaveFileDefaultDirectory)) {
1242    DCHECK(prefs->FindPreference(prefs::kDownloadDefaultDirectory));
1243    FilePath default_save_path = prefs->GetFilePath(
1244        prefs::kDownloadDefaultDirectory);
1245    prefs->RegisterFilePathPref(prefs::kSaveFileDefaultDirectory,
1246                                default_save_path);
1247  }
1248
1249  // Get the directory from preference.
1250  FilePath save_file_path = prefs->GetFilePath(
1251      prefs::kSaveFileDefaultDirectory);
1252  DCHECK(!save_file_path.empty());
1253
1254  return save_file_path;
1255}
1256
1257void SavePackage::GetSaveInfo() {
1258  // Can't use tab_contents_ in the file thread, so get the data that we need
1259  // before calling to it.
1260  PrefService* prefs = tab_contents()->profile()->GetPrefs();
1261  FilePath website_save_dir = GetSaveDirPreference(prefs);
1262  FilePath download_save_dir = prefs->GetFilePath(
1263      prefs::kDownloadDefaultDirectory);
1264  std::string mime_type = tab_contents()->contents_mime_type();
1265
1266  BrowserThread::PostTask(
1267      BrowserThread::FILE, FROM_HERE,
1268      NewRunnableMethod(this, &SavePackage::CreateDirectoryOnFileThread,
1269          website_save_dir, download_save_dir, mime_type));
1270}
1271
1272void SavePackage::CreateDirectoryOnFileThread(
1273    const FilePath& website_save_dir,
1274    const FilePath& download_save_dir,
1275    const std::string& mime_type) {
1276  FilePath save_dir;
1277  // If the default html/websites save folder doesn't exist...
1278  if (!file_util::DirectoryExists(website_save_dir)) {
1279    // If the default download dir doesn't exist, create it.
1280    if (!file_util::DirectoryExists(download_save_dir))
1281      file_util::CreateDirectory(download_save_dir);
1282    save_dir = download_save_dir;
1283  } else {
1284    // If it does exist, use the default save dir param.
1285    save_dir = website_save_dir;
1286  }
1287
1288  bool can_save_as_complete = CanSaveAsComplete(mime_type);
1289  FilePath suggested_filename = GetSuggestedNameForSaveAs(can_save_as_complete,
1290                                                          mime_type);
1291  FilePath::StringType pure_file_name =
1292      suggested_filename.RemoveExtension().BaseName().value();
1293  FilePath::StringType file_name_ext = suggested_filename.Extension();
1294
1295  // Need to make sure the suggested file name is not too long.
1296  uint32 max_path = GetMaxPathLengthForDirectory(save_dir);
1297
1298  if (GetSafePureFileName(save_dir, file_name_ext, max_path, &pure_file_name)) {
1299    save_dir = save_dir.Append(pure_file_name + file_name_ext);
1300  } else {
1301    // Cannot create a shorter filename. This will cause the save as operation
1302    // to fail unless the user pick a shorter name. Continuing even though it
1303    // will fail because returning means no save as popup for the user, which
1304    // is even more confusing. This case should be rare though.
1305    save_dir = save_dir.Append(suggested_filename);
1306  }
1307
1308  BrowserThread::PostTask(
1309      BrowserThread::UI, FROM_HERE,
1310      NewRunnableMethod(this, &SavePackage::ContinueGetSaveInfo, save_dir,
1311                        can_save_as_complete));
1312}
1313
1314void SavePackage::ContinueGetSaveInfo(const FilePath& suggested_path,
1315                                      bool can_save_as_complete) {
1316  // The TabContents which owns this SavePackage may have disappeared during
1317  // the UI->FILE->UI thread hop of
1318  // GetSaveInfo->CreateDirectoryOnFileThread->ContinueGetSaveInfo.
1319  if (!tab_contents())
1320    return;
1321  DownloadPrefs* download_prefs =
1322      tab_contents()->profile()->GetDownloadManager()->download_prefs();
1323  int file_type_index =
1324      SavePackageTypeToIndex(
1325          static_cast<SavePackageType>(download_prefs->save_file_type()));
1326
1327  SelectFileDialog::FileTypeInfo file_type_info;
1328  FilePath::StringType default_extension;
1329
1330  // If the contents can not be saved as complete-HTML, do not show the
1331  // file filters.
1332  if (can_save_as_complete) {
1333    bool add_extra_extension = false;
1334    FilePath::StringType extra_extension;
1335    if (!suggested_path.Extension().empty() &&
1336        suggested_path.Extension().compare(FILE_PATH_LITERAL("htm")) &&
1337        suggested_path.Extension().compare(FILE_PATH_LITERAL("html"))) {
1338      add_extra_extension = true;
1339      extra_extension = suggested_path.Extension().substr(1);
1340    }
1341
1342    file_type_info.extensions.resize(2);
1343    file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1].push_back(
1344        FILE_PATH_LITERAL("htm"));
1345    file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1].push_back(
1346        FILE_PATH_LITERAL("html"));
1347
1348    if (add_extra_extension) {
1349      file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1].push_back(
1350          extra_extension);
1351    }
1352
1353    file_type_info.extension_description_overrides.push_back(
1354        l10n_util::GetStringUTF16(kIndexToIDS[kSelectFileCompleteIndex - 1]));
1355    file_type_info.extensions[kSelectFileCompleteIndex - 1].push_back(
1356        FILE_PATH_LITERAL("htm"));
1357    file_type_info.extensions[kSelectFileCompleteIndex - 1].push_back(
1358        FILE_PATH_LITERAL("html"));
1359
1360    if (add_extra_extension) {
1361      file_type_info.extensions[kSelectFileCompleteIndex - 1].push_back(
1362          extra_extension);
1363    }
1364
1365    file_type_info.extension_description_overrides.push_back(
1366        l10n_util::GetStringUTF16(kIndexToIDS[kSelectFileCompleteIndex]));
1367    file_type_info.include_all_files = false;
1368    default_extension = kDefaultHtmlExtension;
1369  } else {
1370    file_type_info.extensions.resize(1);
1371    file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1].push_back(
1372        suggested_path.Extension());
1373
1374    if (!file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1][0].empty()) {
1375      // Drop the .
1376      file_type_info.extensions[kSelectFileHtmlOnlyIndex - 1][0].erase(0, 1);
1377    }
1378
1379    file_type_info.include_all_files = true;
1380    file_type_index = 1;
1381  }
1382
1383  if (g_should_prompt_for_filename) {
1384    if (!select_file_dialog_.get())
1385      select_file_dialog_ = SelectFileDialog::Create(this);
1386    select_file_dialog_->SelectFile(SelectFileDialog::SELECT_SAVEAS_FILE,
1387                                    string16(),
1388                                    suggested_path,
1389                                    &file_type_info,
1390                                    file_type_index,
1391                                    default_extension,
1392                                    tab_contents(),
1393                                    platform_util::GetTopLevel(
1394                                        tab_contents()->GetNativeView()),
1395                                    NULL);
1396  } else {
1397    // Just use 'suggested_path' instead of opening the dialog prompt.
1398    ContinueSave(suggested_path, file_type_index);
1399  }
1400}
1401
1402// Called after the save file dialog box returns.
1403void SavePackage::ContinueSave(const FilePath& final_name,
1404                               int index) {
1405  // Ensure the filename is safe.
1406  saved_main_file_path_ = final_name;
1407  download_util::GenerateSafeFileName(tab_contents()->contents_mime_type(),
1408                                      &saved_main_file_path_);
1409
1410  // The option index is not zero-based.
1411  DCHECK(index >= kSelectFileHtmlOnlyIndex &&
1412         index <= kSelectFileCompleteIndex);
1413
1414  saved_main_directory_path_ = saved_main_file_path_.DirName();
1415
1416  PrefService* prefs = tab_contents()->profile()->GetPrefs();
1417  StringPrefMember save_file_path;
1418  save_file_path.Init(prefs::kSaveFileDefaultDirectory, prefs, NULL);
1419#if defined(OS_POSIX)
1420  std::string path_string = saved_main_directory_path_.value();
1421#elif defined(OS_WIN)
1422  std::string path_string = WideToUTF8(saved_main_directory_path_.value());
1423#endif
1424  // If user change the default saving directory, we will remember it just
1425  // like IE and FireFox.
1426  if (!tab_contents()->profile()->IsOffTheRecord() &&
1427      save_file_path.GetValue() != path_string) {
1428    save_file_path.SetValue(path_string);
1429  }
1430
1431  save_type_ = kIndexToSaveType[index];
1432
1433  prefs->SetInteger(prefs::kSaveFileType, save_type_);
1434
1435  if (save_type_ == SavePackage::SAVE_AS_COMPLETE_HTML) {
1436    // Make new directory for saving complete file.
1437    saved_main_directory_path_ = saved_main_directory_path_.Append(
1438        saved_main_file_path_.RemoveExtension().BaseName().value() +
1439        FILE_PATH_LITERAL("_files"));
1440  }
1441
1442  Init();
1443}
1444
1445// Static
1446bool SavePackage::IsSavableURL(const GURL& url) {
1447  for (int i = 0; chrome::kSavableSchemes[i] != NULL; ++i) {
1448    if (url.SchemeIs(chrome::kSavableSchemes[i])) {
1449      return true;
1450    }
1451  }
1452  return false;
1453}
1454
1455// Static
1456bool SavePackage::IsSavableContents(const std::string& contents_mime_type) {
1457  // WebKit creates Document object when MIME type is application/xhtml+xml,
1458  // so we also support this MIME type.
1459  return contents_mime_type == "text/html" ||
1460         contents_mime_type == "text/xml" ||
1461         contents_mime_type == "application/xhtml+xml" ||
1462         contents_mime_type == "text/plain" ||
1463         contents_mime_type == "text/css" ||
1464         net::IsSupportedJavascriptMimeType(contents_mime_type.c_str());
1465}
1466
1467// SelectFileDialog::Listener interface.
1468void SavePackage::FileSelected(const FilePath& path,
1469                               int index, void* params) {
1470  ContinueSave(path, index);
1471}
1472
1473void SavePackage::FileSelectionCanceled(void* params) {
1474}
1475