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