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 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. 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. 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 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 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(). 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 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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 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. 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. 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. 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 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. 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. 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. 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. 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 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. 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. 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. 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. 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 1126 void SavePackage::SetShouldPromptUser(bool should_prompt) { 1127 g_should_prompt_for_filename = should_prompt; 1128 } 1129 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 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 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 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. 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 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 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 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. 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 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 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. 1468 void SavePackage::FileSelected(const FilePath& path, 1469 int index, void* params) { 1470 ContinueSave(path, index); 1471 } 1472 1473 void SavePackage::FileSelectionCanceled(void* params) { 1474 } 1475