// Copyright 2012 The Chromium Authors
// 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 <optional>
#include <utility>
#include <vector>

#include "base/check_is_test.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/string_escape.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/trace_event/memory_usage_estimator.h"
#include "base/trace_event/trace_event.h"
#include "base/uuid.h"
#include "build/build_config.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_item_rename_handler.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/base/network_change_notifier.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/url_request/referrer_policy.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"

#if BUILDFLAG(IS_ANDROID)
#include "components/download/internal/common/android/download_collection_bridge.h"
#endif  // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#include "base/time/time.h"
#endif  // BUILDFLAG(IS_MAC)

namespace download {

namespace {

void DeleteDownloadedFileDone(base::WeakPtr<DownloadItemImpl> item,
                              base::OnceCallback<void(bool)> callback,
                              bool success) {
  if (success && item.get())
    item->OnDownloadedFileRemoved();
  std::move(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_dir;
  if (!base::GetTempDir(&temp_dir)) {
    return base::FilePath();
  }

  base::FilePath temp_file_path;
  base::File temp_file =
      base::CreateAndOpenTemporaryFileInDir(temp_dir, &temp_file_path);
  if (!temp_file.IsValid()) {
    return base::FilePath();
  }

  base::File source_file(download_file->FullPath(),
                         base::File::FLAG_OPEN | base::File::FLAG_READ);
  if (!source_file.IsValid()) {
    DeleteDownloadedFile(temp_file_path);
    return base::FilePath();
  }

  if (!base::CopyFileContents(source_file, temp_file)) {
    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();
  }
}

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_ALLOWLISTED_BY_POLICY:
      return "ALLOWLISTED_BY_POLICY";
    case DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING:
      return "ASYNC_SCANNING";
    case DOWNLOAD_DANGER_TYPE_BLOCKED_PASSWORD_PROTECTED:
      return "BLOCKED_PASSWORD_PROTECTED";
    case DOWNLOAD_DANGER_TYPE_BLOCKED_TOO_LARGE:
      return "BLOCKED_TOO_LARGE";
    case DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING:
      return "SENSITIVE_CONTENT_WARNING";
    case DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK:
      return "SENSITIVE_CONTENT_BLOCK";
    case DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE:
      return "DEEP_SCANNED_SAFE";
    case DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_OPENED_DANGEROUS:
      return "DEEP_SCANNED_OPENED_DANGEROUS";
    case DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING:
      return "PROMPT_FOR_SCANNING";
    case DOWNLOAD_DANGER_TYPE_DANGEROUS_ACCOUNT_COMPROMISE:
      return "DANGEROUS_ACCOUNT_COMPROMISE";
    case DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_FAILED:
      return "DEEP_SCANNED_FAILED";
    case DOWNLOAD_DANGER_TYPE_PROMPT_FOR_LOCAL_PASSWORD_SCANNING:
      return "PROMPT_FOR_LOCAL_PASSWORD_SCANNING";
    case DOWNLOAD_DANGER_TYPE_ASYNC_LOCAL_PASSWORD_SCANNING:
      return "ASYNC_LOCAL_PASSWORD_SCANNING";
    case DOWNLOAD_DANGER_TYPE_BLOCKED_SCAN_FAILED:
      return "BLOCKED_SCAN_FAILED";
    case DOWNLOAD_DANGER_TYPE_FORCE_SAVE_TO_GDRIVE:
      return "FORCE_SAVE_TO_GDRIVE";
    case DOWNLOAD_DANGER_TYPE_FORCE_SAVE_TO_ONEDRIVE:
      return "FORCE_SAVE_TO_ONEDRIVE";
    case DOWNLOAD_DANGER_TYPE_MAX:
      NOTREACHED();
  }
}

class DownloadItemActivatedData
    : public base::trace_event::ConvertableToTraceFormat {
 public:
  DownloadItemActivatedData(DownloadItem::DownloadCreationType download_type,
                            uint32_t download_id,
                            const GURL& original_url,
                            const GURL& 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(const DownloadItemActivatedData&) = delete;
  DownloadItemActivatedData& operator=(const DownloadItemActivatedData&) =
      delete;

  ~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_.is_valid() ? original_url_.spec() : "",
                           true, out);
    out->append(",");
    out->append("\"final_url\":");
    base::EscapeJSONString(final_url_.is_valid() ? final_url_.spec() : "", 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\"",
                                   base::ToString(has_user_gesture_).c_str()));
    out->append("}");
  }

 private:
  DownloadItem::DownloadCreationType download_type_;
  uint32_t download_id_;
  GURL original_url_;
  GURL final_url_;
  std::string file_name_;
  DownloadDangerType danger_type_;
  int64_t start_offset_;
  bool has_user_gesture_;
};

}  // 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 std::string& serialized_embedder_download_data,
    const GURL& tab_url,
    const GURL& tab_referrer_url,
    const std::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,
    ::network::mojom::CredentialsMode credentials_mode,
    const std::optional<net::IsolationInfo>& isolation_info,
    int64_t range_request_from,
    int64_t range_request_to)
    : url_chain(url_chain),
      referrer_url(referrer_url),
      serialized_embedder_download_data(serialized_embedder_download_data),
      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),
      credentials_mode(credentials_mode),
      isolation_info(isolation_info),
      range_request_from(range_request_from),
      range_request_to(range_request_to) {}

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 std::string& serialized_embedder_download_data,
    const GURL& tab_url,
    const GURL& tab_refererr_url,
    const std::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,
    int64_t range_request_from,
    int64_t range_request_to,
    std::unique_ptr<DownloadEntry> download_entry)
    : request_info_(url_chain,
                    referrer_url,
                    serialized_embedder_download_data,
                    tab_url,
                    tab_refererr_url,
                    request_initiator,
                    std::string(),
                    base::FilePath(),
                    ui::PAGE_TRANSITION_LINK,
                    false,
                    std::string(),
                    start_time,
                    ::network::mojom::CredentialsMode::kInclude,
                    std::nullopt,
                    range_request_from,
                    range_request_to),
      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::Uuid::ParseCaseInsensitive(guid_).is_valid());

  if (download_entry) {
    download_source_ = download_entry->download_source;
    fetch_error_body_ = download_entry->fetch_error_body;
    request_headers_ = download_entry->request_headers;
    ukm_download_id_ = download_entry->ukm_download_id;
    bytes_wasted_ = download_entry->bytes_wasted;
    request_info_.fetched_via_service_worker =
        download_entry->fetched_via_service_worker;
  } else {
    ukm_download_id_ = ukm::NoURLSourceId();
  }
  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.serialized_embedder_download_data,
                    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,
                    info.credentials_mode,
                    info.isolation_info,
                    info.save_info->range_request_from,
                    info.save_info->range_request_to),
      guid_(info.guid.empty()
                ? base::Uuid::GenerateRandomV4().AsLowercaseString()
                : 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),
      require_safety_checks_(info.require_safety_checks),
      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)
