| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // File method ordering: Methods in this file are in the same order as |
| // in download_item_impl.h, with the following exception: The public |
| // interface Start is placed in chronological order with the other |
| // (private) routines that together define a DownloadItem's state |
| // transitions as the download progresses. See "Download progression |
| // cascade" later in this file. |
| |
| // A regular DownloadItem (created for a download in this session of |
| // the browser) normally goes through the following states: |
| // * Created (when download starts) |
| // * Destination filename determined |
| // * Entered into the history database. |
| // * Made visible in the download shelf. |
| // * All the data is saved. Note that the actual data download occurs |
| // in parallel with the above steps, but until those steps are |
| // complete, the state of the data save will be ignored. |
| // * Download file is renamed to its final name, and possibly |
| // auto-opened. |
| |
| #include "components/download/public/common/download_item_impl.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/format_macros.h" |
| #include "base/guid.h" |
| #include "base/json/string_escape.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/optional.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task_runner_util.h" |
| #include "build/build_config.h" |
| #include "components/download/database/in_progress/download_entry.h" |
| #include "components/download/internal/common/download_job_impl.h" |
| #include "components/download/internal/common/parallel_download_utils.h" |
| #include "components/download/public/common/download_danger_type.h" |
| #include "components/download/public/common/download_features.h" |
| #include "components/download/public/common/download_file.h" |
| #include "components/download/public/common/download_interrupt_reasons.h" |
| #include "components/download/public/common/download_item_impl_delegate.h" |
| #include "components/download/public/common/download_job_factory.h" |
| #include "components/download/public/common/download_stats.h" |
| #include "components/download/public/common/download_task_runner.h" |
| #include "components/download/public/common/download_ukm_helper.h" |
| #include "components/download/public/common/download_url_parameters.h" |
| #include "components/download/public/common/download_utils.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_status_code.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| |
| #if defined(OS_ANDROID) |
| #include "components/download/internal/common/android/download_collection_bridge.h" |
| #endif // defined(OS_ANDROID) |
| |
| namespace download { |
| |
| namespace { |
| |
| void DeleteDownloadedFileDone(base::WeakPtr<DownloadItemImpl> item, |
| const base::Callback<void(bool)>& callback, |
| bool success) { |
| if (success && item.get()) |
| item->OnDownloadedFileRemoved(); |
| callback.Run(success); |
| } |
| |
| // Wrapper around DownloadFile::Detach and DownloadFile::Cancel that |
| // takes ownership of the DownloadFile and hence implicitly destroys it |
| // at the end of the function. |
| base::FilePath DownloadFileDetach(std::unique_ptr<DownloadFile> download_file) { |
| DCHECK(GetDownloadTaskRunner()->RunsTasksInCurrentSequence()); |
| base::FilePath full_path = download_file->FullPath(); |
| download_file->Detach(); |
| return full_path; |
| } |
| |
| base::FilePath MakeCopyOfDownloadFile(DownloadFile* download_file) { |
| DCHECK(GetDownloadTaskRunner()->RunsTasksInCurrentSequence()); |
| base::FilePath temp_file_path; |
| if (!base::CreateTemporaryFile(&temp_file_path)) |
| return base::FilePath(); |
| |
| if (!base::CopyFile(download_file->FullPath(), temp_file_path)) { |
| DeleteDownloadedFile(temp_file_path); |
| return base::FilePath(); |
| } |
| |
| return temp_file_path; |
| } |
| |
| void DownloadFileCancel(std::unique_ptr<DownloadFile> download_file) { |
| DCHECK(GetDownloadTaskRunner()->RunsTasksInCurrentSequence()); |
| download_file->Cancel(); |
| } |
| |
| // Most of the cancellation pathways behave the same whether the cancellation |
| // was initiated by ther user (CANCELED) or initiated due to browser context |
| // shutdown (SHUTDOWN). |
| bool IsCancellation(DownloadInterruptReason reason) { |
| return reason == DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN || |
| reason == DOWNLOAD_INTERRUPT_REASON_USER_CANCELED; |
| } |
| |
| std::string GetDownloadCreationTypeNames( |
| DownloadItem::DownloadCreationType type) { |
| switch (type) { |
| case DownloadItem::TYPE_ACTIVE_DOWNLOAD: |
| return "NEW_DOWNLOAD"; |
| case DownloadItem::TYPE_HISTORY_IMPORT: |
| return "HISTORY_IMPORT"; |
| case DownloadItem::TYPE_SAVE_PAGE_AS: |
| return "SAVE_PAGE_AS"; |
| default: |
| NOTREACHED(); |
| return "INVALID_TYPE"; |
| } |
| } |
| |
| std::string GetDownloadDangerNames(DownloadDangerType type) { |
| switch (type) { |
| case DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: |
| return "NOT_DANGEROUS"; |
| case DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: |
| return "DANGEROUS_FILE"; |
| case DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: |
| return "DANGEROUS_URL"; |
| case DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: |
| return "DANGEROUS_CONTENT"; |
| case DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: |
| return "MAYBE_DANGEROUS_CONTENT"; |
| case DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: |
| return "UNCOMMON_CONTENT"; |
| case DOWNLOAD_DANGER_TYPE_USER_VALIDATED: |
| return "USER_VALIDATED"; |
| case DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: |
| return "DANGEROUS_HOST"; |
| case DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: |
| return "POTENTIALLY_UNWANTED"; |
| case DOWNLOAD_DANGER_TYPE_WHITELISTED_BY_POLICY: |
| return "WHITELISTED_BY_POLICY"; |
| default: |
| NOTREACHED(); |
| return "UNKNOWN_DANGER_TYPE"; |
| } |
| } |
| |
| class DownloadItemActivatedData |
| : public base::trace_event::ConvertableToTraceFormat { |
| public: |
| DownloadItemActivatedData(DownloadItem::DownloadCreationType download_type, |
| uint32_t download_id, |
| std::string original_url, |
| std::string final_url, |
| std::string file_name, |
| DownloadDangerType danger_type, |
| int64_t start_offset, |
| bool has_user_gesture) |
| : download_type_(download_type), |
| download_id_(download_id), |
| original_url_(original_url), |
| final_url_(final_url), |
| file_name_(file_name), |
| danger_type_(danger_type), |
| start_offset_(start_offset), |
| has_user_gesture_(has_user_gesture) {} |
| |
| ~DownloadItemActivatedData() override = default; |
| |
| void AppendAsTraceFormat(std::string* out) const override { |
| out->append("{"); |
| out->append(base::StringPrintf( |
| "\"type\":\"%s\",", |
| GetDownloadCreationTypeNames(download_type_).c_str())); |
| out->append(base::StringPrintf("\"id\":\"%d\",", download_id_)); |
| out->append("\"original_url\":"); |
| base::EscapeJSONString(original_url_, true, out); |
| out->append(","); |
| out->append("\"final_url\":"); |
| base::EscapeJSONString(final_url_, true, out); |
| out->append(","); |
| out->append("\"file_name\":"); |
| base::EscapeJSONString(file_name_, true, out); |
| out->append(","); |
| out->append( |
| base::StringPrintf("\"danger_type\":\"%s\",", |
| GetDownloadDangerNames(danger_type_).c_str())); |
| out->append( |
| base::StringPrintf("\"start_offset\":\"%" PRId64 "\",", start_offset_)); |
| out->append(base::StringPrintf("\"has_user_gesture\":\"%s\"", |
| has_user_gesture_ ? "true" : "false")); |
| out->append("}"); |
| } |
| |
| private: |
| DownloadItem::DownloadCreationType download_type_; |
| uint32_t download_id_; |
| std::string original_url_; |
| std::string final_url_; |
| std::string file_name_; |
| DownloadDangerType danger_type_; |
| int64_t start_offset_; |
| bool has_user_gesture_; |
| DISALLOW_COPY_AND_ASSIGN(DownloadItemActivatedData); |
| }; |
| |
| } // namespace |
| |
| // The maximum number of attempts we will make to resume automatically. |
| const int DownloadItemImpl::kMaxAutoResumeAttempts = 5; |
| |
| DownloadItemImpl::RequestInfo::RequestInfo( |
| const std::vector<GURL>& url_chain, |
| const GURL& referrer_url, |
| const GURL& site_url, |
| const GURL& tab_url, |
| const GURL& tab_referrer_url, |
| const base::Optional<url::Origin>& request_initiator, |
| const std::string& suggested_filename, |
| const base::FilePath& forced_file_path, |
| ui::PageTransition transition_type, |
| bool has_user_gesture, |
| const std::string& remote_address, |
| base::Time start_time) |
| : url_chain(url_chain), |
| referrer_url(referrer_url), |
| site_url(site_url), |
| tab_url(tab_url), |
| tab_referrer_url(tab_referrer_url), |
| request_initiator(request_initiator), |
| suggested_filename(suggested_filename), |
| forced_file_path(forced_file_path), |
| transition_type(transition_type), |
| has_user_gesture(has_user_gesture), |
| remote_address(remote_address), |
| start_time(start_time) {} |
| |
| DownloadItemImpl::RequestInfo::RequestInfo(const GURL& url) |
| : url_chain(std::vector<GURL>(1, url)), start_time(base::Time::Now()) {} |
| |
| DownloadItemImpl::RequestInfo::RequestInfo() = default; |
| |
| DownloadItemImpl::RequestInfo::RequestInfo( |
| const DownloadItemImpl::RequestInfo& other) = default; |
| |
| DownloadItemImpl::RequestInfo::~RequestInfo() = default; |
| |
| DownloadItemImpl::DestinationInfo::DestinationInfo( |
| const base::FilePath& target_path, |
| const base::FilePath& current_path, |
| int64_t received_bytes, |
| bool all_data_saved, |
| const std::string& hash, |
| base::Time end_time) |
| : target_path(target_path), |
| current_path(current_path), |
| received_bytes(received_bytes), |
| all_data_saved(all_data_saved), |
| hash(hash), |
| end_time(end_time) {} |
| |
| DownloadItemImpl::DestinationInfo::DestinationInfo( |
| TargetDisposition target_disposition) |
| : target_disposition(target_disposition) {} |
| |
| DownloadItemImpl::DestinationInfo::DestinationInfo() = default; |
| |
| DownloadItemImpl::DestinationInfo::DestinationInfo( |
| const DownloadItemImpl::DestinationInfo& other) = default; |
| |
| DownloadItemImpl::DestinationInfo::~DestinationInfo() = default; |
| |
| // Constructor for reading from the history service. |
| DownloadItemImpl::DownloadItemImpl( |
| DownloadItemImplDelegate* delegate, |
| const std::string& guid, |
| uint32_t download_id, |
| const base::FilePath& current_path, |
| const base::FilePath& target_path, |
| const std::vector<GURL>& url_chain, |
| const GURL& referrer_url, |
| const GURL& site_url, |
| const GURL& tab_url, |
| const GURL& tab_refererr_url, |
| const base::Optional<url::Origin>& request_initiator, |
| const std::string& mime_type, |
| const std::string& original_mime_type, |
| base::Time start_time, |
| base::Time end_time, |
| const std::string& etag, |
| const std::string& last_modified, |
| int64_t received_bytes, |
| int64_t total_bytes, |
| int32_t auto_resume_count, |
| const std::string& hash, |
| DownloadItem::DownloadState state, |
| DownloadDangerType danger_type, |
| DownloadInterruptReason interrupt_reason, |
| bool paused, |
| bool allow_metered, |
| bool opened, |
| base::Time last_access_time, |
| bool transient, |
| const std::vector<DownloadItem::ReceivedSlice>& received_slices) |
| : request_info_(url_chain, |
| referrer_url, |
| site_url, |
| tab_url, |
| tab_refererr_url, |
| request_initiator, |
| std::string(), |
| base::FilePath(), |
| ui::PAGE_TRANSITION_LINK, |
| false, |
| std::string(), |
| start_time), |
| guid_(guid), |
| download_id_(download_id), |
| mime_type_(mime_type), |
| original_mime_type_(original_mime_type), |
| total_bytes_(total_bytes), |
| last_reason_(interrupt_reason), |
| start_tick_(base::TimeTicks()), |
| state_(ExternalToInternalState(state)), |
| danger_type_(danger_type), |
| delegate_(delegate), |
| paused_(paused), |
| allow_metered_(allow_metered), |
| opened_(opened), |
| last_access_time_(last_access_time), |
| transient_(transient), |
| destination_info_(target_path, |
| current_path, |
| received_bytes, |
| state == COMPLETE, |
| hash, |
| end_time), |
| auto_resume_count_(auto_resume_count), |
| last_modified_time_(last_modified), |
| etag_(etag), |
| received_slices_(received_slices), |
| is_updating_observers_(false) { |
| delegate_->Attach(); |
| DCHECK(state_ == COMPLETE_INTERNAL || state_ == INTERRUPTED_INTERNAL || |
| state_ == CANCELLED_INTERNAL); |
| DCHECK(base::IsValidGUID(guid_)); |
| Init(false /* not actively downloading */, TYPE_HISTORY_IMPORT); |
| } |
| |
| // Constructing for a regular download: |
| DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate, |
| uint32_t download_id, |
| const DownloadCreateInfo& info) |
| : request_info_(info.url_chain, |
| info.referrer_url, |
| info.site_url, |
| info.tab_url, |
| info.tab_referrer_url, |
| info.request_initiator, |
| base::UTF16ToUTF8(info.save_info->suggested_name), |
| info.save_info->file_path, |
| info.transition_type ? info.transition_type.value() |
| : ui::PAGE_TRANSITION_LINK, |
| info.has_user_gesture, |
| info.remote_address, |
| info.start_time), |
| guid_(info.guid.empty() ? base::GenerateGUID() : info.guid), |
| download_id_(download_id), |
| response_headers_(info.response_headers), |
| content_disposition_(info.content_disposition), |
| mime_type_(info.mime_type), |
| original_mime_type_(info.original_mime_type), |
| total_bytes_(info.total_bytes), |
| last_reason_(info.result), |
| start_tick_(base::TimeTicks::Now()), |
| state_(INITIAL_INTERNAL), |
| delegate_(delegate), |
| is_temporary_(!info.transient && !info.save_info->file_path.empty()), |
| transient_(info.transient), |
| destination_info_(info.save_info->prompt_for_save_location |
| ? TARGET_DISPOSITION_PROMPT |
| : TARGET_DISPOSITION_OVERWRITE), |
| last_modified_time_(info.last_modified), |
| etag_(info.etag), |
| is_updating_observers_(false), |
| fetch_error_body_(info.fetch_error_body), |
| request_headers_(info.request_headers), |
| download_source_(info.download_source) { |
| delegate_->Attach(); |
| Init(true /* actively downloading */, TYPE_ACTIVE_DOWNLOAD); |
| allow_metered_ |= delegate_->IsActiveNetworkMetered(); |
| |
| TRACE_EVENT_INSTANT0("download", "DownloadStarted", TRACE_EVENT_SCOPE_THREAD); |
| } |
| |
| // Constructing for the "Save Page As..." feature: |
| DownloadItemImpl::DownloadItemImpl( |
| DownloadItemImplDelegate* delegate, |
| uint32_t download_id, |
| const base::FilePath& path, |
| const GURL& url, |
| const std::string& mime_type, |
| std::unique_ptr<DownloadRequestHandleInterface> request_handle) |
| : request_info_(url), |
| guid_(base::GenerateGUID()), |
| download_id_(download_id), |
| mime_type_(mime_type), |
| original_mime_type_(mime_type), |
| start_tick_(base::TimeTicks::Now()), |
| state_(IN_PROGRESS_INTERNAL), |
| delegate_(delegate), |
| destination_info_(path, path, 0, false, std::string(), base::Time()), |
| is_updating_observers_(false) { |
| job_ = DownloadJobFactory::CreateJob(this, std::move(request_handle), |
| DownloadCreateInfo(), true, nullptr, |
| nullptr, nullptr); |
| delegate_->Attach(); |
| Init(true /* actively downloading */, TYPE_SAVE_PAGE_AS); |
| } |
| |
| DownloadItemImpl::~DownloadItemImpl() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // Should always have been nuked before now, at worst in |
| // DownloadManager shutdown. |
| DCHECK(!download_file_); |
| CHECK(!is_updating_observers_); |
| |
| for (auto& observer : observers_) |
| observer.OnDownloadDestroyed(this); |
| delegate_->Detach(); |
| } |
| |
| void DownloadItemImpl::AddObserver(Observer* observer) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| observers_.AddObserver(observer); |
| } |
| |
| void DownloadItemImpl::RemoveObserver(Observer* observer) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| observers_.RemoveObserver(observer); |
| } |
| |
| void DownloadItemImpl::UpdateObservers() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DVLOG(20) << __func__ << "()"; |
| |
| // Nested updates should not be allowed. |
| DCHECK(!is_updating_observers_); |
| |
| is_updating_observers_ = true; |
| for (auto& observer : observers_) |
| observer.OnDownloadUpdated(this); |
| is_updating_observers_ = false; |
| } |
| |
| void DownloadItemImpl::ValidateDangerousDownload() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(!IsDone()); |
| DCHECK(IsDangerous()); |
| |
| DVLOG(20) << __func__ << "() download=" << DebugString(true); |
| |
| if (IsDone() || !IsDangerous()) |
| return; |
| |
| RecordDangerousDownloadAccept(GetDangerType(), GetTargetFilePath()); |
| |
| danger_type_ = DOWNLOAD_DANGER_TYPE_USER_VALIDATED; |
| |
| TRACE_EVENT_INSTANT1("download", "DownloadItemSaftyStateUpdated", |
| TRACE_EVENT_SCOPE_THREAD, "danger_type", |
| GetDownloadDangerNames(danger_type_).c_str()); |
| |
| UpdateObservers(); // TODO(asanka): This is potentially unsafe. The download |
| // may not be in a consistent state or around at all after |
| // invoking observers. http://crbug.com/586610 |
| |
| MaybeCompleteDownload(); |
| } |
| |
| void DownloadItemImpl::StealDangerousDownload( |
| bool delete_file_afterward, |
| const AcquireFileCallback& callback) { |
| DVLOG(20) << __func__ << "() download = " << DebugString(true); |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(IsDangerous()); |
| DCHECK(AllDataSaved()); |
| |
| if (delete_file_afterward) { |
| if (download_file_) { |
| base::PostTaskAndReplyWithResult( |
| GetDownloadTaskRunner().get(), FROM_HERE, |
| base::Bind(&DownloadFileDetach, base::Passed(&download_file_)), |
| callback); |
| } else { |
| callback.Run(GetFullPath()); |
| } |
| destination_info_.current_path.clear(); |
| Remove(); |
| // Download item has now been deleted. |
| } else if (download_file_) { |
| base::PostTaskAndReplyWithResult( |
| GetDownloadTaskRunner().get(), FROM_HERE, |
| base::Bind(&MakeCopyOfDownloadFile, download_file_.get()), callback); |
| } else { |
| callback.Run(GetFullPath()); |
| } |
| } |
| |
| void DownloadItemImpl::Pause() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // Ignore irrelevant states. |
| if (IsPaused()) |
| return; |
| |
| switch (state_) { |
| case CANCELLED_INTERNAL: |
| case COMPLETE_INTERNAL: |
| case COMPLETING_INTERNAL: |
| return; |
| case INITIAL_INTERNAL: |
| case INTERRUPTED_INTERNAL: |
| case INTERRUPTED_TARGET_PENDING_INTERNAL: |
| case RESUMING_INTERNAL: |
| // No active request. |
| paused_ = true; |
| UpdateObservers(); |
| return; |
| |
| case IN_PROGRESS_INTERNAL: |
| case TARGET_PENDING_INTERNAL: |
| paused_ = true; |
| job_->Pause(); |
| UpdateObservers(); |
| return; |
| |
| case MAX_DOWNLOAD_INTERNAL_STATE: |
| case TARGET_RESOLVED_INTERNAL: |
| NOTREACHED(); |
| } |
| } |
| |
| void DownloadItemImpl::Resume(bool user_resume) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DVLOG(20) << __func__ << "() download = " << DebugString(true); |
| switch (state_) { |
| case CANCELLED_INTERNAL: // Nothing to resume. |
| case COMPLETE_INTERNAL: |
| case COMPLETING_INTERNAL: |
| case INITIAL_INTERNAL: |
| case INTERRUPTED_TARGET_PENDING_INTERNAL: |
| case RESUMING_INTERNAL: // Resumption in progress. |
| return; |
| |
| case TARGET_PENDING_INTERNAL: |
| case IN_PROGRESS_INTERNAL: |
| if (!IsPaused()) |
| return; |
| paused_ = false; |
| if (job_) |
| job_->Resume(true); |
| |
| UpdateResumptionInfo(true); |
| UpdateObservers(); |
| return; |
| |
| case INTERRUPTED_INTERNAL: |
| UpdateResumptionInfo(paused_ || user_resume); |
| paused_ = false; |
| if (auto_resume_count_ >= kMaxAutoResumeAttempts) |
| return; |
| |
| ResumeInterruptedDownload(user_resume |
| ? ResumptionRequestSource::USER |
| : ResumptionRequestSource::AUTOMATIC); |
| UpdateObservers(); |
| return; |
| |
| case MAX_DOWNLOAD_INTERNAL_STATE: |
| case TARGET_RESOLVED_INTERNAL: |
| NOTREACHED(); |
| } |
| } |
| |
| void DownloadItemImpl::UpdateResumptionInfo(bool user_resume) { |
| if (user_resume) { |
| allow_metered_ |= delegate_->IsActiveNetworkMetered(); |
| bytes_wasted_ = 0; |
| } |
| |
| auto_resume_count_ = user_resume ? 0 : ++auto_resume_count_; |
| } |
| |
| void DownloadItemImpl::Cancel(bool user_cancel) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DVLOG(20) << __func__ << "() download = " << DebugString(true); |
| InterruptAndDiscardPartialState( |
| user_cancel ? DOWNLOAD_INTERRUPT_REASON_USER_CANCELED |
| : DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN); |
| UpdateObservers(); |
| } |
| |
| void DownloadItemImpl::Remove() { |
| DVLOG(20) << __func__ << "() download = " << DebugString(true); |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| RecordDownloadDeletion(GetEndTime(), GetMimeType()); |
| |
| InterruptAndDiscardPartialState(DOWNLOAD_INTERRUPT_REASON_USER_CANCELED); |
| UpdateObservers(); |
| NotifyRemoved(); |
| delegate_->DownloadRemoved(this); |
| // We have now been deleted. |
| } |
| |
| void DownloadItemImpl::OpenDownload() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (!IsDone()) { |
| // We don't honor the open_when_complete_ flag for temporary |
| // downloads. Don't set it because it shows up in the UI. |
| if (!IsTemporary()) |
| open_when_complete_ = !open_when_complete_; |
| return; |
| } |
| |
| if (state_ != COMPLETE_INTERNAL || file_externally_removed_) |
| return; |
| |
| // Ideally, we want to detect errors in opening and report them, but we |
| // don't generally have the proper interface for that to the external |
| // program that opens the file. So instead we spawn a check to update |
| // the UI if the file has been deleted in parallel with the open. |
| delegate_->CheckForFileRemoval(this); |
| RecordOpen(GetEndTime()); |
| opened_ = true; |
| last_access_time_ = base::Time::Now(); |
| for (auto& observer : observers_) |
| observer.OnDownloadOpened(this); |
| delegate_->OpenDownload(this); |
| } |
| |
| void DownloadItemImpl::ShowDownloadInShell() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // Ideally, we want to detect errors in showing and report them, but we |
| // don't generally have the proper interface for that to the external |
| // program that opens the file. So instead we spawn a check to update |
| // the UI if the file has been deleted in parallel with the show. |
| delegate_->CheckForFileRemoval(this); |
| delegate_->ShowDownloadInShell(this); |
| } |
| |
| void DownloadItemImpl::RenameDownloadedFileDone(RenameDownloadCallback callback, |
| const base::FilePath& new_path, |
| DownloadRenameResult result) { |
| if (result == DownloadRenameResult::SUCCESS) { |
| destination_info_.target_path = new_path; |
| destination_info_.current_path = new_path; |
| UpdateObservers(); |
| } |
| std::move(callback).Run(result); |
| } |
| |
| void DownloadItemImpl::Rename(const base::FilePath& name, |
| DownloadItem::RenameDownloadCallback callback) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (name.IsAbsolute()) { |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&DownloadItemImpl::RenameDownloadedFileDone, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(callback), GetFullPath(), |
| DownloadRenameResult::FAILURE_NAME_INVALID)); |
| return; |
| } |
| |
| auto full_path = base::FilePath(GetFullPath().DirName()).Append(name); |
| |
| base::PostTaskAndReplyWithResult( |
| GetDownloadTaskRunner().get(), FROM_HERE, |
| base::BindOnce(&download::RenameDownloadedFile, GetFullPath(), full_path), |
| base::BindOnce(&DownloadItemImpl::RenameDownloadedFileDone, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| full_path)); |
| } |
| |
| uint32_t DownloadItemImpl::GetId() const { |
| return download_id_; |
| } |
| |
| const std::string& DownloadItemImpl::GetGuid() const { |
| return guid_; |
| } |
| |
| DownloadItem::DownloadState DownloadItemImpl::GetState() const { |
| return InternalToExternalState(state_); |
| } |
| |
| DownloadInterruptReason DownloadItemImpl::GetLastReason() const { |
| return last_reason_; |
| } |
| |
| bool DownloadItemImpl::IsPaused() const { |
| return paused_; |
| } |
| |
| bool DownloadItemImpl::AllowMetered() const { |
| return allow_metered_; |
| } |
| |
| bool DownloadItemImpl::IsTemporary() const { |
| return is_temporary_; |
| } |
| |
| bool DownloadItemImpl::CanResume() const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| switch (state_) { |
| case INITIAL_INTERNAL: |
| case COMPLETING_INTERNAL: |
| case COMPLETE_INTERNAL: |
| case CANCELLED_INTERNAL: |
| case RESUMING_INTERNAL: |
| case INTERRUPTED_TARGET_PENDING_INTERNAL: |
| return false; |
| |
| case TARGET_PENDING_INTERNAL: |
| case TARGET_RESOLVED_INTERNAL: |
| case IN_PROGRESS_INTERNAL: |
| return IsPaused(); |
| |
| case INTERRUPTED_INTERNAL: { |
| ResumeMode resume_mode = GetResumeMode(); |
| // Only allow Resume() calls if the resumption mode requires a user |
| // action. |
| return resume_mode == ResumeMode::USER_RESTART || |
| resume_mode == ResumeMode::USER_CONTINUE; |
| } |
| |
| case MAX_DOWNLOAD_INTERNAL_STATE: |
| NOTREACHED(); |
| } |
| return false; |
| } |
| |
| bool DownloadItemImpl::IsDone() const { |
| return IsDownloadDone(GetURL(), GetState(), GetLastReason()); |
| } |
| |
| int64_t DownloadItemImpl::GetBytesWasted() const { |
| return bytes_wasted_; |
| } |
| |
| int32_t DownloadItemImpl::GetAutoResumeCount() const { |
| return auto_resume_count_; |
| } |
| |
| const GURL& DownloadItemImpl::GetURL() const { |
| return request_info_.url_chain.empty() ? GURL::EmptyGURL() |
| : request_info_.url_chain.back(); |
| } |
| |
| const std::vector<GURL>& DownloadItemImpl::GetUrlChain() const { |
| return request_info_.url_chain; |
| } |
| |
| const GURL& DownloadItemImpl::GetOriginalUrl() const { |
| // Be careful about taking the front() of possibly-empty vectors! |
| // http://crbug.com/190096 |
| return request_info_.url_chain.empty() ? GURL::EmptyGURL() |
| : request_info_.url_chain.front(); |
| } |
| |
| const GURL& DownloadItemImpl::GetReferrerUrl() const { |
| return request_info_.referrer_url; |
| } |
| |
| const GURL& DownloadItemImpl::GetSiteUrl() const { |
| return request_info_.site_url; |
| } |
| |
| const GURL& DownloadItemImpl::GetTabUrl() const { |
| return request_info_.tab_url; |
| } |
| |
| const GURL& DownloadItemImpl::GetTabReferrerUrl() const { |
| return request_info_.tab_referrer_url; |
| } |
| |
| const base::Optional<url::Origin>& DownloadItemImpl::GetRequestInitiator() |
| const { |
| return request_info_.request_initiator; |
| } |
| |
| std::string DownloadItemImpl::GetSuggestedFilename() const { |
| return request_info_.suggested_filename; |
| } |
| |
| const scoped_refptr<const net::HttpResponseHeaders>& |
| DownloadItemImpl::GetResponseHeaders() const { |
| return response_headers_; |
| } |
| |
| std::string DownloadItemImpl::GetContentDisposition() const { |
| return content_disposition_; |
| } |
| |
| std::string DownloadItemImpl::GetMimeType() const { |
| return mime_type_; |
| } |
| |
| std::string DownloadItemImpl::GetOriginalMimeType() const { |
| return original_mime_type_; |
| } |
| |
| std::string DownloadItemImpl::GetRemoteAddress() const { |
| return request_info_.remote_address; |
| } |
| |
| bool DownloadItemImpl::HasUserGesture() const { |
| return request_info_.has_user_gesture; |
| } |
| |
| ui::PageTransition DownloadItemImpl::GetTransitionType() const { |
| return request_info_.transition_type; |
| } |
| |
| const std::string& DownloadItemImpl::GetLastModifiedTime() const { |
| return last_modified_time_; |
| } |
| |
| const std::string& DownloadItemImpl::GetETag() const { |
| return etag_; |
| } |
| |
| bool DownloadItemImpl::IsSavePackageDownload() const { |
| return job_ && job_->IsSavePackageDownload(); |
| } |
| |
| const base::FilePath& DownloadItemImpl::GetFullPath() const { |
| return destination_info_.current_path; |
| } |
| |
| const base::FilePath& DownloadItemImpl::GetTargetFilePath() const { |
| return destination_info_.target_path; |
| } |
| |
| const base::FilePath& DownloadItemImpl::GetForcedFilePath() const { |
| // TODO(asanka): Get rid of GetForcedFilePath(). We should instead just |
| // require that clients respect GetTargetFilePath() if it is already set. |
| return request_info_.forced_file_path; |
| } |
| |
| base::FilePath DownloadItemImpl::GetTemporaryFilePath() const { |
| if (state_ == TARGET_PENDING_INTERNAL || INTERRUPTED_TARGET_PENDING_INTERNAL) |
| return download_file_ ? download_file_->FullPath() : base::FilePath(); |
| return base::FilePath(); |
| } |
| |
| base::FilePath DownloadItemImpl::GetFileNameToReportUser() const { |
| if (!display_name_.empty()) |
| return display_name_; |
| return GetTargetFilePath().BaseName(); |
| } |
| |
| DownloadItem::TargetDisposition DownloadItemImpl::GetTargetDisposition() const { |
| return destination_info_.target_disposition; |
| } |
| |
| const std::string& DownloadItemImpl::GetHash() const { |
| return destination_info_.hash; |
| } |
| |
| bool DownloadItemImpl::GetFileExternallyRemoved() const { |
| return file_externally_removed_; |
| } |
| |
| void DownloadItemImpl::DeleteFile(const base::Callback<void(bool)>& callback) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (GetState() != DownloadItem::COMPLETE) { |
| // Pass a null WeakPtr so it doesn't call OnDownloadedFileRemoved. |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DeleteDownloadedFileDone, |
| base::WeakPtr<DownloadItemImpl>(), callback, false)); |
| return; |
| } |
| if (GetFullPath().empty() || file_externally_removed_) { |
| // Pass a null WeakPtr so it doesn't call OnDownloadedFileRemoved. |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DeleteDownloadedFileDone, |
| base::WeakPtr<DownloadItemImpl>(), callback, true)); |
| return; |
| } |
| base::PostTaskAndReplyWithResult( |
| GetDownloadTaskRunner().get(), FROM_HERE, |
| base::Bind(&DeleteDownloadedFile, GetFullPath()), |
| base::Bind(&DeleteDownloadedFileDone, weak_ptr_factory_.GetWeakPtr(), |
| callback)); |
| } |
| |
| DownloadFile* DownloadItemImpl::GetDownloadFile() { |
| return download_file_.get(); |
| } |
| |
| bool DownloadItemImpl::IsDangerous() const { |
| return (danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_URL || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED); |
| } |
| |
| DownloadDangerType DownloadItemImpl::GetDangerType() const { |
| return danger_type_; |
| } |
| |
| bool DownloadItemImpl::TimeRemaining(base::TimeDelta* remaining) const { |
| if (total_bytes_ <= 0) |
| return false; // We never received the content_length for this download. |
| |
| int64_t speed = CurrentSpeed(); |
| if (speed == 0) |
| return false; |
| |
| *remaining = |
| base::TimeDelta::FromSeconds((total_bytes_ - GetReceivedBytes()) / speed); |
| return true; |
| } |
| |
| int64_t DownloadItemImpl::CurrentSpeed() const { |
| if (IsPaused()) |
| return 0; |
| return bytes_per_sec_; |
| } |
| |
| int DownloadItemImpl::PercentComplete() const { |
| // If the delegate is delaying completion of the download, then we have no |
| // idea how long it will take. |
| if (delegate_delayed_complete_ || total_bytes_ <= 0) |
| return -1; |
| |
| return static_cast<int>(GetReceivedBytes() * 100.0 / total_bytes_); |
| } |
| |
| bool DownloadItemImpl::AllDataSaved() const { |
| return destination_info_.all_data_saved; |
| } |
| |
| int64_t DownloadItemImpl::GetTotalBytes() const { |
| return total_bytes_; |
| } |
| |
| int64_t DownloadItemImpl::GetReceivedBytes() const { |
| return destination_info_.received_bytes; |
| } |
| |
| const std::vector<DownloadItem::ReceivedSlice>& |
| DownloadItemImpl::GetReceivedSlices() const { |
| return received_slices_; |
| } |
| |
| base::Time DownloadItemImpl::GetStartTime() const { |
| return request_info_.start_time; |
| } |
| |
| base::Time DownloadItemImpl::GetEndTime() const { |
| return destination_info_.end_time; |
| } |
| |
| bool DownloadItemImpl::CanShowInFolder() { |
| // A download can be shown in the folder if the downloaded file is in a known |
| // location. |
| return CanOpenDownload() && !GetFullPath().empty(); |
| } |
| |
| bool DownloadItemImpl::CanOpenDownload() { |
| // We can open the file or mark it for opening on completion if the download |
| // is expected to complete successfully. Exclude temporary downloads, since |
| // they aren't owned by the download system. |
| const bool is_complete = GetState() == DownloadItem::COMPLETE; |
| return (!IsDone() || is_complete) && !IsTemporary() && |
| !file_externally_removed_; |
| } |
| |
| bool DownloadItemImpl::ShouldOpenFileBasedOnExtension() { |
| return delegate_->ShouldOpenFileBasedOnExtension(GetTargetFilePath()); |
| } |
| |
| bool DownloadItemImpl::GetOpenWhenComplete() const { |
| return open_when_complete_; |
| } |
| |
| bool DownloadItemImpl::GetAutoOpened() { |
| return auto_opened_; |
| } |
| |
| bool DownloadItemImpl::GetOpened() const { |
| return opened_; |
| } |
| |
| base::Time DownloadItemImpl::GetLastAccessTime() const { |
| return last_access_time_; |
| } |
| |
| bool DownloadItemImpl::IsTransient() const { |
| return transient_; |
| } |
| |
| bool DownloadItemImpl::IsParallelDownload() const { |
| bool is_parallelizable = job_ ? job_->IsParallelizable() : false; |
| return is_parallelizable && download::IsParallelDownloadEnabled(); |
| } |
| |
| DownloadItem::DownloadCreationType DownloadItemImpl::GetDownloadCreationType() |
| const { |
| return download_type_; |
| } |
| |
| void DownloadItemImpl::OnContentCheckCompleted(DownloadDangerType danger_type, |
| DownloadInterruptReason reason) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(AllDataSaved()); |
| |
| // Danger type is only allowed to be set on an active download after all data |
| // has been saved. This excludes all other states. In particular, |
| // OnContentCheckCompleted() isn't allowed on an INTERRUPTED download since |
| // such an interruption would need to happen between OnAllDataSaved() and |
| // OnContentCheckCompleted() during which no disk or network activity |
| // should've taken place. |
| DCHECK_EQ(state_, IN_PROGRESS_INTERNAL); |
| DVLOG(20) << __func__ << "() danger_type=" << danger_type |
| << " download=" << DebugString(true); |
| SetDangerType(danger_type); |
| if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) { |
| InterruptAndDiscardPartialState(reason); |
| DCHECK_EQ(ResumeMode::INVALID, GetResumeMode()); |
| } |
| UpdateObservers(); |
| } |
| |
| void DownloadItemImpl::SetOpenWhenComplete(bool open) { |
| open_when_complete_ = open; |
| } |
| |
| void DownloadItemImpl::SetOpened(bool opened) { |
| opened_ = opened; |
| } |
| |
| void DownloadItemImpl::SetLastAccessTime(base::Time last_access_time) { |
| last_access_time_ = last_access_time; |
| UpdateObservers(); |
| } |
| |
| void DownloadItemImpl::SetDisplayName(const base::FilePath& name) { |
| display_name_ = name; |
| } |
| |
| std::string DownloadItemImpl::DebugString(bool verbose) const { |
| std::string description = base::StringPrintf( |
| "{ id = %d" |
| " state = %s", |
| download_id_, DebugDownloadStateString(state_)); |
| |
| // Construct a string of the URL chain. |
| std::string url_list("<none>"); |
| if (!request_info_.url_chain.empty()) { |
| auto iter = request_info_.url_chain.begin(); |
| auto last = request_info_.url_chain.end(); |
| url_list = (*iter).is_valid() ? (*iter).spec() : "<invalid>"; |
| ++iter; |
| for (; verbose && (iter != last); ++iter) { |
| url_list += " ->\n\t"; |
| const GURL& next_url = *iter; |
| url_list += next_url.is_valid() ? next_url.spec() : "<invalid>"; |
| } |
| } |
| |
| if (verbose) { |
| description += base::StringPrintf( |
| " total = %" PRId64 " received = %" PRId64 |
| " reason = %s" |
| " paused = %c" |
| " resume_mode = %s" |
| " auto_resume_count = %d" |
| " danger = %d" |
| " all_data_saved = %c" |
| " last_modified = '%s'" |
| " etag = '%s'" |
| " has_download_file = %s" |
| " url_chain = \n\t\"%s\"\n\t" |
| " current_path = \"%" PRFilePath |
| "\"\n\t" |
| " target_path = \"%" PRFilePath |
| "\"" |
| " referrer = \"%s\"" |
| " site_url = \"%s\"", |
| GetTotalBytes(), GetReceivedBytes(), |
| DownloadInterruptReasonToString(last_reason_).c_str(), |
| IsPaused() ? 'T' : 'F', DebugResumeModeString(GetResumeMode()), |
| auto_resume_count_, GetDangerType(), AllDataSaved() ? 'T' : 'F', |
| GetLastModifiedTime().c_str(), GetETag().c_str(), |
| download_file_ ? "true" : "false", url_list.c_str(), |
| GetFullPath().value().c_str(), GetTargetFilePath().value().c_str(), |
| GetReferrerUrl().spec().c_str(), GetSiteUrl().spec().c_str()); |
| } else { |
| description += base::StringPrintf(" url = \"%s\"", url_list.c_str()); |
| } |
| |
| description += " }"; |
| |
| return description; |
| } |
| |
| void DownloadItemImpl::SimulateErrorForTesting(DownloadInterruptReason reason) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| InterruptWithPartialState(GetReceivedBytes(), nullptr, reason); |
| UpdateObservers(); |
| } |
| |
| ResumeMode DownloadItemImpl::GetResumeMode() const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // We can't continue without a handle on the intermediate file. |
| // We also can't continue if we don't have some verifier to make sure |
| // we're getting the same file. |
| bool restart_required = |
| (GetFullPath().empty() || |
| (!HasStrongValidators() && |
| !base::FeatureList::IsEnabled( |
| features::kAllowDownloadResumptionWithoutStrongValidators))); |
| // We won't auto-restart if we've used up our attempts or the |
| // download has been paused by user action. |
| bool user_action_required = |
| (auto_resume_count_ >= kMaxAutoResumeAttempts || IsPaused()); |
| |
| return GetDownloadResumeMode(GetURL(), last_reason_, restart_required, |
| user_action_required); |
| } |
| |
| bool DownloadItemImpl::HasStrongValidators() const { |
| return !etag_.empty() || !last_modified_time_.empty(); |
| } |
| |
| void DownloadItemImpl::UpdateValidatorsOnResumption( |
| const DownloadCreateInfo& new_create_info) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK_EQ(RESUMING_INTERNAL, state_); |
| DCHECK(!new_create_info.url_chain.empty()); |
| |
| // We are going to tack on any new redirects to our list of redirects. |
| // When a download is resumed, the URL used for the resumption request is the |
| // one at the end of the previous redirect chain. Tacking additional redirects |
| // to the end of this chain ensures that: |
| // - If the download needs to be resumed again, the ETag/Last-Modified headers |
| // will be used with the last server that sent them to us. |
| // - The redirect chain contains all the servers that were involved in this |
| // download since the initial request, in order. |
| auto chain_iter = new_create_info.url_chain.begin(); |
| if (*chain_iter == request_info_.url_chain.back()) |
| ++chain_iter; |
| |
| // Record some stats. If the precondition failed (the server returned |
| // HTTP_PRECONDITION_FAILED), then the download will automatically retried as |
| // a full request rather than a partial. Full restarts clobber validators. |
| if (etag_ != new_create_info.etag || |
| last_modified_time_ != new_create_info.last_modified) { |
| if (destination_info_.received_bytes > 0) { |
| RecordResumptionRestartCount( |
| ResumptionRestartCountTypes::kStrongValidatorChangesCount); |
| } |
| received_slices_.clear(); |
| destination_info_.received_bytes = 0; |
| } |
| |
| if (destination_info_.received_bytes > 0 && new_create_info.offset == 0) { |
| if (!base::FeatureList::IsEnabled( |
| features::kAllowDownloadResumptionWithoutStrongValidators) || |
| GetDownloadValidationLengthConfig() > |
| destination_info_.received_bytes) { |
| RecordResumptionRestartCount( |
| ResumptionRestartCountTypes::kRequestedByServerCount); |
| } |
| } |
| |
| request_info_.url_chain.insert(request_info_.url_chain.end(), chain_iter, |
| new_create_info.url_chain.end()); |
| etag_ = new_create_info.etag; |
| last_modified_time_ = new_create_info.last_modified; |
| response_headers_ = new_create_info.response_headers; |
| content_disposition_ = new_create_info.content_disposition; |
| // It is possible that the previous download attempt failed right before the |
| // response is received. Need to reset the MIME type. |
| mime_type_ = new_create_info.mime_type; |
| |
| // Don't update observers. This method is expected to be called just before a |
| // DownloadFile is created and Start() is called. The observers will be |
| // notified when the download transitions to the IN_PROGRESS state. |
| } |
| |
| void DownloadItemImpl::NotifyRemoved() { |
| for (auto& observer : observers_) |
| observer.OnDownloadRemoved(this); |
| } |
| |
| void DownloadItemImpl::OnDownloadedFileRemoved() { |
| file_externally_removed_ = true; |
| DVLOG(20) << __func__ << "() download=" << DebugString(true); |
| UpdateObservers(); |
| } |
| |
| base::WeakPtr<DownloadDestinationObserver> |
| DownloadItemImpl::DestinationObserverAsWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void DownloadItemImpl::SetTotalBytes(int64_t total_bytes) { |
| total_bytes_ = total_bytes; |
| } |
| |
| void DownloadItemImpl::OnAllDataSaved( |
| int64_t total_bytes, |
| std::unique_ptr<crypto::SecureHash> hash_state) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(!AllDataSaved()); |
| destination_info_.all_data_saved = true; |
| SetTotalBytes(total_bytes); |
| UpdateProgress(total_bytes, 0); |
| received_slices_.clear(); |
| SetHashState(std::move(hash_state)); |
| hash_state_.reset(); // No need to retain hash_state_ since we are done with |
| // the download and don't expect to receive any more |
| // data. |
| |
| if (received_bytes_at_length_mismatch_ > 0) { |
| if (total_bytes > received_bytes_at_length_mismatch_) { |
| RecordDownloadCountWithSource( |
| MORE_BYTES_RECEIVED_AFTER_CONTENT_LENGTH_MISMATCH_COUNT, |
| download_source_); |
| } else if (total_bytes == received_bytes_at_length_mismatch_) { |
| RecordDownloadCountWithSource( |
| NO_BYTES_RECEIVED_AFTER_CONTENT_LENGTH_MISMATCH_COUNT, |
| download_source_); |
| } else { |
| // This could happen if the content changes on the server. |
| } |
| } |
| DVLOG(20) << __func__ << "() download=" << DebugString(true); |
| UpdateObservers(); |
| } |
| |
| void DownloadItemImpl::MarkAsComplete() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| DCHECK(AllDataSaved()); |
| destination_info_.end_time = base::Time::Now(); |
| TransitionTo(COMPLETE_INTERNAL); |
| UpdateObservers(); |
| } |
| |
| void DownloadItemImpl::DestinationUpdate( |
| int64_t bytes_so_far, |
| int64_t bytes_per_sec, |
| const std::vector<DownloadItem::ReceivedSlice>& received_slices) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| // If the download is in any other state we don't expect any |
| // DownloadDestinationObserver callbacks. An interruption or a cancellation |
| // results in a call to ReleaseDownloadFile which invalidates the weak |
| // reference held by the DownloadFile and hence cuts off any pending |
| // callbacks. |
| DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL || |
| state_ == INTERRUPTED_TARGET_PENDING_INTERNAL); |
| |
| // There must be no pending deferred_interrupt_reason_. |
| DCHECK(deferred_interrupt_reason_ == DOWNLOAD_INTERRUPT_REASON_NONE || |
| state_ == INTERRUPTED_TARGET_PENDING_INTERNAL); |
| |
| DVLOG(20) << __func__ << "() so_far=" << bytes_so_far |
| << " per_sec=" << bytes_per_sec |
| << " download=" << DebugString(true); |
| |
| UpdateProgress(bytes_so_far, bytes_per_sec); |
| received_slices_ = received_slices; |
| TRACE_EVENT_INSTANT1("download", "DownloadItemUpdated", |
| TRACE_EVENT_SCOPE_THREAD, "bytes_so_far", |
| GetReceivedBytes()); |
| |
| if (IsPaused() && destination_info_.received_bytes == bytes_so_far) |
| return; |
| |
| UpdateObservers(); |
| } |
| |
| void DownloadItemImpl::DestinationError( |
| DownloadInterruptReason reason, |
| int64_t bytes_so_far, |
| std::unique_ptr<crypto::SecureHash> secure_hash) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| // If the download is in any other state we don't expect any |
| // DownloadDestinationObserver callbacks. An interruption or a cancellation |
| // results in a call to ReleaseDownloadFile which invalidates the weak |
| // reference held by the DownloadFile and hence cuts off any pending |
| // callbacks. |
| DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL); |
| DVLOG(20) << __func__ |
| << "() reason:" << DownloadInterruptReasonToString(reason) |
| << " this:" << DebugString(true); |
| |
| InterruptWithPartialState(bytes_so_far, std::move(secure_hash), reason); |
| UpdateObservers(); |
| } |
| |
| void DownloadItemImpl::DestinationCompleted( |
| int64_t total_bytes, |
| std::unique_ptr<crypto::SecureHash> secure_hash) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| // If the download is in any other state we don't expect any |
| // DownloadDestinationObserver callbacks. An interruption or a cancellation |
| // results in a call to ReleaseDownloadFile which invalidates the weak |
| // reference held by the DownloadFile and hence cuts off any pending |
| // callbacks. |
| DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL || |
| state_ == INTERRUPTED_TARGET_PENDING_INTERNAL); |
| DVLOG(20) << __func__ << "() download=" << DebugString(true); |
| |
| OnAllDataSaved(total_bytes, std::move(secure_hash)); |
| MaybeCompleteDownload(); |
| } |
| |
| void DownloadItemImpl::SetDelegate(DownloadItemImplDelegate* delegate) { |
| delegate_->Detach(); |
| delegate_ = delegate; |
| delegate_->Attach(); |
| } |
| |
| void DownloadItemImpl::SetDownloadId(uint32_t download_id) { |
| download_id_ = download_id; |
| } |
| |
| void DownloadItemImpl::SetAutoResumeCountForTesting(int32_t auto_resume_count) { |
| auto_resume_count_ = auto_resume_count; |
| } |
| |
| // **** Download progression cascade |
| |
| void DownloadItemImpl::Init(bool active, |
| DownloadItem::DownloadCreationType download_type) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| download_type_ = download_type; |
| std::string file_name; |
| if (download_type == TYPE_HISTORY_IMPORT) { |
| // target_path_ works for History and Save As versions. |
| file_name = GetTargetFilePath().AsUTF8Unsafe(); |
| } else { |
| // See if it's set programmatically. |
| file_name = GetForcedFilePath().AsUTF8Unsafe(); |
| // Possibly has a 'download' attribute for the anchor. |
| if (file_name.empty()) |
| file_name = GetSuggestedFilename(); |
| // From the URL file name. |
| if (file_name.empty()) |
| file_name = GetURL().ExtractFileName(); |
| } |
| |
| auto active_data = std::make_unique<DownloadItemActivatedData>( |
| download_type, GetId(), GetOriginalUrl().spec(), GetURL().spec(), |
| file_name, GetDangerType(), GetReceivedBytes(), HasUserGesture()); |
| |
| if (active) { |
| TRACE_EVENT_ASYNC_BEGIN1("download", "DownloadItemActive", download_id_, |
| "download_item", std::move(active_data)); |
| ukm_download_id_ = GetUniqueDownloadId(); |
| } else { |
| TRACE_EVENT_INSTANT1("download", "DownloadItemActive", |
| TRACE_EVENT_SCOPE_THREAD, "download_item", |
| std::move(active_data)); |
| |
| // Read data from in-progress cache. |
| // TODO(qinmin): Remove this once we initialize the data in DownloadItemImpl |
| // ctor. |
| auto in_progress_entry = delegate_->GetInProgressEntry(this); |
| if (in_progress_entry) { |
| download_source_ = in_progress_entry->download_source; |
| fetch_error_body_ = in_progress_entry->fetch_error_body; |
| request_headers_ = in_progress_entry->request_headers; |
| ukm_download_id_ = in_progress_entry->ukm_download_id; |
| bytes_wasted_ = in_progress_entry->bytes_wasted; |
| } else { |
| ukm_download_id_ = GetUniqueDownloadId(); |
| } |
| } |
| |
| DVLOG(20) << __func__ << "() " << DebugString(true); |
| } |
| |
| // We're starting the download. |
| void DownloadItemImpl::Start( |
| std::unique_ptr<DownloadFile> file, |
| std::unique_ptr<DownloadRequestHandleInterface> req_handle, |
| const DownloadCreateInfo& new_create_info, |
| scoped_refptr<download::DownloadURLLoaderFactoryGetter> |
| url_loader_factory_getter, |
| net::URLRequestContextGetter* url_request_context_getter) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(!download_file_); |
| DVLOG(20) << __func__ << "() this=" << DebugString(true); |
| RecordDownloadCountWithSource(START_COUNT, download_source_); |
| |
| download_file_ = std::move(file); |
| job_ = DownloadJobFactory::CreateJob( |
| this, std::move(req_handle), new_create_info, false, |
| std::move(url_loader_factory_getter), url_request_context_getter, |
| delegate_ ? delegate_->GetServiceManagerConnector() : nullptr); |
| if (job_->IsParallelizable()) { |
| RecordParallelizableDownloadCount(START_COUNT, IsParallelDownloadEnabled()); |
| } |
| |
| deferred_interrupt_reason_ = DOWNLOAD_INTERRUPT_REASON_NONE; |
| |
| if (state_ == CANCELLED_INTERNAL) { |
| // The download was in the process of resuming when it was cancelled. Don't |
| // proceed. |
| ReleaseDownloadFile(true); |
| job_->Cancel(true); |
| return; |
| } |
| |
| // The state could be one of the following: |
| // |
| // INITIAL_INTERNAL: A normal download attempt. |
| // |
| // RESUMING_INTERNAL: A resumption attempt. May or may not have been |
| // successful. |
| DCHECK(state_ == INITIAL_INTERNAL || state_ == RESUMING_INTERNAL); |
| |
| // If the state_ is INITIAL_INTERNAL, then the target path must be empty. |
| DCHECK(state_ != INITIAL_INTERNAL || GetTargetFilePath().empty()); |
| |
| // If a resumption attempted failed, or if the download was DOA, then the |
| // download should go back to being interrupted. |
| if (new_create_info.result != DOWNLOAD_INTERRUPT_REASON_NONE) { |
| DCHECK(!download_file_); |
| |
| // Download requests that are interrupted by Start() should result in a |
| // DownloadCreateInfo with an intact DownloadSaveInfo. |
| DCHECK(new_create_info.save_info); |
| |
| std::unique_ptr<crypto::SecureHash> hash_state = |
| new_create_info.save_info->hash_state |
| ? new_create_info.save_info->hash_state->Clone() |
| : nullptr; |
| |
| hash_state_ = std::move(hash_state); |
| destination_info_.hash.clear(); |
| deferred_interrupt_reason_ = new_create_info.result; |
| TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL); |
| DetermineDownloadTarget(); |
| return; |
| } |
| |
| if (state_ == INITIAL_INTERNAL) { |
| RecordDownloadCountWithSource(NEW_DOWNLOAD_COUNT, download_source_); |
| if (job_->IsParallelizable()) { |
| RecordParallelizableDownloadCount(NEW_DOWNLOAD_COUNT, |
| IsParallelDownloadEnabled()); |
| } |
| RecordDownloadMimeType(mime_type_); |
| DownloadContent file_type = DownloadContentFromMimeType(mime_type_, false); |
| bool is_same_host_download = |
| base::StringPiece(new_create_info.url().host()) |
| .ends_with(new_create_info.site_url.host()); |
| DownloadConnectionSecurity state = CheckDownloadConnectionSecurity( |
| new_create_info.url(), new_create_info.url_chain); |
| DownloadUkmHelper::RecordDownloadStarted( |
| ukm_download_id_, new_create_info.ukm_source_id, file_type, |
| download_source_, state, is_same_host_download); |
| RecordDownloadValidationMetrics(DownloadMetricsCallsite::kDownloadItem, |
| state, file_type); |
| |
| if (!delegate_->IsOffTheRecord()) { |
| RecordDownloadCountWithSource(NEW_DOWNLOAD_COUNT_NORMAL_PROFILE, |
| download_source_); |
| RecordDownloadMimeTypeForNormalProfile(mime_type_); |
| } |
| } |
| |
| // Successful download start. |
| DCHECK(download_file_); |
| DCHECK(job_); |
| |
| if (state_ == RESUMING_INTERNAL) |
| UpdateValidatorsOnResumption(new_create_info); |
| |
| // If the download is not parallel, clear the |received_slices_|. |
| if (!received_slices_.empty() && !job_->IsParallelizable()) { |
| destination_info_.received_bytes = |
| GetMaxContiguousDataBlockSizeFromBeginning(received_slices_); |
| received_slices_.clear(); |
| } |
| |
| TransitionTo(TARGET_PENDING_INTERNAL); |
| |
| job_->Start(download_file_.get(), |
| base::Bind(&DownloadItemImpl::OnDownloadFileInitialized, |
| weak_ptr_factory_.GetWeakPtr()), |
| GetReceivedSlices()); |
| } |
| |
| void DownloadItemImpl::OnDownloadFileInitialized(DownloadInterruptReason result, |
| int64_t bytes_wasted) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(state_ == TARGET_PENDING_INTERNAL || |
| state_ == INTERRUPTED_TARGET_PENDING_INTERNAL) |
| << "Unexpected state: " << DebugDownloadStateString(state_); |
| |
| DVLOG(20) << __func__ |
| << "() result:" << DownloadInterruptReasonToString(result); |
| |
| if (bytes_wasted > 0) { |
| bytes_wasted_ += bytes_wasted; |
| delegate_->ReportBytesWasted(this); |
| } |
| |
| // Handle download interrupt reason. |
| if (result != DOWNLOAD_INTERRUPT_REASON_NONE) { |
| ReleaseDownloadFile(true); |
| InterruptAndDiscardPartialState(result); |
| } |
| |
| DetermineDownloadTarget(); |
| } |
| |
| void DownloadItemImpl::DetermineDownloadTarget() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DVLOG(20) << __func__ << "() " << DebugString(true); |
| |
| RecordDownloadCountWithSource(DETERMINE_DOWNLOAD_TARGET_COUNT, |
| download_source_); |
| delegate_->DetermineDownloadTarget( |
| this, base::Bind(&DownloadItemImpl::OnDownloadTargetDetermined, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| // Called by delegate_ when the download target path has been determined. |
| void DownloadItemImpl::OnDownloadTargetDetermined( |
| const base::FilePath& target_path, |
| TargetDisposition disposition, |
| DownloadDangerType danger_type, |
| const base::FilePath& intermediate_path, |
| DownloadInterruptReason interrupt_reason) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (state_ == CANCELLED_INTERNAL) |
| return; |
| |
| DCHECK(state_ == TARGET_PENDING_INTERNAL || |
| state_ == INTERRUPTED_TARGET_PENDING_INTERNAL); |
| DVLOG(20) << __func__ << "() target_path:" << target_path.value() |
| << " intermediate_path:" << intermediate_path.value() |
| << " disposition:" << disposition << " danger_type:" << danger_type |
| << " interrupt_reason:" |
| << DownloadInterruptReasonToString(interrupt_reason) |
| << " this:" << DebugString(true); |
| |
| RecordDownloadCountWithSource(DOWNLOAD_TARGET_DETERMINED_COUNT, |
| download_source_); |
| |
| if (IsCancellation(interrupt_reason) || target_path.empty()) { |
| Cancel(true); |
| return; |
| } |
| |
| // There were no other pending errors, and we just failed to determined the |
| // download target. The target path, if it is non-empty, should be considered |
| // suspect. The safe option here is to interrupt the download without doing an |
| // intermediate rename. In the case of a new download, we'll lose the partial |
| // data that may have been downloaded, but that should be a small loss. |
| if (state_ == TARGET_PENDING_INTERNAL && |
| interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE) { |
| deferred_interrupt_reason_ = interrupt_reason; |
| TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL); |
| OnTargetResolved(); |
| return; |
| } |
| |
| destination_info_.target_path = target_path; |
| destination_info_.target_disposition = disposition; |
| SetDangerType(danger_type); |
| |
| // This was an interrupted download that was looking for a filename. Resolve |
| // early without performing the intermediate rename. If there is a |
| // DownloadFile, then that should be renamed to the intermediate name before |
| // we can interrupt the download. Otherwise we may lose intermediate state. |
| if (state_ == INTERRUPTED_TARGET_PENDING_INTERNAL && !download_file_) { |
| OnTargetResolved(); |
| return; |
| } |
| |
| // We want the intermediate and target paths to refer to the same directory so |
| // that they are both on the same device and subject to same |
| // space/permission/availability constraints. |
| DCHECK(intermediate_path.DirName() == target_path.DirName()); |
| |
| // During resumption, we may choose to proceed with the same intermediate |
| // file. No rename is necessary if our intermediate file already has the |
| // correct name. |
| // |
| // The intermediate name may change from its original value during filename |
| // determination on resumption, for example if the reason for the interruption |
| // was the download target running out space, resulting in a user prompt. |
| if (intermediate_path == GetFullPath()) { |
| OnDownloadRenamedToIntermediateName(DOWNLOAD_INTERRUPT_REASON_NONE, |
| intermediate_path); |
| return; |
| } |
| |
| // Rename to intermediate name. |
| // TODO(asanka): Skip this rename if AllDataSaved() is true. This avoids a |
| // spurious rename when we can just rename to the final |
| // filename. Unnecessary renames may cause bugs like |
| // http://crbug.com/74187. |
| DCHECK(!IsSavePackageDownload()); |
| DownloadFile::RenameCompletionCallback callback = |
| base::Bind(&DownloadItemImpl::OnDownloadRenamedToIntermediateName, |
| weak_ptr_factory_.GetWeakPtr()); |
| #if defined(OS_ANDROID) |
| if ((download_type_ == TYPE_ACTIVE_DOWNLOAD && |
| DownloadCollectionBridge::ShouldPublishDownload(GetTargetFilePath())) || |
| GetTargetFilePath().IsContentUri()) { |
| GetDownloadTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DownloadFile::RenameToIntermediateUri, |
| // Safe because we control download file lifetime. |
| base::Unretained(download_file_.get()), GetOriginalUrl(), |
| GetReferrerUrl(), GetFileNameToReportUser(), |
| GetMimeType(), GetTargetFilePath(), |
| std::move(callback))); |
| return; |
| } |
| #endif // defined(OS_ANDROID) |
| |
| GetDownloadTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DownloadFile::RenameAndUniquify, |
| // Safe because we control download file lifetime. |
| base::Unretained(download_file_.get()), intermediate_path, |
| std::move(callback))); |
| } |
| |
| void DownloadItemImpl::OnDownloadRenamedToIntermediateName( |
| DownloadInterruptReason reason, |
| const base::FilePath& full_path) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(state_ == TARGET_PENDING_INTERNAL || |
| state_ == INTERRUPTED_TARGET_PENDING_INTERNAL); |
| DCHECK(download_file_); |
| DVLOG(20) << __func__ << "() download=" << DebugString(true); |
| |
| if (DOWNLOAD_INTERRUPT_REASON_NONE == reason) { |
| SetFullPath(full_path); |
| #if defined(OS_ANDROID) |
| // For content URIs, target file path is the same as the current path. |
| if (full_path.IsContentUri()) { |
| destination_info_.target_path = full_path; |
| if (display_name_.empty()) |
| SetDisplayName(download_file_->GetDisplayName()); |
| } |
| #endif // defined(OS_ANDROID) |
| } else { |
| // TODO(asanka): Even though the rename failed, it may still be possible to |
| // recover the partial state from the 'before' name. |
| deferred_interrupt_reason_ = reason; |
| TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL); |
| } |
| OnTargetResolved(); |
| } |
| |
| void DownloadItemImpl::OnTargetResolved() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DVLOG(20) << __func__ << "() download=" << DebugString(true); |
| DCHECK((state_ == TARGET_PENDING_INTERNAL && |
| deferred_interrupt_reason_ == DOWNLOAD_INTERRUPT_REASON_NONE) || |
| (state_ == INTERRUPTED_TARGET_PENDING_INTERNAL && |
| deferred_interrupt_reason_ != DOWNLOAD_INTERRUPT_REASON_NONE)) |
| << " deferred_interrupt_reason_:" |
| << DownloadInterruptReasonToString(deferred_interrupt_reason_) |
| << " this:" << DebugString(true); |
| |
| // This transition is here to ensure that the DownloadItemImpl state machine |
| // doesn't transition to INTERRUPTED or IN_PROGRESS from |
| // TARGET_PENDING_INTERNAL directly. Doing so without passing through |
| // OnTargetResolved() can result in an externally visible state where the |
| // download is interrupted but doesn't have a target path associated with it. |
| // |
| // While not terrible, this complicates the DownloadItem<->Observer |
| // relationship since an observer that needs a target path in order to respond |
| // properly to an interruption will need to wait for another OnDownloadUpdated |
| // notification. This requirement currently affects all of our UIs. |
| TransitionTo(TARGET_RESOLVED_INTERNAL); |
| |
| if (DOWNLOAD_INTERRUPT_REASON_NONE != deferred_interrupt_reason_) { |
| InterruptWithPartialState(GetReceivedBytes(), std::move(hash_state_), |
| deferred_interrupt_reason_); |
| deferred_interrupt_reason_ = DOWNLOAD_INTERRUPT_REASON_NONE; |
| UpdateObservers(); |
| return; |
| } |
| |
| TransitionTo(IN_PROGRESS_INTERNAL); |
| // TODO(asanka): Calling UpdateObservers() prior to MaybeCompleteDownload() is |
| // not safe. The download could be in an underminate state after invoking |
| // observers. http://crbug.com/586610 |
| UpdateObservers(); |
| MaybeCompleteDownload(); |
| } |
| |
| // When SavePackage downloads MHTML to GData (see |
| // SavePackageFilePickerChromeOS), GData calls MaybeCompleteDownload() like it |
| // does for non-SavePackage downloads, but SavePackage downloads never satisfy |
| // IsDownloadReadyForCompletion(). GDataDownloadObserver manually calls |
| // DownloadItem::UpdateObservers() when the upload completes so that |
| // SavePackage notices that the upload has completed and runs its normal |
| // Finish() pathway. MaybeCompleteDownload() is never the mechanism by which |
| // SavePackage completes downloads. SavePackage always uses its own Finish() to |
| // mark downloads complete. |
| void DownloadItemImpl::MaybeCompleteDownload() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(!IsSavePackageDownload()); |
| |
| if (!IsDownloadReadyForCompletion( |
| base::Bind(&DownloadItemImpl::MaybeCompleteDownload, |
| weak_ptr_factory_.GetWeakPtr()))) |
| return; |
| // Confirm we're in the proper set of states to be here; have all data, have a |
| // history handle, (validated or safe). |
| DCHECK_EQ(IN_PROGRESS_INTERNAL, state_); |
| DCHECK(!IsDangerous()); |
| DCHECK(AllDataSaved()); |
| |
| OnDownloadCompleting(); |
| } |
| |
| // Called by MaybeCompleteDownload() when it has determined that the download |
| // is ready for completion. |
| void DownloadItemImpl::OnDownloadCompleting() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (state_ != IN_PROGRESS_INTERNAL) |
| return; |
| |
| DVLOG(20) << __func__ << "() " << DebugString(true); |
| DCHECK(!GetTargetFilePath().empty()); |
| DCHECK(!IsDangerous()); |
| |
| DCHECK(download_file_); |
| // Unilaterally rename; even if it already has the right name, |
| // we need theannotation. |
| DownloadFile::RenameCompletionCallback callback = |
| base::Bind(&DownloadItemImpl::OnDownloadRenamedToFinalName, |
| weak_ptr_factory_.GetWeakPtr()); |
| #if defined(OS_ANDROID) |
| if (GetTargetFilePath().IsContentUri()) { |
| GetDownloadTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DownloadFile::PublishDownload, |
| // Safe because we control download file lifetime. |
| base::Unretained(download_file_.get()), |
| std::move(callback))); |
| return; |
| } |
| #endif // defined(OS_ANDROID) |
| |
| std::unique_ptr<service_manager::Connector> new_connector; |
| service_manager::Connector* connector = |
| delegate_->GetServiceManagerConnector(); |
| if (connector) |
| new_connector = connector->Clone(); |
| |
| GetDownloadTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DownloadFile::RenameAndAnnotate, |
| base::Unretained(download_file_.get()), |
| GetTargetFilePath(), |
| delegate_->GetApplicationClientIdForFileScanning(), |
| delegate_->IsOffTheRecord() ? GURL() : GetURL(), |
| delegate_->IsOffTheRecord() ? GURL() : GetReferrerUrl(), |
| std::move(new_connector), std::move(callback))); |
| } |
| |
| void DownloadItemImpl::OnDownloadRenamedToFinalName( |
| DownloadInterruptReason reason, |
| const base::FilePath& full_path) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(!IsSavePackageDownload()); |
| |
| // If a cancel or interrupt hit, we'll cancel the DownloadFile, which |
| // will result in deleting the file on the file thread. So we don't |
| // care about the name having been changed. |
| if (state_ != IN_PROGRESS_INTERNAL) |
| return; |
| |
| DVLOG(20) << __func__ << "() full_path = \"" << full_path.value() << "\" " |
| << DebugString(false); |
| |
| if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) { |
| // Failure to perform the final rename is considered fatal. TODO(asanka): It |
| // may not be, in which case we should figure out whether we can recover the |
| // state. |
| InterruptAndDiscardPartialState(reason); |
| UpdateObservers(); |
| return; |
| } |
| |
| DCHECK(GetTargetFilePath() == full_path); |
| |
| if (full_path != GetFullPath()) { |
| // full_path is now the current and target file path. |
| DCHECK(!full_path.empty()); |
| SetFullPath(full_path); |
| } |
| |
| // Complete the download and release the DownloadFile. |
| DCHECK(download_file_); |
| ReleaseDownloadFile(false); |
| |
| // We're not completely done with the download item yet, but at this |
| // point we're committed to complete the download. Cancels (or Interrupts, |
| // though it's not clear how they could happen) after this point will be |
| // ignored. |
| TransitionTo(COMPLETING_INTERNAL); |
| |
| if (delegate_->ShouldOpenDownload( |
| this, base::Bind(&DownloadItemImpl::DelayedDownloadOpened, |
| weak_ptr_factory_.GetWeakPtr()))) { |
| Completed(); |
| } else { |
| delegate_delayed_complete_ = true; |
| UpdateObservers(); |
| } |
| } |
| |
| void DownloadItemImpl::DelayedDownloadOpened(bool auto_opened) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| auto_opened_ = auto_opened; |
| Completed(); |
| } |
| |
| void DownloadItemImpl::Completed() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| DVLOG(20) << __func__ << "() " << DebugString(false); |
| |
| DCHECK(AllDataSaved()); |
| destination_info_.end_time = base::Time::Now(); |
| TransitionTo(COMPLETE_INTERNAL); |
| |
| bool is_parallelizable = job_ && job_->IsParallelizable(); |
| RecordDownloadCompleted(GetReceivedBytes(), is_parallelizable, |
| download_source_, has_resumed_, |
| HasStrongValidators()); |
| if (!delegate_->IsOffTheRecord()) { |
| RecordDownloadCountWithSource(COMPLETED_COUNT_NORMAL_PROFILE, |
| download_source_); |
| } |
| if (is_parallelizable) { |
| RecordParallelizableDownloadCount(COMPLETED_COUNT, |
| IsParallelDownloadEnabled()); |
| int64_t content_length = -1; |
| if (response_headers_->response_code() != net::HTTP_PARTIAL_CONTENT) { |
| content_length = response_headers_->GetContentLength(); |
| } else { |
| int64_t first_byte = -1; |
| int64_t last_byte = -1; |
| response_headers_->GetContentRangeFor206(&first_byte, &last_byte, |
| &content_length); |
| } |
| if (content_length > 0) |
| RecordParallelizableContentLength(content_length); |
| } |
| |
| if (auto_opened_) { |
| // If it was already handled by the delegate, do nothing. |
| } else if (GetOpenWhenComplete() || ShouldOpenFileBasedOnExtension() || |
| IsTemporary()) { |
| // If the download is temporary, like in drag-and-drop, do not open it but |
| // we still need to set it auto-opened so that it can be removed from the |
| // download shelf. |
| if (!IsTemporary()) |
| OpenDownload(); |
| |
| auto_opened_ = true; |
| } |
| |
| base::TimeDelta time_since_start = GetEndTime() - GetStartTime(); |
| |
| // If all data is saved, the number of received bytes is resulting file size. |
| int resulting_file_size = GetReceivedBytes(); |
| |
| DownloadUkmHelper::RecordDownloadCompleted( |
| ukm_download_id_, resulting_file_size, time_since_start, bytes_wasted_); |
| |
| // After all of the records are done, then update the observers. |
| UpdateObservers(); |
| } |
| |
| // **** End of Download progression cascade |
| |
| void DownloadItemImpl::InterruptAndDiscardPartialState( |
| DownloadInterruptReason reason) { |
| InterruptWithPartialState(0, nullptr, reason); |
| } |
| |
| void DownloadItemImpl::InterruptWithPartialState( |
| int64_t bytes_so_far, |
| std::unique_ptr<crypto::SecureHash> hash_state, |
| DownloadInterruptReason reason) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, reason); |
| DVLOG(20) << __func__ |
| << "() reason:" << DownloadInterruptReasonToString(reason) |
| << " bytes_so_far:" << bytes_so_far |
| << " hash_state:" << (hash_state ? "Valid" : "Invalid") |
| << " this=" << DebugString(true); |
| |
| // Somewhat counter-intuitively, it is possible for us to receive an |
| // interrupt after we've already been interrupted. The generation of |
| // interrupts from the file thread Renames and the generation of |
| // interrupts from disk writes go through two different mechanisms (driven |
| // by rename requests from UI thread and by write requests from IO thread, |
| // respectively), and since we choose not to keep state on the File thread, |
| // this is the place where the races collide. It's also possible for |
| // interrupts to race with cancels. |
| switch (state_) { |
| case CANCELLED_INTERNAL: |
| // If the download is already cancelled, then there's no point in |
| // transitioning out to interrupted. |
| case COMPLETING_INTERNAL: |
| case COMPLETE_INTERNAL: |
| // Already complete. |
| return; |
| |
| case INITIAL_INTERNAL: |
| case MAX_DOWNLOAD_INTERNAL_STATE: |
| NOTREACHED(); |
| return; |
| |
| case TARGET_PENDING_INTERNAL: |
| case INTERRUPTED_TARGET_PENDING_INTERNAL: |
| // Postpone recognition of this error until after file name determination |
| // has completed and the intermediate file has been renamed to simplify |
| // resumption conditions. The target determination logic is much simpler |
| // if the state of the download remains constant until that stage |
| // completes. |
| // |
| // current_path_ may be empty because it is possible for |
| // DownloadItem to receive a DestinationError prior to the |
| // download file initialization complete callback. |
| if (!IsCancellation(reason)) { |
| UpdateProgress(bytes_so_far, 0); |
| SetHashState(std::move(hash_state)); |
| deferred_interrupt_reason_ = reason; |
| TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL); |
| return; |
| } |
| // else - Fallthrough for cancellation handling which is equivalent to the |
| // IN_PROGRESS state. |
| FALLTHROUGH; |
| |
| case IN_PROGRESS_INTERNAL: |
| case TARGET_RESOLVED_INTERNAL: { |
| // last_reason_ needs to be set for GetResumeMode() to work. |
| last_reason_ = reason; |
| |
| ResumeMode resume_mode = GetResumeMode(); |
| ReleaseDownloadFile(resume_mode != ResumeMode::IMMEDIATE_CONTINUE && |
| resume_mode != ResumeMode::USER_CONTINUE); |
| } break; |
| |
| case RESUMING_INTERNAL: |
| case INTERRUPTED_INTERNAL: |
| DCHECK(!download_file_); |
| // The first non-cancel interrupt reason wins in cases where multiple |
| // things go wrong. |
| if (!IsCancellation(reason)) |
| return; |
| |
| last_reason_ = reason; |
| // There is no download file and this is transitioning from INTERRUPTED |
| // to CANCELLED. The intermediate file is no longer usable, and should |
| // be deleted. |
| DeleteDownloadFile(); |
| break; |
| } |
| |
| // Reset all data saved, as even if we did save all the data we're going to go |
| // through another round of downloading when we resume. There's a potential |
| // problem here in the abstract, as if we did download all the data and then |
| // run into a continuable error, on resumption we won't download any more |
| // data. However, a) there are currently no continuable errors that can occur |
| // after we download all the data, and b) if there were, that would probably |
| // simply result in a null range request, which would generate a |
| // DestinationCompleted() notification from the DownloadFile, which would |
| // behave properly with setting all_data_saved_ to false here. |
| destination_info_.all_data_saved = false; |
| |
| if (GetFullPath().empty()) { |
| hash_state_.reset(); |
| destination_info_.hash.clear(); |
| destination_info_.received_bytes = 0; |
| received_slices_.clear(); |
| } else { |
| UpdateProgress(bytes_so_far, 0); |
| SetHashState(std::move(hash_state)); |
| } |
| |
| if (job_) |
| job_->Cancel(false); |
| |
| if (IsCancellation(reason)) { |
| RecordDownloadCountWithSource(CANCELLED_COUNT, download_source_); |
| if (job_ && job_->IsParallelizable()) { |
| RecordParallelizableDownloadCount(CANCELLED_COUNT, |
| IsParallelDownloadEnabled()); |
| } |
| DCHECK_EQ(last_reason_, reason); |
| TransitionTo(CANCELLED_INTERNAL); |
| return; |
| } |
| |
| RecordDownloadInterrupted(reason, GetReceivedBytes(), total_bytes_, |
| job_ && job_->IsParallelizable(), |
| IsParallelDownloadEnabled(), download_source_, |
| received_bytes_at_length_mismatch_ > 0); |
| |
| base::TimeDelta time_since_start = base::Time::Now() - GetStartTime(); |
| int resulting_file_size = GetReceivedBytes(); |
| base::Optional<int> change_in_file_size; |
| if (total_bytes_ >= 0) { |
| change_in_file_size = total_bytes_ - resulting_file_size; |
| } |
| |
| DownloadUkmHelper::RecordDownloadInterrupted( |
| ukm_download_id_, change_in_file_size, reason, resulting_file_size, |
| time_since_start, bytes_wasted_); |
| if (reason == DOWNLOAD_INTERRUPT_REASON_SERVER_CONTENT_LENGTH_MISMATCH) { |
| received_bytes_at_length_mismatch_ = GetReceivedBytes(); |
| } |
| |
| // TODO(asanka): This is not good. We can transition to interrupted from |
| // target-pending, which is something we don't want to do. Perhaps we should |
| // explicitly transition to target-resolved prior to switching to interrupted. |
| DCHECK_EQ(last_reason_, reason); |
| TransitionTo(INTERRUPTED_INTERNAL); |
| delegate_->DownloadInterrupted(this); |
| AutoResumeIfValid(); |
| } |
| |
| void DownloadItemImpl::UpdateProgress(int64_t bytes_so_far, |
| int64_t bytes_per_sec) { |
| destination_info_.received_bytes = bytes_so_far; |
| bytes_per_sec_ = bytes_per_sec; |
| |
| // If we've received more data than we were expecting (bad server info?), |
| // revert to 'unknown size mode'. |
| if (bytes_so_far > total_bytes_) |
| total_bytes_ = 0; |
| } |
| |
| void DownloadItemImpl::SetHashState( |
| std::unique_ptr<crypto::SecureHash> hash_state) { |
| hash_state_ = std::move(hash_state); |
| if (!hash_state_) { |
| destination_info_.hash.clear(); |
| return; |
| } |
| |
| std::unique_ptr<crypto::SecureHash> clone_of_hash_state(hash_state_->Clone()); |
| std::vector<char> hash_value(clone_of_hash_state->GetHashLength()); |
| clone_of_hash_state->Finish(&hash_value.front(), hash_value.size()); |
| destination_info_.hash.assign(hash_value.begin(), hash_value.end()); |
| } |
| |
| void DownloadItemImpl::ReleaseDownloadFile(bool destroy_file) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DVLOG(20) << __func__ << "() destroy_file:" << destroy_file; |
| |
| if (destroy_file) { |
| if (download_file_) { |
| GetDownloadTaskRunner()->PostTask( |
| FROM_HERE, |
| // Will be deleted at end of task execution. |
| base::BindOnce(&DownloadFileCancel, std::move(download_file_))); |
| } else { |
| DeleteDownloadFile(); |
| } |
| // Avoid attempting to reuse the intermediate file by clearing out |
| // current_path_ and received slices. |
| destination_info_.current_path.clear(); |
| received_slices_.clear(); |
| } else if (download_file_) { |
| GetDownloadTaskRunner()->PostTask( |
| FROM_HERE, base::BindOnce(base::IgnoreResult(&DownloadFileDetach), |
| // Will be deleted at end of task execution. |
| std::move(download_file_))); |
| } |
| // Don't accept any more messages from the DownloadFile, and null |
| // out any previous "all data received". This also breaks links to |
| // other entities we've given out weak pointers to. |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| } |
| |
| void DownloadItemImpl::DeleteDownloadFile() { |
| if (GetFullPath().empty()) |
| return; |
| GetDownloadTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(base::IgnoreResult(&DeleteDownloadedFile), GetFullPath())); |
| destination_info_.current_path.clear(); |
| } |
| |
| bool DownloadItemImpl::IsDownloadReadyForCompletion( |
| const base::Closure& state_change_notification) { |
| // If the download hasn't progressed to the IN_PROGRESS state, then it's not |
| // ready for completion. |
| if (state_ != IN_PROGRESS_INTERNAL) |
| return false; |
| |
| // If we don't have all the data, the download is not ready for |
| // completion. |
| if (!AllDataSaved()) |
| return false; |
| |
| // If the download is dangerous, but not yet validated, it's not ready for |
| // completion. |
| if (IsDangerous()) |
| return false; |
| |
| // Check for consistency before invoking delegate. Since there are no pending |
| // target determination calls and the download is in progress, both the target |
| // and current paths should be non-empty and they should point to the same |
| // directory. |
| DCHECK(!GetTargetFilePath().empty()); |
| DCHECK(!GetFullPath().empty()); |
| DCHECK(GetTargetFilePath().DirName() == GetFullPath().DirName()); |
| |
| // Give the delegate a chance to hold up a stop sign. It'll call |
| // use back through the passed callback if it does and that state changes. |
| if (!delegate_->ShouldCompleteDownload(this, state_change_notification)) |
| return false; |
| |
| return true; |
| } |
| |
| void DownloadItemImpl::TransitionTo(DownloadInternalState new_state) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (state_ == new_state) |
| return; |
| |
| DownloadInternalState old_state = state_; |
| state_ = new_state; |
| |
| DCHECK(IsSavePackageDownload() |
| ? IsValidSavePackageStateTransition(old_state, new_state) |
| : IsValidStateTransition(old_state, new_state)) |
| << "Invalid state transition from:" << DebugDownloadStateString(old_state) |
| << " to:" << DebugDownloadStateString(new_state); |
| |
| switch (state_) { |
| case INITIAL_INTERNAL: |
| NOTREACHED(); |
| break; |
| |
| case TARGET_PENDING_INTERNAL: |
| case TARGET_RESOLVED_INTERNAL: |
| break; |
| |
| case INTERRUPTED_TARGET_PENDING_INTERNAL: |
| DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, deferred_interrupt_reason_) |
| << "Interrupt reason must be set prior to transitioning into " |
| "TARGET_PENDING"; |
| break; |
| |
| case IN_PROGRESS_INTERNAL: |
| DCHECK(!GetFullPath().empty()) << "Current output path must be known."; |
| DCHECK(!GetTargetFilePath().empty()) << "Target path must be known."; |
| DCHECK(GetFullPath().DirName() == GetTargetFilePath().DirName()) |
| << "Current output directory must match target directory."; |
| DCHECK(download_file_) << "Output file must be owned by download item."; |
| DCHECK(job_) << "Must have active download job."; |
| DCHECK(!job_->is_paused()) |
| << "At the time a download enters IN_PROGRESS state, " |
| "it must not be paused."; |
| break; |
| |
| case COMPLETING_INTERNAL: |
| DCHECK(AllDataSaved()) << "All data must be saved prior to completion."; |
| DCHECK(!download_file_) |
| << "Download file must be released prior to completion."; |
| DCHECK(!GetTargetFilePath().empty()) << "Target path must be known."; |
| DCHECK(GetFullPath() == GetTargetFilePath()) |
| << "Current output path must match target path."; |
| |
| TRACE_EVENT_INSTANT2("download", "DownloadItemCompleting", |
| TRACE_EVENT_SCOPE_THREAD, "bytes_so_far", |
| GetReceivedBytes(), "final_hash", |
| destination_info_.hash); |
| break; |
| |
| case COMPLETE_INTERNAL: |
| TRACE_EVENT_INSTANT1("download", "DownloadItemFinished", |
| TRACE_EVENT_SCOPE_THREAD, "auto_opened", |
| auto_opened_ ? "yes" : "no"); |
| break; |
| |
| case INTERRUPTED_INTERNAL: |
| DCHECK(!download_file_) |
| << "Download file must be released prior to interruption."; |
| DCHECK_NE(last_reason_, DOWNLOAD_INTERRUPT_REASON_NONE); |
| TRACE_EVENT_INSTANT2("download", "DownloadItemInterrupted", |
| TRACE_EVENT_SCOPE_THREAD, "interrupt_reason", |
| DownloadInterruptReasonToString(last_reason_), |
| "bytes_so_far", GetReceivedBytes()); |
| break; |
| |
| case RESUMING_INTERNAL: |
| TRACE_EVENT_INSTANT2("download", "DownloadItemResumed", |
| TRACE_EVENT_SCOPE_THREAD, "interrupt_reason", |
| DownloadInterruptReasonToString(last_reason_), |
| "bytes_so_far", GetReceivedBytes()); |
| break; |
| |
| case CANCELLED_INTERNAL: |
| TRACE_EVENT_INSTANT1("download", "DownloadItemCancelled", |
| TRACE_EVENT_SCOPE_THREAD, "bytes_so_far", |
| GetReceivedBytes()); |
| break; |
| |
| case MAX_DOWNLOAD_INTERNAL_STATE: |
| NOTREACHED(); |
| break; |
| } |
| |
| DVLOG(20) << __func__ << "() from:" << DebugDownloadStateString(old_state) |
| << " to:" << DebugDownloadStateString(state_) |
| << " this = " << DebugString(true); |
| bool is_done = |
| (state_ == COMPLETE_INTERNAL || state_ == INTERRUPTED_INTERNAL || |
| state_ == RESUMING_INTERNAL || state_ == CANCELLED_INTERNAL); |
| bool was_done = |
| (old_state == COMPLETE_INTERNAL || old_state == INTERRUPTED_INTERNAL || |
| old_state == RESUMING_INTERNAL || old_state == CANCELLED_INTERNAL); |
| |
| // Termination |
| if (is_done && !was_done) |
| TRACE_EVENT_ASYNC_END0("download", "DownloadItemActive", download_id_); |
| |
| // Resumption |
| if (was_done && !is_done) { |
| std::string file_name(GetTargetFilePath().BaseName().AsUTF8Unsafe()); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN1( |
| "download", "DownloadItemActive", download_id_, "download_item", |
| std::make_unique<DownloadItemActivatedData>( |
| TYPE_ACTIVE_DOWNLOAD, GetId(), GetOriginalUrl().spec(), |
| GetURL().spec(), file_name, GetDangerType(), GetReceivedBytes(), |
| HasUserGesture())); |
| } |
| } |
| |
| void DownloadItemImpl::SetDangerType(DownloadDangerType danger_type) { |
| if (danger_type != danger_type_) { |
| TRACE_EVENT_INSTANT1("download", "DownloadItemSaftyStateUpdated", |
| TRACE_EVENT_SCOPE_THREAD, "danger_type", |
| GetDownloadDangerNames(danger_type).c_str()); |
| } |
| // Only record the Malicious UMA stat if it's going from {not malicious} -> |
| // {malicious}. |
| if ((danger_type_ == DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_WHITELISTED_BY_POLICY) && |
| (danger_type == DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST || |
| danger_type == DOWNLOAD_DANGER_TYPE_DANGEROUS_URL || |
| danger_type == DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT || |
| danger_type == DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED)) { |
| RecordMaliciousDownloadClassified(danger_type); |
| } |
| danger_type_ = danger_type; |
| } |
| |
| void DownloadItemImpl::SetFullPath(const base::FilePath& new_path) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DVLOG(20) << __func__ << "() new_path = \"" << new_path.value() << "\" " |
| << DebugString(true); |
| DCHECK(!new_path.empty()); |
| |
| TRACE_EVENT_INSTANT2("download", "DownloadItemRenamed", |
| TRACE_EVENT_SCOPE_THREAD, "old_filename", |
| destination_info_.current_path.AsUTF8Unsafe(), |
| "new_filename", new_path.AsUTF8Unsafe()); |
| |
| destination_info_.current_path = new_path; |
| } |
| |
| void DownloadItemImpl::AutoResumeIfValid() { |
| DVLOG(20) << __func__ << "() " << DebugString(true); |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| ResumeMode mode = GetResumeMode(); |
| |
| if (mode != ResumeMode::IMMEDIATE_RESTART && |
| mode != ResumeMode::IMMEDIATE_CONTINUE) { |
| return; |
| } |
| |
| auto_resume_count_++; |
| |
| ResumeInterruptedDownload(ResumptionRequestSource::AUTOMATIC); |
| } |
| |
| void DownloadItemImpl::ResumeInterruptedDownload( |
| ResumptionRequestSource source) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| // If we're not interrupted, ignore the request; our caller is drunk. |
| if (state_ != INTERRUPTED_INTERNAL) |
| return; |
| |
| // We are starting a new request. Shake off all pending operations. |
| DCHECK(!download_file_); |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| |
| // Reset the appropriate state if restarting. |
| ResumeMode mode = GetResumeMode(); |
| if (mode == ResumeMode::IMMEDIATE_RESTART || |
| mode == ResumeMode::USER_RESTART) { |
| LOG_IF(ERROR, !GetFullPath().empty()) |
| << "Download full path should be empty before resumption"; |
| if (destination_info_.received_bytes > 0) { |
| if (!HasStrongValidators()) { |
| RecordResumptionRestartCount( |
| ResumptionRestartCountTypes::kMissingStrongValidatorsCount); |
| } |
| RecordResumptionRestartReason(last_reason_); |
| } |
| destination_info_.received_bytes = 0; |
| last_modified_time_.clear(); |
| etag_.clear(); |
| destination_info_.hash.clear(); |
| hash_state_.reset(); |
| received_slices_.clear(); |
| } |
| |
| net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("download_manager_resume", R"( |
| semantics { |
| sender: "Download Manager" |
| description: |
| "When user resumes downloading a file, a network request is made " |
| "to fetch it." |
| trigger: |
| "User resumes a download." |
| data: "None." |
| destination: WEBSITE |
| } |
| policy { |
| cookies_allowed: YES |
| cookies_store: "user" |
| setting: |
| "This feature cannot be disabled in settings, but it is activated " |
| "by direct user action." |
| chrome_policy { |
| DownloadRestrictions { |
| DownloadRestrictions: 3 |
| } |
| } |
| })"); |
| // Avoid using the WebContents even if it's still around. Resumption requests |
| // are consistently routed through the no-renderer code paths so that the |
| // request will not be dropped if the WebContents (and by extension, the |
| // associated renderer) goes away before a response is received. |
| std::unique_ptr<DownloadUrlParameters> download_params( |
| new DownloadUrlParameters(GetURL(), traffic_annotation)); |
| download_params->set_file_path(GetFullPath()); |
| if (received_slices_.size() > 0) { |
| std::vector<DownloadItem::ReceivedSlice> slices_to_download = |
| FindSlicesToDownload(received_slices_); |
| download_params->set_offset(slices_to_download[0].offset); |
| } else { |
| download_params->set_offset(GetReceivedBytes()); |
| } |
| download_params->set_last_modified(GetLastModifiedTime()); |
| download_params->set_etag(GetETag()); |
| download_params->set_hash_of_partial_file(GetHash()); |
| download_params->set_hash_state(std::move(hash_state_)); |
| download_params->set_guid(guid_); |
| if (!HasStrongValidators() && download_params->offset() > 0 && |
| base::FeatureList::IsEnabled( |
| features::kAllowDownloadResumptionWithoutStrongValidators)) { |
| download_params->set_use_if_range(false); |
| download_params->set_file_offset(download_params->offset()); |
| int64_t validation_length = GetDownloadValidationLengthConfig(); |
| download_params->set_offset(download_params->offset() > validation_length |
| ? download_params->offset() - |
| validation_length |
| : 0); |
| } |
| |
| // TODO(xingliu): Read |fetch_error_body| and |request_headers_| from the |
| // cache, and don't copy them into DownloadItemImpl. |
| download_params->set_fetch_error_body(fetch_error_body_); |
| for (const auto& header : request_headers_) { |
| download_params->add_request_header(header.first, header.second); |
| } |
| // The offset is calculated after decompression, so the range request cannot |
| // involve any compression, |
| download_params->add_request_header("Accept-Encoding", "identity"); |
| |
| // Note that resumed downloads disallow redirects. Hence the referrer URL |
| // (which is the contents of the Referer header for the last download request) |
| // will only be sent to the URL returned by GetURL(). |
| download_params->set_referrer(GetReferrerUrl()); |
| download_params->set_referrer_policy(net::URLRequest::NEVER_CLEAR_REFERRER); |
| download_params->set_follow_cross_origin_redirects(false); |
| |
| // If the interruption was caused by content length mismatch, ignore it during |
| // resumption. |
| if (last_reason_ == |
| DOWNLOAD_INTERRUPT_REASON_SERVER_CONTENT_LENGTH_MISMATCH) { |
| download_params->set_ignore_content_length_mismatch(true); |
| } |
| |
| TransitionTo(RESUMING_INTERNAL); |
| RecordDownloadCountWithSource(source == ResumptionRequestSource::USER |
| ? MANUAL_RESUMPTION_COUNT |
| : AUTO_RESUMPTION_COUNT, |
| download_source_); |
| |
| base::TimeDelta time_since_start = base::Time::Now() - GetStartTime(); |
| |
| DownloadUkmHelper::RecordDownloadResumed(ukm_download_id_, GetResumeMode(), |
| time_since_start); |
| RecordDownloadResumed(HasStrongValidators()); |
| |
| delegate_->ResumeInterruptedDownload(std::move(download_params), |
| request_info_.site_url); |
| |
| has_resumed_ = true; |
| if (job_) |
| job_->Resume(false); |
| } |
| |
| // static |
| DownloadItem::DownloadState DownloadItemImpl::InternalToExternalState( |
| DownloadInternalState internal_state) { |
| switch (internal_state) { |
| case INITIAL_INTERNAL: |
| case TARGET_PENDING_INTERNAL: |
| case TARGET_RESOLVED_INTERNAL: |
| case INTERRUPTED_TARGET_PENDING_INTERNAL: |
| // TODO(asanka): Introduce an externally visible state to distinguish |
| // between the above states and IN_PROGRESS_INTERNAL. The latter (the |
| // state where the download is active and has a known target) is the state |
| // that most external users are interested in. |
| case IN_PROGRESS_INTERNAL: |
| return IN_PROGRESS; |
| case COMPLETING_INTERNAL: |
| return IN_PROGRESS; |
| case COMPLETE_INTERNAL: |
| return COMPLETE; |
| case CANCELLED_INTERNAL: |
| return CANCELLED; |
| case INTERRUPTED_INTERNAL: |
| return INTERRUPTED; |
| case RESUMING_INTERNAL: |
| return IN_PROGRESS; |
| case MAX_DOWNLOAD_INTERNAL_STATE: |
| break; |
| } |
| NOTREACHED(); |
| return MAX_DOWNLOAD_STATE; |
| } |
| |
| // static |
| DownloadItemImpl::DownloadInternalState |
| DownloadItemImpl::ExternalToInternalState(DownloadState external_state) { |
| switch (external_state) { |
| case IN_PROGRESS: |
| return IN_PROGRESS_INTERNAL; |
| case COMPLETE: |
| return COMPLETE_INTERNAL; |
| case CANCELLED: |
| return CANCELLED_INTERNAL; |
| case INTERRUPTED: |
| return INTERRUPTED_INTERNAL; |
| default: |
| NOTREACHED(); |
| } |
| return MAX_DOWNLOAD_INTERNAL_STATE; |
| } |
| |
| // static |
| bool DownloadItemImpl::IsValidSavePackageStateTransition( |
| DownloadInternalState from, |
| DownloadInternalState to) { |
| #if DCHECK_IS_ON() |
| switch (from) { |
| case INITIAL_INTERNAL: |
| case TARGET_PENDING_INTERNAL: |
| case INTERRUPTED_TARGET_PENDING_INTERNAL: |
| case TARGET_RESOLVED_INTERNAL: |
| case COMPLETING_INTERNAL: |
| case COMPLETE_INTERNAL: |
| case INTERRUPTED_INTERNAL: |
| case RESUMING_INTERNAL: |
| case CANCELLED_INTERNAL: |
| return false; |
| |
| case IN_PROGRESS_INTERNAL: |
| return to == CANCELLED_INTERNAL || to == COMPLETE_INTERNAL; |
| |
| case MAX_DOWNLOAD_INTERNAL_STATE: |
| NOTREACHED(); |
| } |
| return false; |
| #else |
| return true; |
| #endif |
| } |
| |
| // static |
| bool DownloadItemImpl::IsValidStateTransition(DownloadInternalState from, |
| DownloadInternalState to) { |
| #if DCHECK_IS_ON() |
| switch (from) { |
| case INITIAL_INTERNAL: |
| return to == TARGET_PENDING_INTERNAL || |
| to == INTERRUPTED_TARGET_PENDING_INTERNAL; |
| |
| case TARGET_PENDING_INTERNAL: |
| return to == INTERRUPTED_TARGET_PENDING_INTERNAL || |
| to == TARGET_RESOLVED_INTERNAL || to == CANCELLED_INTERNAL; |
| |
| case INTERRUPTED_TARGET_PENDING_INTERNAL: |
| return to == TARGET_RESOLVED_INTERNAL || to == CANCELLED_INTERNAL; |
| |
| case TARGET_RESOLVED_INTERNAL: |
| return to == IN_PROGRESS_INTERNAL || to == INTERRUPTED_INTERNAL || |
| to == CANCELLED_INTERNAL; |
| |
| case IN_PROGRESS_INTERNAL: |
| return to == COMPLETING_INTERNAL || to == CANCELLED_INTERNAL || |
| to == INTERRUPTED_INTERNAL; |
| |
| case COMPLETING_INTERNAL: |
| return to == COMPLETE_INTERNAL; |
| |
| case COMPLETE_INTERNAL: |
| return false; |
| |
| case INTERRUPTED_INTERNAL: |
| return to == RESUMING_INTERNAL || to == CANCELLED_INTERNAL; |
| |
| case RESUMING_INTERNAL: |
| return to == TARGET_PENDING_INTERNAL || |
| to == INTERRUPTED_TARGET_PENDING_INTERNAL || |
| to == TARGET_RESOLVED_INTERNAL || to == CANCELLED_INTERNAL; |
| |
| case CANCELLED_INTERNAL: |
| return false; |
| |
| case MAX_DOWNLOAD_INTERNAL_STATE: |
| NOTREACHED(); |
| } |
| return false; |
| #else |
| return true; |
| #endif // DCHECK_IS_ON() |
| } |
| |
| const char* DownloadItemImpl::DebugDownloadStateString( |
| DownloadInternalState state) { |
| switch (state) { |
| case INITIAL_INTERNAL: |
| return "INITIAL"; |
| case TARGET_PENDING_INTERNAL: |
| return "TARGET_PENDING"; |
| case INTERRUPTED_TARGET_PENDING_INTERNAL: |
| return "INTERRUPTED_TARGET_PENDING"; |
| case TARGET_RESOLVED_INTERNAL: |
| return "TARGET_RESOLVED"; |
| case IN_PROGRESS_INTERNAL: |
| return "IN_PROGRESS"; |
| case COMPLETING_INTERNAL: |
| return "COMPLETING"; |
| case COMPLETE_INTERNAL: |
| return "COMPLETE"; |
| case CANCELLED_INTERNAL: |
| return "CANCELLED"; |
| case INTERRUPTED_INTERNAL: |
| return "INTERRUPTED"; |
| case RESUMING_INTERNAL: |
| return "RESUMING"; |
| case MAX_DOWNLOAD_INTERNAL_STATE: |
| break; |
| } |
| NOTREACHED() << "Unknown download state " << state; |
| return "unknown"; |
| } |
| |
| const char* DownloadItemImpl::DebugResumeModeString(ResumeMode mode) { |
| switch (mode) { |
| case ResumeMode::INVALID: |
| return "INVALID"; |
| case ResumeMode::IMMEDIATE_CONTINUE: |
| return "IMMEDIATE_CONTINUE"; |
| case ResumeMode::IMMEDIATE_RESTART: |
| return "IMMEDIATE_RESTART"; |
| case ResumeMode::USER_CONTINUE: |
| return "USER_CONTINUE"; |
| case ResumeMode::USER_RESTART: |
| return "USER_RESTART"; |
| } |
| NOTREACHED() << "Unknown resume mode " << static_cast<int>(mode); |
| return "unknown"; |
| } |
| |
| } // namespace download |