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