#if BUILDFLAG(IS_ANDROID)
      ,
      allow_auto_open_after_completion_(info.allow_auto_open_after_completion)
#endif  // BUILDFLAG(IS_ANDROID)
{
  request_info_.fetched_via_service_worker = info.fetched_via_service_worker;
  delegate_->Attach();
  Init(true /* actively downloading */, TYPE_ACTIVE_DOWNLOAD);
  allow_metered_ |= delegate_->IsActiveNetworkMetered();

  TRACE_EVENT_INSTANT("download", "DownloadStarted");
}

// Constructing for the "Save Page As..." feature:
DownloadItemImpl::DownloadItemImpl(
    DownloadItemImplDelegate* delegate,
    uint32_t download_id,
    const base::FilePath& path,
    const base::FilePath& display_name,
    const GURL& url,
    const std::string& mime_type,
    DownloadJob::CancelRequestCallback cancel_request_callback)
    : request_info_(url),
      guid_(base::Uuid::GenerateRandomV4().AsLowercaseString()),
      download_id_(download_id),
      display_name_(display_name),
      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(cancel_request_callback), DownloadCreateInfo(), true,
      URLLoaderFactoryProvider::GetNullPtr(),
      /*wake_lock_provider_binder*/ base::NullCallback());
  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_INSTANT("download", "DownloadItemSaftyStateUpdated",
                      "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::ValidateInsecureDownload() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(!IsDone());
  DCHECK(IsInsecure());

  DVLOG(20) << __func__ << "() download=" << DebugString(true);

  insecure_download_status_ = InsecureDownloadStatus::VALIDATED;

  UpdateObservers();  // TODO(asanka): This is potentially unsafe. The download
                      // may not be in a consistent state or around at all after
                      // invoking observers, but we keep it here because it is
                      // used in ValidateDangerousDownload(), too.
                      // http://crbug.com/586610

  MaybeCompleteDownload();
}

