• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
58 using base::Time;
59 using WebKit::WebPageSerializerClient;
60 
61 namespace {
62 
63 // A counter for uniquely identifying each save package.
64 int g_save_package_id = 0;
65 
66 // Default name which will be used when we can not get proper name from
67 // resource URL.
68 const char kDefaultSaveName[] = "saved_resource";
69 
70 const 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.
79 const 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)
85 const uint32 kMaxFilePathLength = MAX_PATH - 1;
86 #elif defined(OS_POSIX)
87 const 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.
93 const 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.
97 bool 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.
101 const int kSelectFileHtmlOnlyIndex = 1;
102 const int kSelectFileCompleteIndex = 2;
103 
104 // Used for mapping between SavePackageType constants and the indexes above.
105 const 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.
112 const int kIndexToIDS[] = {
113   0, IDS_SAVE_PAGE_DESC_HTML_ONLY, IDS_SAVE_PAGE_DESC_COMPLETE,
114 };
115 
SavePackageTypeToIndex(SavePackage::SavePackageType type)116 int 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.
StripOrdinalNumber(const FilePath::StringType & pure_file_name)128 FilePath::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.
CanSaveAsComplete(const std::string & contents_mime_type)149 bool 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 
SavePackage(TabContents * tab_contents,SavePackageType save_type,const FilePath & file_full_path,const FilePath & directory_full_path)156 SavePackage::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 
SavePackage(TabContents * tab_contents)186 SavePackage::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().
SavePackage(TabContents * tab_contents,const FilePath & file_full_path,const FilePath & directory_full_path)208 SavePackage::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 
~SavePackage()227 SavePackage::~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.
GetUrlToBeSaved()261 GURL 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.
Cancel(bool user_action)273 void 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.
InternalInit()285 void 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.
Init()300 bool 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.
GetMaxPathLengthForDirectory(const FilePath & base_dir)358 uint32 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.
GetSafePureFileName(const FilePath & dir_path,const FilePath::StringType & file_name_ext,uint32 max_file_path_len,FilePath::StringType * pure_file_name)385 bool 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.
GenerateFileName(const std::string & disposition,const GURL & url,bool need_html_ext,FilePath::StringType * generated_name)413 bool 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.
StartSave(const SaveFileCreateInfo * info)514 void 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.
LookupItemInProcessBySaveId(int32 save_id)594 SaveItem* 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.
PutInProgressItemToSavedMap(SaveItem * save_item)608 void 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.
UpdateSaveProgress(int32 save_id,int64 size,bool write_success)629 bool 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.
Stop()650 void 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 
CheckFinish()695 void 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.
Finish()724 void 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.
SaveFinished(int32 save_id,int64 size,bool is_success)755 void 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.
SaveFailed(const GURL & save_url)798 void 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 
SaveCanceled(SaveItem * save_item)831 void 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.
SaveNextFile(bool process_all_remaining_items)848 void 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.
ShowDownloadInShell()877 void 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.
PercentComplete()894 int 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.
DoSavingProcess()905 void 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 
OnMessageReceived(const IPC::Message & message)943 bool 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.
GetSerializedHtmlDataForCurrentPageWithLocalLinks()959 void 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.
OnReceivedSerializedHtmlData(const GURL & frame_url,const std::string & data,int32 status)1003 void 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.
GetAllSavableResourceLinksForCurrentPage()1072 void 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.
OnReceivedSavableResourceLinksForCurrentPage(const std::vector<GURL> & resources_list,const std::vector<GURL> & referrers_list,const std::vector<GURL> & frames_list)1084 void 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 
SetShouldPromptUser(bool should_prompt)1126 void SavePackage::SetShouldPromptUser(bool should_prompt) {
1127   g_should_prompt_for_filename = should_prompt;
1128 }
1129 
GetSuggestedNameForSaveAs(bool can_save_as_complete,const std::string & contents_mime_type)1130 FilePath 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 
EnsureHtmlExtension(const FilePath & name)1176 FilePath 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 
EnsureMimeExtension(const FilePath & name,const std::string & contents_mime_type)1191 FilePath 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 
ExtensionForMimeType(const std::string & contents_mime_type)1209 const 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.
GetSaveDirPreference(PrefService * prefs)1238 FilePath 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 
GetSaveInfo()1257 void 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 
CreateDirectoryOnFileThread(const FilePath & website_save_dir,const FilePath & download_save_dir,const std::string & mime_type)1272 void 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 
ContinueGetSaveInfo(const FilePath & suggested_path,bool can_save_as_complete)1314 void 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.
ContinueSave(const FilePath & final_name,int index)1403 void 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
IsSavableURL(const GURL & url)1446 bool 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
IsSavableContents(const std::string & contents_mime_type)1456 bool 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.
FileSelected(const FilePath & path,int index,void * params)1468 void SavePackage::FileSelected(const FilePath& path,
1469                                int index, void* params) {
1470   ContinueSave(path, index);
1471 }
1472 
FileSelectionCanceled(void * params)1473 void SavePackage::FileSelectionCanceled(void* params) {
1474 }
1475