void DownloadItemImpl::ConfirmNonDangerousDownload() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(!IsDone());
  DCHECK(!IsDangerous());

  DVLOG(20) << __func__ << "() download=" << DebugString(true);

  if (IsDone() || IsDangerous()) {
    return;
  }

  is_user_confirmed_ = true;

  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::CopyDownload(AcquireFileCallback callback) {
  DVLOG(20) << __func__ << "() download = " << DebugString(true);
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(AllDataSaved());

  if (download_file_) {
    GetDownloadTaskRunner()->PostTaskAndReplyWithResult(
        FROM_HERE,
        base::BindOnce(&MakeCopyOfDownloadFile, download_file_.get()),
        std::move(callback));
  } else {
    std::move(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) {
        UpdateObservers();
        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_;
  if (user_resume)
    auto_resume_count_ = 0;
}

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_);

  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);
  opened_ = true;
  last_access_time_ = base::Time::Now();
  for (auto& observer : observers_)
    observer.OnDownloadOpened(this);

#if BUILDFLAG(IS_WIN)
  // On Windows, don't actually open the file if it has no extension, to prevent
  // Windows from interpreting it as the command for an executable of the same
  // name.
  if (destination_info_.current_path.Extension().empty()) {
    delegate_->ShowDownloadInShell(this);
    return;
  }
#endif
  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& display_name,
    DownloadRenameResult result) {
  if (result == DownloadRenameResult::SUCCESS) {
    bool is_content_uri = false;
#if BUILDFLAG(IS_ANDROID)
    is_content_uri = GetFullPath().IsContentUri();
#endif  // BUILDFLAG(IS_ANDROID)
    if (is_content_uri) {
      SetDisplayName(display_name);
    } else {
      auto new_full_path =
          base::FilePath(GetFullPath().DirName()).Append(display_name);
      destination_info_.target_path = new_full_path;
      destination_info_.current_path = new_full_path;
    }
    UpdateObservers();
  }
  std::move(callback).Run(result);
}

void DownloadItemImpl::Rename(const base::FilePath& display_name,
                              DownloadItem::RenameDownloadCallback callback) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  if (display_name.IsAbsolute()) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&DownloadItemImpl::RenameDownloadedFileDone,
                                  weak_ptr_factory_.GetWeakPtr(),
                                  std::move(callback), GetFullPath(),
                                  DownloadRenameResult::FAILURE_NAME_INVALID));
    return;
  }

  GetDownloadTaskRunner()->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&download::RenameDownloadedFile, GetFullPath(),
                     display_name),
      base::BindOnce(&DownloadItemImpl::RenameDownloadedFileDone,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                     display_name));
}

uint32_t DownloadItemImpl::GetId() const {
  return download_id_;
}

const std::string& DownloadItemImpl::GetGuid() const {
  return guid_;
}

DownloadItem::DownloadState DownloadItemImpl::GetState() const {
  return InternalToExternalState(state_);
}

void DownloadItemImpl::SetStateForTesting(DownloadItem::DownloadState state) {
  CHECK_IS_TEST();
  state_ = ExternalToInternalState(state);
}

void DownloadItemImpl::SetDownloadUrlForTesting(const GURL& url) {
  request_info_.url_chain.push_back(url);
}

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();
  }
}

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;
}

bool DownloadItemImpl::IsUrlTruncated() const {
  return url_truncated_;
}

void DownloadItemImpl::SetURLLoaderFactory(
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
  url_loader_factory_ = std::move(url_loader_factory);
}

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 std::string& DownloadItemImpl::GetSerializedEmbedderDownloadData() const {
  return request_info_.serialized_embedder_download_data;
}

const GURL& DownloadItemImpl::GetTabUrl() const {
  if (IsSavePackageDownload()) {
    return GetURL();
  }
  return request_info_.tab_url;
}

const GURL& DownloadItemImpl::GetTabReferrerUrl() const {
  return request_info_.tab_referrer_url;
}

const std::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();
}

DownloadSource DownloadItemImpl::GetDownloadSource() const {
  return download_source_;
}

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 ||
      state_ == 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(base::OnceCallback<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::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&DeleteDownloadedFileDone,
                                  base::WeakPtr<DownloadItemImpl>(),
                                  std::move(callback), false));
    return;
  }
  if (GetFullPath().empty() || file_externally_removed_) {
    // Pass a null WeakPtr so it doesn't call OnDownloadedFileRemoved.
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&DeleteDownloadedFileDone,
                                  base::WeakPtr<DownloadItemImpl>(),
                                  std::move(callback), true));
    return;
  }
  GetDownloadTaskRunner()->PostTaskAndReplyWithResult(
      FROM_HERE, base::BindOnce(&DeleteDownloadedFile, GetFullPath()),
      base::BindOnce(&DeleteDownloadedFileDone, weak_ptr_factory_.GetWeakPtr(),
                     std::move(callback)));
}

DownloadFile* DownloadItemImpl::GetDownloadFile() {
  return download_file_.get();
}

DownloadItemRenameHandler* DownloadItemImpl::GetRenameHandler() {
  if (!rename_handler_ && delegate_) {
    rename_handler_ = delegate_->GetRenameHandlerForDownload(this);
  }
  return rename_handler_.get();
}

#if BUILDFLAG(IS_ANDROID)
bool DownloadItemImpl::IsFromExternalApp() {
  return is_from_external_app_;
}

bool DownloadItemImpl::AllowAutoOpenAfterCompletion() {
  return allow_auto_open_after_completion_;
}
#endif  // BUILDFLAG(IS_ANDROID)

bool DownloadItemImpl::IsUserConfirmed() const {
  return is_user_confirmed_;
}

bool DownloadItemImpl::IsDangerous() const {
  switch (danger_type_) {
    case DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
    case DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
    case DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
    case DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
    case DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
    case DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
    case DOWNLOAD_DANGER_TYPE_BLOCKED_PASSWORD_PROTECTED:
    case DOWNLOAD_DANGER_TYPE_BLOCKED_TOO_LARGE:
    case DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING:
    case DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK:
    case DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_OPENED_DANGEROUS:
    case DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING:
    case DOWNLOAD_DANGER_TYPE_DANGEROUS_ACCOUNT_COMPROMISE:
    case DOWNLOAD_DANGER_TYPE_PROMPT_FOR_LOCAL_PASSWORD_SCANNING:
    case DOWNLOAD_DANGER_TYPE_BLOCKED_SCAN_FAILED:
    case DOWNLOAD_DANGER_TYPE_FORCE_SAVE_TO_GDRIVE:
    case DOWNLOAD_DANGER_TYPE_FORCE_SAVE_TO_ONEDRIVE:
      return true;
    case DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
    case DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
    case DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
    case DOWNLOAD_DANGER_TYPE_ALLOWLISTED_BY_POLICY:
    case DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING:
    case DOWNLOAD_DANGER_TYPE_ASYNC_LOCAL_PASSWORD_SCANNING:
    case DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE:
    case DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_FAILED:
      return false;
    case DOWNLOAD_DANGER_TYPE_MAX:
      NOTREACHED();
  }
}

bool DownloadItemImpl::IsInsecure() const {
  return insecure_download_status_ == InsecureDownloadStatus::WARN ||
         insecure_download_status_ == InsecureDownloadStatus::BLOCK ||
         insecure_download_status_ == InsecureDownloadStatus::SILENT_BLOCK;
}

DownloadDangerType DownloadItemImpl::GetDangerType() const {
  return danger_type_;
}

DownloadItem::InsecureDownloadStatus
DownloadItemImpl::GetInsecureDownloadStatus() const {
  return insecure_download_status_;
}

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::Seconds((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_;
}

int64_t DownloadItemImpl::GetUploadedBytes() const {
  return destination_info_.uploaded_bytes;
}

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_->ShouldAutomaticallyOpenFile(GetURL(), GetTargetFilePath());
}

bool DownloadItemImpl::ShouldOpenFileByPolicyBasedOnExtension() {
  return delegate_->ShouldAutomaticallyOpenFileByPolicy(GetURL(),
                                                        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::RequireSafetyChecks() const {
  return require_safety_checks_;
}

bool DownloadItemImpl::IsParallelDownload() const {
  return job_ && job_->IsParallelizable() &&
         download::IsParallelDownloadEnabled();
}

DownloadItem::DownloadCreationType DownloadItemImpl::GetDownloadCreationType()
    const {
  return download_type_;
}

::network::mojom::CredentialsMode DownloadItemImpl::GetCredentialsMode() const {
  return request_info_.credentials_mode;
}

const std::optional<net::IsolationInfo>& DownloadItemImpl::GetIsolationInfo()
    const {
  return request_info_.isolation_info;
}

void DownloadItemImpl::OnContentCheckCompleted(DownloadDangerType danger_type,
                                               DownloadInterruptReason reason) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(AllDataSaved() || IsSavePackageDownload());

  // 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::OnAsyncScanningCompleted(
    DownloadDangerType danger_type) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(AllDataSaved() || IsSavePackageDownload());

  DVLOG(20) << __func__ << "() danger_type=" << danger_type
            << " download=" << DebugString(true);
  SetDangerType(danger_type);
  UpdateObservers();
}

void DownloadItemImpl::SetOpenWhenComplete(bool open) {
  if (open_when_complete_ != open) {
    open_when_complete_ = open;
    UpdateObservers();
  }
}

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
        "\"\n\t"
        " referrer = \"%s\""
        " serialized_embedder_download_data = \"%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(),
        base::ToString(download_file_).c_str(), url_list.c_str(),
        GetFullPath().value().c_str(), GetTargetFilePath().value().c_str(),
        GetReferrerUrl().spec().c_str(),
        GetSerializedEmbedderDownloadData().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::BindWakeLockProvider(
    mojo::PendingReceiver<device::mojom::WakeLockProvider> receiver) {
  if (delegate_)
    delegate_->BindWakeLockProvider(std::move(receiver));
}

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) {
    received_slices_.clear();
    destination_info_.received_bytes = 0;
  }

  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();
#if BUILDFLAG(IS_ANDROID)
  if (GetTargetFilePath().IsContentUri()) {
    GetDownloadTaskRunner()->PostTask(
        FROM_HERE,
        base::BindOnce(
            base::IgnoreResult(&DownloadCollectionBridge::PublishDownload),
            GetTargetFilePath()));
  }
#endif
  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_INSTANT("download", "DownloadItemUpdated", "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(), GetURL(), file_name,
      GetDangerType(), GetReceivedBytes(), HasUserGesture());

  if (active) {
    TRACE_EVENT_BEGIN("download", "DownloadItemActive",
                      perfetto::NamedTrack("DownloadItemActive", download_id_),
                      "download_item", std::move(active_data));
    ukm_download_id_ = ukm::NoURLSourceId();
  } else {
    TRACE_EVENT_INSTANT("download", "DownloadItemActive", "download_item",
                        std::move(active_data));
  }

  DVLOG(20) << __func__ << "() " << DebugString(true);
}

// We're starting the download.
void DownloadItemImpl::Start(
    std::unique_ptr<DownloadFile> file,
    DownloadJob::CancelRequestCallback cancel_request_callback,
    const DownloadCreateInfo& new_create_info,
    URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
        url_loader_factory_provider) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  CHECK(!download_file_) << "last interrupt reason: "
                         << DownloadInterruptReasonToString(last_reason_)
                         << ", state: " << DebugDownloadStateString(state_);
  DVLOG(20) << __func__ << "() this=" << DebugString(true);
  RecordDownloadCountWithSource(START_COUNT, download_source_);

  download_file_ = std::move(file);
  job_ = DownloadJobFactory::CreateJob(
      this, std::move(cancel_request_callback), new_create_info, false,
      std::move(url_loader_factory_provider),
      base::BindRepeating(&DownloadItemImpl::BindWakeLockProvider,
                          weak_ptr_factory_.GetWeakPtr()));
  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);
    // We're posting the call to DetermineDownloadTarget() instead of calling it
    // directly to ensure that OnDownloadTargetDetermined() is not called
    // synchronously. See crbug.com/1209856 for more details.
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(&DownloadItemImpl::DetermineDownloadTarget,
                                  weak_ptr_factory_.GetWeakPtr()));
    return;
  }

  if (state_ == INITIAL_INTERNAL) {
    RecordNewDownloadStarted(net::NetworkChangeNotifier::GetConnectionType(),
                             download_source_);
    if (job_->IsParallelizable()) {
      RecordParallelizableDownloadCount(NEW_DOWNLOAD_COUNT,
                                        IsParallelDownloadEnabled());
    }
    RecordDownloadMimeType(mime_type_, transient_);

    if (!delegate_->IsOffTheRecord()) {
      RecordDownloadCountWithSource(NEW_DOWNLOAD_COUNT_NORMAL_PROFILE,
                                    download_source_);
      RecordDownloadMimeTypeForNormalProfile(mime_type_, transient_);
    }
  }

  // Successful download start.
  DCHECK(download_file_);
  DCHECK(job_);

  if (state_ == RESUMING_INTERNAL) {
    if (total_bytes_ == 0 && new_create_info.total_bytes > 0)
      total_bytes_ = new_create_info.total_bytes;
    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::BindRepeating(&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::BindOnce(&DownloadItemImpl::OnDownloadTargetDetermined,
                           weak_ptr_factory_.GetWeakPtr()));
}

// Called by delegate_ when the download target path has been determined.
void DownloadItemImpl::OnDownloadTargetDetermined(
    DownloadTargetInfo target_info) {
  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_info.target_path.value()
            << " intermediate_path:" << target_info.intermediate_path.value()
            << " disposition:" << target_info.target_disposition
            << " danger_type:" << target_info.danger_type
            << " interrupt_reason:"
            << DownloadInterruptReasonToString(target_info.interrupt_reason)
            << " this:" << DebugString(true);

  RecordDownloadCountWithSource(DOWNLOAD_TARGET_DETERMINED_COUNT,
                                download_source_);

  if (IsCancellation(target_info.interrupt_reason) ||
      target_info.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 &&
      target_info.interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
    deferred_interrupt_reason_ = target_info.interrupt_reason;
    insecure_download_status_ = target_info.insecure_download_status;
    TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
    OnTargetResolved();
    return;
  }

  destination_info_.target_path = target_info.target_path;
  destination_info_.target_disposition = target_info.target_disposition;
  SetDangerType(target_info.danger_type);
  insecure_download_status_ = target_info.insecure_download_status;
  if (!target_info.display_name.empty()) {
    SetDisplayName(target_info.display_name);
  }
  if (!target_info.mime_type.empty()) {
    mime_type_ = target_info.mime_type;
  }
#if BUILDFLAG(IS_MAC)
  file_tags_ = target_info.file_tags;
#endif

  // 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(target_info.intermediate_path.DirName() ==
         target_info.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 (target_info.intermediate_path == GetFullPath()) {
    OnDownloadRenamedToIntermediateName(DOWNLOAD_INTERRUPT_REASON_NONE,
                                        target_info.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());

  GetDownloadTaskRunner()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &DownloadFile::RenameAndUniquify,
          // Safe because we control download file lifetime.
          base::Unretained(download_file_.get()), target_info.intermediate_path,
          base::BindOnce(&DownloadItemImpl::OnDownloadRenamedToIntermediateName,
                         weak_ptr_factory_.GetWeakPtr())));
}

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);
  } 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_);

  if (!IsDownloadReadyForCompletion(
          base::BindRepeating(&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 the annotation.
  DownloadFile::RenameCompletionCallback rename_callback =
      base::BindOnce(&DownloadItemImpl::OnRenameAndAnnotateDone,
                     weak_ptr_factory_.GetWeakPtr());

#if BUILDFLAG(IS_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(rename_callback)));
    return;
  }
#endif  // BUILDFLAG(IS_ANDROID)

  mojo::PendingRemote<quarantine::mojom::Quarantine> quarantine;
  auto quarantine_callback = delegate_->GetQuarantineConnectionCallback();
  if (quarantine_callback)
    quarantine_callback.Run(quarantine.InitWithNewPipeAndPassReceiver());

  GetDownloadTaskRunner()->PostTask(
      FROM_HERE,
      base::BindOnce(
          &DownloadFile::RenameAndAnnotate,
          base::Unretained(download_file_.get()), GetTargetFilePath(),
          delegate_->GetApplicationClientIdForFileScanning(),
          delegate_->IsOffTheRecord() ? GURL() : GetURL(),
          delegate_->IsOffTheRecord() ? GURL() : GetReferrerUrl(),
          delegate_->IsOffTheRecord() ? std::nullopt : GetRequestInitiator(),
          std::move(quarantine), std::move(rename_callback)));
}

void DownloadItemImpl::OnRenameAndAnnotateDone(
    DownloadInterruptReason reason,
    const base::FilePath& full_path) {
  DownloadFile::RenameCompletionCallback rename_callback =
      base::BindOnce(&DownloadItemImpl::OnDownloadRenamedToFinalName,
                     weak_ptr_factory_.GetWeakPtr());
  if (auto* rename_handler = GetRenameHandler()) {
    renaming_ = true;

    rename_handler->Start(
        base::BindRepeating(&DownloadItemImpl::UpdateRenameProgress,
                            weak_ptr_factory_.GetWeakPtr()),
        std::move(rename_callback));
    return;
  }

  OnDownloadRenamedToFinalName(reason, full_path);
}

void DownloadItemImpl::OnDownloadRenamedToFinalName(
    DownloadInterruptReason reason,
    const base::FilePath& full_path) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(!IsSavePackageDownload());

  renaming_ = false;

  // 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;
  }

  if (full_path != GetFullPath()) {
    // full_path is now the current and target file path.
    DCHECK(!full_path.empty());
    destination_info_.target_path = full_path;
    SetFullPath(full_path);
  }

#if BUILDFLAG(IS_MAC)
  base::mac::SetFileTags(full_path, file_tags_);
#endif

  // 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::BindOnce(&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,
                          net::NetworkChangeNotifier::GetConnectionType(),
                          download_source_);
  if (!delegate_->IsOffTheRecord()) {
    RecordDownloadCountWithSource(COMPLETED_COUNT_NORMAL_PROFILE,
                                  download_source_);
  }
  if (is_parallelizable) {
    RecordParallelizableDownloadCount(COMPLETED_COUNT,
                                      IsParallelDownloadEnabled());
  }

  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();

    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_);

  base::TimeDelta time_since_start = base::Time::Now() - GetStartTime();
  int resulting_file_size = GetReceivedBytes();
  std::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());
  destination_info_.hash.resize(clone_of_hash_state->GetHashLength());
  clone_of_hash_state->Finish(
      base::as_writable_byte_span(destination_info_.hash));
}

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(
    base::OnceClosure 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;

  // If the download is insecure, but not yet validated, it's not ready for
  // completion.
  if (IsInsecure())
    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,
                                         std::move(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;

  if (IsDownloadDone(GetURL(), InternalToExternalState(new_state),
                     last_reason_)) {
    url_truncated_ |= TruncateDataUrlAtTheEndIfNeeded(&request_info_.url_chain);
  }
  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();

    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_INSTANT("download", "DownloadItemCompleting", "bytes_so_far",
                          GetReceivedBytes(), "final_hash",
                          destination_info_.hash);
      break;

    case COMPLETE_INTERNAL:
      TRACE_EVENT_INSTANT("download", "DownloadItemFinished", "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_INSTANT("download", "DownloadItemInterrupted",
                          "interrupt_reason",
                          DownloadInterruptReasonToString(last_reason_),
                          "bytes_so_far", GetReceivedBytes());
      break;

    case RESUMING_INTERNAL:
      TRACE_EVENT_INSTANT("download", "DownloadItemResumed", "interrupt_reason",
                          DownloadInterruptReasonToString(last_reason_),
                          "bytes_so_far", GetReceivedBytes());
      break;

    case CANCELLED_INTERNAL:
      TRACE_EVENT_INSTANT("download", "DownloadItemCancelled", "bytes_so_far",
                          GetReceivedBytes());
      break;

    case MAX_DOWNLOAD_INTERNAL_STATE:
      NOTREACHED();
  }

  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_END("download",
                    perfetto::NamedTrack("DownloadItemActive", download_id_));

  // Resumption
  if (was_done && !is_done) {
    std::string file_name(GetTargetFilePath().BaseName().AsUTF8Unsafe());
    TRACE_EVENT_BEGIN(
        "download", "DownloadItemActive",
        perfetto::NamedTrack("DownloadItemActive", download_id_),
        "download_item",
        std::make_unique<DownloadItemActivatedData>(
            TYPE_ACTIVE_DOWNLOAD, GetId(), GetOriginalUrl(), GetURL(),
            file_name, GetDangerType(), GetReceivedBytes(), HasUserGesture()));
  }
}

void DownloadItemImpl::SetDangerType(DownloadDangerType danger_type) {
  if (danger_type != danger_type_) {
    TRACE_EVENT_INSTANT("download", "DownloadItemSaftyStateUpdated",
                        "danger_type",
                        GetDownloadDangerNames(danger_type).c_str());
  }
  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_INSTANT("download", "DownloadItemRenamed", "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.
  CHECK(!download_file_);
  weak_ptr_factory_.InvalidateWeakPtrs();

  // Reset the appropriate state if restarting.
  ResumeMode mode = GetResumeMode();
  if (request_info_.fetched_via_service_worker) {
    // SW responses can't be range-served, so resumption must restart from
    // offset 0 and re-dispatch the fetch event.
    mode = (source == ResumptionRequestSource::USER)
               ? ResumeMode::USER_RESTART
               : ResumeMode::IMMEDIATE_RESTART;
  }
  if (mode == ResumeMode::IMMEDIATE_RESTART ||
      mode == ResumeMode::USER_RESTART) {
    LOG_IF(ERROR, !GetFullPath().empty())
        << "Download full path should be empty before resumption";
    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));
  // A resume of a network-fetched download must not be routed through the
  // SW interceptor: the SW response is a single full body that would be
  // appended at the resume offset (or restart from network mismatched).
  // A restart of a SW-fetched download keeps SW interception so the fetch
  // event runs again from offset 0.
  download_params->set_skip_service_worker_interception(
      !request_info_.fetched_via_service_worker);
  download_params->set_file_path(GetFullPath());
  int64_t offset = 0;
  if (received_slices_.size() > 0) {
    std::vector<DownloadItem::ReceivedSlice> slices_to_download =
        FindSlicesToDownload(received_slices_);
    offset = slices_to_download[0].offset;
  } else {
    offset = GetReceivedBytes();
  }

  // The previous hash state covers GetReceivedBytes() bytes of the partial
  // file. If the resume offset is smaller (i.e. there were non-contiguous
  // received slices), it no longer matches the prefix up to |offset| and must
  // be dropped so that BaseFile re-derives it from the partial file.
  if (offset != GetReceivedBytes()) {
    hash_state_.reset();
    destination_info_.hash.clear();
  }

  download_params->set_offset(offset);
  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 (url_loader_factory_) {
    download_params->set_url_loader_factory(url_loader_factory_->Clone());
  }
  if (!HasStrongValidators() &&
      base::FeatureList::IsEnabled(
          features::kAllowDownloadResumptionWithoutStrongValidators)) {
    int64_t validation_length = GetDownloadValidationLengthConfig();
    if (download_params->offset() > validation_length) {
      // There is enough data for validation, set the file_offset so
      // DownloadFileImpl will validate the data between offset to
      // file_offset.
      download_params->set_use_if_range(false);
      download_params->set_file_offset(download_params->offset());
      download_params->set_offset(download_params->offset() -
                                  validation_length);
    } else {
      // There is not enough data for validation, simply overwrites the
      // existing data from the beginning.
      download_params->set_offset(0);
      download_params->set_hash_state(nullptr);
    }
  }

  // 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);
  }

  if (request_info_.range_request_from != kInvalidRange ||
      request_info_.range_request_to != kInvalidRange) {
    // Arbitrary range request doesn't use If-Range header and resumption
    // invalidation.
    download_params->set_range_request_offset(request_info_.range_request_from,
                                              request_info_.range_request_to);
    download_params->set_use_if_range(false);
    download_params->set_offset(offset);
    download_params->set_file_offset(offset);
  }

  // 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::ReferrerPolicy::NEVER_CLEAR);
  download_params->set_cross_origin_redirects(
      network::mojom::RedirectMode::kError);

  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);

  delegate_->ResumeInterruptedDownload(
      std::move(download_params),
      request_info_.serialized_embedder_download_data);

  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();
}

// 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();
  }
}

// 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 ||
             to == INTERRUPTED_INTERNAL;

    case MAX_DOWNLOAD_INTERNAL_STATE:
      NOTREACHED();
  }
#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();
  }
#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;
}

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);
}

std::pair<int64_t, int64_t> DownloadItemImpl::GetRangeRequestOffset() const {
  return std::make_pair(request_info_.range_request_from,
                        request_info_.range_request_to);
}

void DownloadItemImpl::UpdateRenameProgress(int64_t bytes_so_far,
                                            int64_t bytes_per_sec) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(renaming_);

  destination_info_.uploaded_bytes = bytes_so_far;

  UpdateObservers();
}

}  // namespace download
