// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/download/download_target_determiner.h"

#include <optional>
#include <string>
#include <vector>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/download/chrome_download_manager_delegate.h"
#include "chrome/browser/download/download_confirmation_reason.h"
#include "chrome/browser/download/download_crx_util.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/download/download_stats.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/safe_browsing_metrics_collector_factory.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/download/public/common/download_interrupt_reasons.h"
#include "components/download/public/common/download_item.h"
#include "components/download/public/common/download_target_info.h"
#include "components/history/core/browser/history_service.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/buildflags.h"
#include "components/safe_browsing/core/browser/safe_browsing_metrics_collector.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_item_utils.h"
#include "content/public/common/buildflags.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/constants.h"
#include "net/base/filename_util.h"
#include "net/http/http_content_disposition.h"
#include "third_party/blink/public/common/mime_util/mime_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/origin.h"

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#include "chrome/browser/extensions/webstore_installer.h"
#include "extensions/common/feature_switch.h"
#endif

#if BUILDFLAG(ENABLE_PLUGINS)
#include "chrome/browser/plugins/plugin_prefs.h"
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/webplugininfo.h"
#endif

#if BUILDFLAG(IS_WIN)
#include "ui/shell_dialogs/select_file_utils_win.h"
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/policy/dlp/dlp_files_controller_ash.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_file_destination.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h"
#include "components/download/public/common/base_file.h"
#endif

#if BUILDFLAG(IS_ANDROID)
#include "components/download/public/common/download_file.h"
#include "components/safe_browsing/android/safe_browsing_api_handler_bridge.h"
#endif

#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
#include "components/safe_browsing/content/browser/download/download_stats.h"
#include "components/safe_browsing/content/common/file_type_policies.h"
#endif

using content::BrowserThread;
using download::DownloadItem;
using download::DownloadPathReservationTracker;
using safe_browsing::DownloadFileType;

namespace {

const base::FilePath::CharType kCrdownloadSuffix[] =
    FILE_PATH_LITERAL(".crdownload");

// Condenses the results from HistoryService::GetVisibleVisitCountToHost() to a
// single bool. A host is considered visited before if prior visible visits were
// found in history and the first such visit was earlier than the most recent
// midnight.
void VisitCountsToVisitedBefore(base::OnceCallback<void(bool)> callback,
                                history::VisibleVisitCountToHostResult result) {
  std::move(callback).Run(
      result.success && result.count > 0 &&
      (result.first_visit.LocalMidnight() < base::Time::Now().LocalMidnight()));
}

// For the `new_path`, generates a new safe file name if needed. Keep its
// extension if it is empty or matches that of the `old_extension`. Otherwise,
// suggest a new safe extension.
void GenerateSafeFileName(base::FilePath* new_path,
                          const base::FilePath::StringType& old_extension,
                          const std::string& mime_type) {
  DCHECK(new_path);
  if (new_path->Extension().empty() || new_path->Extension() == old_extension) {
    net::GenerateSafeFileName(std::string() /*mime_type*/,
                              false /*ignore_extension*/, new_path);
  } else {
    net::GenerateSafeFileName(mime_type, true /*ignore_extension*/, new_path);
  }
}

}  // namespace

DownloadTargetDeterminerDelegate::~DownloadTargetDeterminerDelegate() = default;

DownloadTargetDeterminer::DownloadTargetDeterminer(
    DownloadItem* download,
    const base::FilePath& initial_virtual_path,
    DownloadPathReservationTracker::FilenameConflictAction conflict_action,
    DownloadPrefs* download_prefs,
    DownloadTargetDeterminerDelegate* delegate,
    CompletionCallback callback)
    : next_state_(STATE_GENERATE_TARGET_PATH),
      confirmation_reason_(DownloadConfirmationReason::NONE),
      should_notify_extensions_(false),
      create_target_directory_(false),
      conflict_action_(conflict_action),
      danger_type_(download->GetDangerType()),
      danger_level_(DownloadFileType::NOT_DANGEROUS),
      virtual_path_(initial_virtual_path),
      is_filetype_handled_safely_(false),
#if BUILDFLAG(IS_ANDROID)
      is_checking_dialog_confirmed_path_(false),
#endif
      download_(download),
      is_resumption_(download_->GetLastReason() !=
                         download::DOWNLOAD_INTERRUPT_REASON_NONE &&
                     !initial_virtual_path.empty()),
      download_prefs_(download_prefs),
      delegate_(delegate),
      completion_callback_(std::move(callback)) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(download_);
  DCHECK(delegate);
  download_->AddObserver(this);

  DoLoop();
}

DownloadTargetDeterminer::~DownloadTargetDeterminer() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(download_);
  DCHECK(!completion_callback_);
  download_->RemoveObserver(this);
}

void DownloadTargetDeterminer::DoLoop() {
  Result result = CONTINUE;
  do {
    State current_state = next_state_;
    next_state_ = STATE_NONE;

    switch (current_state) {
      case STATE_GENERATE_TARGET_PATH:
        result = DoGenerateTargetPath();
        break;
      case STATE_SET_INSECURE_DOWNLOAD_STATUS:
        result = DoSetInsecureDownloadStatus();
        break;
      case STATE_NOTIFY_EXTENSIONS:
        result = DoNotifyExtensions();
        break;
      case STATE_RESERVE_VIRTUAL_PATH:
        result = DoReserveVirtualPath();
        break;
      case STATE_PROMPT_USER_FOR_DOWNLOAD_PATH:
        result = DoRequestConfirmation();
        break;
      case STATE_DETERMINE_LOCAL_PATH:
        result = DoDetermineLocalPath();
        break;
      case STATE_DETERMINE_MIME_TYPE:
        result = DoDetermineMimeType();
        break;
      case STATE_CHECK_DOWNLOAD_URL:
        result = DoCheckDownloadUrl();
        break;
#if BUILDFLAG(IS_ANDROID)
      case STATE_CHECK_APP_VERIFICATION:
        result = DoCheckAppVerification();
        break;
#endif
      case STATE_CHECK_VISITED_REFERRER_BEFORE:
        result = DoCheckVisitedReferrerBefore();
        break;
      case STATE_DETERMINE_INTERMEDIATE_PATH:
        result = DoDetermineIntermediatePath();
        break;
      case STATE_NONE:
        NOTREACHED();
    }
  } while (result == CONTINUE);
  // Note that if a callback completes synchronously, the handler will still
  // return QUIT_DOLOOP. In this case, an inner DoLoop() may complete the target
  // determination and delete |this|.

  if (result == COMPLETE)
    ScheduleCallbackAndDeleteSelf(download::DOWNLOAD_INTERRUPT_REASON_NONE);
}

DownloadTargetDeterminer::Result
    DownloadTargetDeterminer::DoGenerateTargetPath() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(local_path_.empty());
  DCHECK_EQ(confirmation_reason_, DownloadConfirmationReason::NONE);
  DCHECK(!should_notify_extensions_);
  bool is_forced_path = !download_->GetForcedFilePath().empty();

  next_state_ = STATE_SET_INSECURE_DOWNLOAD_STATUS;
  // Transient download should use the existing path.
  if (download_->IsTransient()) {
    if (is_forced_path) {
      RecordDownloadPathGeneration(DownloadPathGenerationEvent::USE_FORCE_PATH,
                                   true);
      virtual_path_ = download_->GetForcedFilePath();
    } else if (!virtual_path_.empty()) {
      RecordDownloadPathGeneration(
          DownloadPathGenerationEvent::USE_EXISTING_VIRTUAL_PATH, true);
    } else {
      // No path is provided, we have no idea what the target path is. Stop the
      // target determination process and wait for self deletion.
      RecordDownloadPathGeneration(DownloadPathGenerationEvent::NO_VALID_PATH,
                                   true);
      RecordDownloadCancelReason(DownloadCancelReason::kNoValidPath);
      ScheduleCallbackAndDeleteSelf(
          download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
      return QUIT_DOLOOP;
    }

    DCHECK(virtual_path_.IsAbsolute());
    return CONTINUE;
  }

  bool no_prompt_needed = HasPromptedForPath();
#if BUILDFLAG(IS_ANDROID)
  // If |virtual_path_| is content URI, there is no need to prompt the user.
  no_prompt_needed |= virtual_path_.IsContentUri();
#endif
  if (!virtual_path_.empty() && no_prompt_needed && !is_forced_path) {
    // The download is being resumed and the user has already been prompted for
    // a path. Assume that it's okay to overwrite the file if there's a conflict
    // and reuse the selection.
    confirmation_reason_ = NeedsConfirmation(virtual_path_);
    conflict_action_ = DownloadPathReservationTracker::OVERWRITE;
    RecordDownloadPathGeneration(
        DownloadPathGenerationEvent::USE_EXISTING_VIRTUAL_PATH, false);
  } else if (!is_forced_path) {
    // If we don't have a forced path, we should construct a path for the
    // download. Forced paths are only specified for programmatic downloads
    // (WebStore, Drag&Drop). Treat the path as a virtual path. We will
    // eventually determine whether this is a local path and if not, figure out
    // a local path.
    base::FilePath generated_filename = GenerateFileName();
    confirmation_reason_ = NeedsConfirmation(generated_filename);
    base::FilePath target_directory;
    if (confirmation_reason_ != DownloadConfirmationReason::NONE) {
      if (download_prefs_->IsDownloadPathManaged())
        DCHECK(confirmation_reason_ == DownloadConfirmationReason::DLP_BLOCKED);
      // If the user is going to be prompted and the user has been prompted
      // before, then always prefer the last directory that the user selected.
      target_directory = download_prefs_->SaveFilePath();
      RecordDownloadPathGeneration(
          DownloadPathGenerationEvent::USE_LAST_PROMPT_DIRECTORY, false);
    } else {
      target_directory = download_prefs_->DownloadPath();
      RecordDownloadPathGeneration(
          DownloadPathGenerationEvent::USE_DEFAULTL_DOWNLOAD_DIRECTORY, false);
    }
    should_notify_extensions_ = true;
    virtual_path_ = target_directory.Append(generated_filename);
    DCHECK(virtual_path_.IsAbsolute());
  } else {
    conflict_action_ = DownloadPathReservationTracker::OVERWRITE;
    virtual_path_ = download_->GetForcedFilePath();
    RecordDownloadPathGeneration(DownloadPathGenerationEvent::USE_FORCE_PATH,
                                 false);
    // If this is a resumed download which was previously interrupted due to an
    // issue with the forced path, the user is still not prompted. If the path
    // supplied to a programmatic download is invalid, then the caller needs to
    // intervene.
    DCHECK(virtual_path_.IsAbsolute());
  }
  DVLOG(20) << "Generated virtual path: " << virtual_path_.AsUTF8Unsafe();

  return CONTINUE;
}

base::FilePath DownloadTargetDeterminer::GenerateFileName() const {
  std::string suggested_filename = download_->GetSuggestedFilename();
  std::string sniffed_mime_type = download_->GetMimeType();

  if (suggested_filename.empty() &&
      sniffed_mime_type == "application/x-x509-user-cert") {
    suggested_filename = "user.crt";
  }

  // Generate the file name, we may replace the file extension based on mime
  // type under certain condition.
  std::string default_filename(
      l10n_util::GetStringUTF8(IDS_DEFAULT_DOWNLOAD_FILENAME));
  std::string referrer_charset =
      GetProfile()->GetPrefs()->GetString(prefs::kDefaultCharset);
  base::FilePath generated_filename = net::GenerateFileName(
      download_->GetURL(), download_->GetContentDisposition(), referrer_charset,
      suggested_filename, sniffed_mime_type, default_filename);

#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
  // We don't replace the file extension if sfafe browsing consider the file
  // extension to be unsafe. Just let safe browsing scan the generated file.
  if (safe_browsing::FileTypePolicies::GetInstance()->IsCheckedBinaryFile(
          generated_filename)) {
    return generated_filename;
  }
#endif

  // If no mime type or explicitly specified a name, don't replace file
  // extension.
  if (sniffed_mime_type.empty() || !suggested_filename.empty())
    return generated_filename;

  // Trust content disposition header filename attribute.
  net::HttpContentDisposition content_disposition_header(
      download_->GetContentDisposition(), referrer_charset);
  if (!content_disposition_header.filename().empty())
    return generated_filename;

  // When headers have X-Content-Type-Options:nosniff, or for many text file
  // types like csv, sniffed mime type will be text/plain. Prefer the extension
  // generated by the URL here.
  if (sniffed_mime_type == "text/plain" &&
      download_->GetOriginalMimeType() != "text/plain") {
    return generated_filename;
  }

  // Replaces file extension based on sniffed mime type in network layer.
  generated_filename = net::GenerateFileName(
      download_->GetURL(), std::string() /* content_disposition */,
      referrer_charset, std::string() /* suggested_filename */,
      sniffed_mime_type, default_filename, true /* should_replace_extension */);
  return generated_filename;
}

DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoSetInsecureDownloadStatus() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!virtual_path_.empty());

  next_state_ = STATE_NOTIFY_EXTENSIONS;

  delegate_->GetInsecureDownloadStatus(
      download_, virtual_path_,
      base::BindOnce(&DownloadTargetDeterminer::GetInsecureDownloadStatusDone,
                     weak_ptr_factory_.GetWeakPtr()));
  return QUIT_DOLOOP;
}

void DownloadTargetDeterminer::GetInsecureDownloadStatusDone(
    download::DownloadItem::InsecureDownloadStatus status) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Delegate should not call back here more than once.
  DCHECK_EQ(STATE_NOTIFY_EXTENSIONS, next_state_);

  insecure_download_status_ = status;

  if (status == download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK) {
    RecordDownloadCancelReason(DownloadCancelReason::kInsecureDownload);
    ScheduleCallbackAndDeleteSelf(
        download::DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED);
    return;
  }

  DoLoop();
}

DownloadTargetDeterminer::Result
    DownloadTargetDeterminer::DoNotifyExtensions() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!virtual_path_.empty());

  next_state_ = STATE_RESERVE_VIRTUAL_PATH;

  if (!should_notify_extensions_ ||
      download_->GetState() != DownloadItem::IN_PROGRESS)
    return CONTINUE;

  delegate_->NotifyExtensions(
      download_, virtual_path_,
      base::BindOnce(&DownloadTargetDeterminer::NotifyExtensionsDone,
                     weak_ptr_factory_.GetWeakPtr()));
  return QUIT_DOLOOP;
}

void DownloadTargetDeterminer::NotifyExtensionsDone(
    const base::FilePath& suggested_path,
    DownloadPathReservationTracker::FilenameConflictAction conflict_action) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DVLOG(20) << "Extension suggested path: " << suggested_path.AsUTF8Unsafe();

  // Extensions should not call back here more than once.
  DCHECK_EQ(STATE_RESERVE_VIRTUAL_PATH, next_state_);

  // Ignore path suggestion for file URLs.
  if (download_->GetURL().SchemeIsFile()) {
    DoLoop();
    return;
  }

  if (!suggested_path.empty()) {
    // If an extension overrides the filename, then the target directory will be
    // forced to download_prefs_->DownloadPath() since extensions cannot place
    // downloaded files anywhere except there. This prevents subdirectories from
    // accumulating: if an extension is allowed to say that a file should go in
    // last_download_path/music/foo.mp3, then last_download_path will accumulate
    // the subdirectory /music/ so that the next download may end up in
    // Downloads/music/music/music/bar.mp3.
    base::FilePath new_path(download_prefs_->DownloadPath().Append(
        suggested_path).NormalizePathSeparators());

    // If the (Chrome) extension does not suggest an file extension, or if the
    // suggested extension matches that of the |virtual_path_|, do not
    // pass a mime type to GenerateSafeFileName so that it does not force the
    // filename to have an extension or generate a different one. Otherwise,
    // correct the file extension in case it is wrongly given.
    GenerateSafeFileName(&new_path, virtual_path_.Extension(),
                         download_->GetMimeType());

    virtual_path_ = new_path;
    create_target_directory_ = true;
  }
  // An extension may set conflictAction without setting filename.
  if (conflict_action != DownloadPathReservationTracker::UNIQUIFY)
    conflict_action_ = conflict_action;

  DoLoop();
}

DownloadTargetDeterminer::Result
    DownloadTargetDeterminer::DoReserveVirtualPath() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!virtual_path_.empty());

  next_state_ = STATE_PROMPT_USER_FOR_DOWNLOAD_PATH;
  if (download_->GetState() != DownloadItem::IN_PROGRESS)
    return CONTINUE;

  delegate_->ReserveVirtualPath(
      download_, virtual_path_, create_target_directory_, conflict_action_,
      base::BindOnce(&DownloadTargetDeterminer::ReserveVirtualPathDone,
                     weak_ptr_factory_.GetWeakPtr()));
  return QUIT_DOLOOP;
}

void DownloadTargetDeterminer::ReserveVirtualPathDone(
    download::PathValidationResult result,
    const base::FilePath& path) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DVLOG(20) << "Reserved path: " << path.AsUTF8Unsafe()
            << " Result:" << static_cast<int>(result);
  DCHECK_EQ(STATE_PROMPT_USER_FOR_DOWNLOAD_PATH, next_state_);
  RecordDownloadPathValidation(result, download_->IsTransient());
  if (download_->IsTransient()) {
    DCHECK_EQ(DownloadConfirmationReason::NONE, confirmation_reason_)
        << "Transient download should not ask the user for confirmation.";
    DCHECK(result != download::PathValidationResult::CONFLICT)
        << "Transient download"
           "should always overwrite or uniquify the file.";
    switch (result) {
      case download::PathValidationResult::PATH_NOT_WRITABLE:
      case download::PathValidationResult::NAME_TOO_LONG:
      case download::PathValidationResult::CONFLICT:
        RecordDownloadCancelReason(
            DownloadCancelReason::kFailedPathReservation);
        ScheduleCallbackAndDeleteSelf(
            download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
        return;
      case download::PathValidationResult::SUCCESS:
      case download::PathValidationResult::SUCCESS_RESOLVED_CONFLICT:
        DCHECK(virtual_path_ == path ||
               conflict_action_ == DownloadPathReservationTracker::UNIQUIFY);
        break;
      case download::PathValidationResult::SAME_AS_SOURCE:
        ScheduleCallbackAndDeleteSelf(
            download::DOWNLOAD_INTERRUPT_REASON_FILE_SAME_AS_SOURCE);
        return;
      case download::PathValidationResult::COUNT:
        NOTREACHED();
    }
  } else {
    virtual_path_ = path;

    switch (result) {
      case download::PathValidationResult::SUCCESS:
        break;

      case download::PathValidationResult::SAME_AS_SOURCE:
        ScheduleCallbackAndDeleteSelf(
            download::DOWNLOAD_INTERRUPT_REASON_FILE_SAME_AS_SOURCE);
        return;

      // TODO(crbug.com/40863725): This should trigger a duplicate download
      // prompt.
      case download::PathValidationResult::SUCCESS_RESOLVED_CONFLICT:
        break;

      case download::PathValidationResult::PATH_NOT_WRITABLE:
        confirmation_reason_ =
            DownloadConfirmationReason::TARGET_PATH_NOT_WRITEABLE;
        break;

      case download::PathValidationResult::NAME_TOO_LONG:
        confirmation_reason_ = DownloadConfirmationReason::NAME_TOO_LONG;
        break;

      case download::PathValidationResult::CONFLICT:
        confirmation_reason_ = DownloadConfirmationReason::TARGET_CONFLICT;
        break;
      case download::PathValidationResult::COUNT:
        NOTREACHED();
    }
  }

  DoLoop();
}

#if BUILDFLAG(IS_ANDROID)
void DownloadTargetDeterminer::RequestIncognitoWarningConfirmationDone(
    bool accepted) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (accepted) {
    DoLoop();
  } else {
    ScheduleCallbackAndDeleteSelf(
        download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
    return;
  }
}
#endif

DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoRequestConfirmation() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!virtual_path_.empty());
#if BUILDFLAG(IS_ANDROID)
  DCHECK(!download_->IsTransient() ||
         confirmation_reason_ == DownloadConfirmationReason::NONE ||
         // On Android we return here a second time after prompting the user.
         confirmation_reason_ == DownloadConfirmationReason::PREFERENCE);
#else
  DCHECK(!download_->IsTransient() ||
         confirmation_reason_ == DownloadConfirmationReason::NONE);
#endif

  next_state_ = STATE_DETERMINE_LOCAL_PATH;

  // Avoid prompting for a download if it isn't in-progress. The user will be
  // prompted once the download is resumed and headers are available.
  if (download_->GetState() == DownloadItem::IN_PROGRESS) {
#if BUILDFLAG(IS_ANDROID)
    // If we were looping back to check the user-confirmed path from the
    // dialog, and there were no additional errors, continue.
    if (is_checking_dialog_confirmed_path_ &&
        (confirmation_reason_ == DownloadConfirmationReason::PREFERENCE ||
         confirmation_reason_ == DownloadConfirmationReason::NONE)) {
      is_checking_dialog_confirmed_path_ = false;
      return CONTINUE;
    }
#endif

    // If there is a non-neutral confirmation reason, prompt the user.
    if (confirmation_reason_ != DownloadConfirmationReason::NONE) {
      base::FilePath sanitized_path = virtual_path_;
#if BUILDFLAG(IS_WIN)
      // Windows prompt dialog will resolve all env variables in the file name,
      // which may generate unexpected results. Remove env variables from the
      // file name first.
      std::wstring sanitized_name = ui::RemoveEnvVarFromFileName<wchar_t>(
          virtual_path_.BaseName().value(), L"%");
      // remove leading "." to avoid resorting to potential extension
      // bug: 41486690
      while (!sanitized_name.empty() && sanitized_name.back() == L'.') {
          sanitized_name.pop_back();
      }
      // trim trailing whitespace (space, tab, NBSP) to prevent stale extensions
      base::TrimWhitespace(sanitized_name, base::TrimPositions::TRIM_TRAILING, &sanitized_name);
      if (sanitized_name.empty()) {
        sanitized_name = base::UTF8ToWide(
            l10n_util::GetStringUTF8(IDS_DEFAULT_DOWNLOAD_FILENAME));
      }
      sanitized_path =
          virtual_path_.DirName().Append(base::FilePath(sanitized_name));
      const base::FilePath::StringType post_sanitize_ext =
          base::FilePath(sanitized_name).Extension();
      GenerateSafeFileName(&sanitized_path, post_sanitize_ext,
                           download_->GetMimeType());
#endif  // BUILDFLAG(IS_WIN)
      delegate_->RequestConfirmation(
          download_, sanitized_path, confirmation_reason_,
          base::BindRepeating(
              &DownloadTargetDeterminer::RequestConfirmationDone,
              weak_ptr_factory_.GetWeakPtr()));
      return QUIT_DOLOOP;
    } else {
#if BUILDFLAG(IS_ANDROID)
      content::BrowserContext* browser_context =
          content::DownloadItemUtils::GetBrowserContext(download_);
      bool isOffTheRecord =
          Profile::FromBrowserContext(browser_context)->IsOffTheRecord();
      if (isOffTheRecord && (!download_->IsTransient() ||
                             !download_->AllowAutoOpenAfterCompletion())) {
        delegate_->RequestIncognitoWarningConfirmation(base::BindOnce(
            &DownloadTargetDeterminer::RequestIncognitoWarningConfirmationDone,
            weak_ptr_factory_.GetWeakPtr()));
        return QUIT_DOLOOP;
      }
#endif
    }
  }

  return CONTINUE;
}

void DownloadTargetDeterminer::RequestConfirmationDone(
    DownloadConfirmationResult result,
    const ui::SelectedFileInfo& selected_file_info) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!download_->IsTransient());

  base::FilePath virtual_path = selected_file_info.path();
  DVLOG(20) << "User selected path:" << virtual_path.AsUTF8Unsafe();

#if BUILDFLAG(IS_ANDROID)
  is_checking_dialog_confirmed_path_ = false;
#endif
  if (result == DownloadConfirmationResult::CANCELED) {
    RecordDownloadCancelReason(DownloadCancelReason::kTargetConfirmationResult);
    ScheduleCallbackAndDeleteSelf(
        download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
    return;
  }
  DCHECK(!virtual_path.empty());
  DCHECK_EQ(STATE_DETERMINE_LOCAL_PATH, next_state_);

  // If the user wasn't prompted, then we need to clear the
  // confirmation_reason_. This way it's clear that user has not given consent
  // to download this resource.
  if (result == DownloadConfirmationResult::CONTINUE_WITHOUT_CONFIRMATION)
    confirmation_reason_ = DownloadConfirmationReason::NONE;

  virtual_path_ = virtual_path;
#if BUILDFLAG(IS_MAC)
  file_tags_ = selected_file_info.file_tags;
#endif

#if BUILDFLAG(IS_ANDROID)
  if (result == DownloadConfirmationResult::CONFIRMED_WITH_DIALOG) {
    // Double check the user-selected path is valid by looping back.
    is_checking_dialog_confirmed_path_ = true;
    if (confirmation_reason_ != DownloadConfirmationReason::PREFERENCE) {
      confirmation_reason_ = DownloadConfirmationReason::NONE;
    }
    next_state_ = STATE_RESERVE_VIRTUAL_PATH;
  }
#endif

  download_prefs_->SetSaveFilePath(virtual_path_.DirName());
  DoLoop();
}

DownloadTargetDeterminer::Result
    DownloadTargetDeterminer::DoDetermineLocalPath() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!virtual_path_.empty());
  DCHECK(local_path_.empty());

  next_state_ = STATE_DETERMINE_MIME_TYPE;

  delegate_->DetermineLocalPath(
      download_, virtual_path_,
      base::BindOnce(&DownloadTargetDeterminer::DetermineLocalPathDone,
                     weak_ptr_factory_.GetWeakPtr()));
  return QUIT_DOLOOP;
}

void DownloadTargetDeterminer::DetermineLocalPathDone(
    const base::FilePath& local_path,
    const base::FilePath& file_name) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DVLOG(20) << "Local path: " << local_path.AsUTF8Unsafe();
  if (local_path.empty()) {
    // Path subsitution failed. Usually caused by something going wrong with the
    // Google Drive logic (e.g. filesystem error while trying to create the
    // cache file). We are going to return a generic error here since a more
    // specific one is unlikely to be helpful to the user.
    RecordDownloadCancelReason(DownloadCancelReason::kEmptyLocalPath);
    ScheduleCallbackAndDeleteSelf(
        download::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
    return;
  }
  DCHECK_EQ(STATE_DETERMINE_MIME_TYPE, next_state_);

  local_path_ = local_path;
#if BUILDFLAG(IS_ANDROID)
  // If the |local path_| is a content Uri while the |virtual_path_| is a
  // canonical path, replace the file name with the new name we got from
  // the system so safebrowsing can check file extensions properly.
  if (local_path_.IsContentUri() && !virtual_path_.IsContentUri()) {
    virtual_path_ = virtual_path_.DirName().Append(file_name);
  }
#endif  // BUILDFLAG(IS_ANDROID)
  DoLoop();
}

DownloadTargetDeterminer::Result
    DownloadTargetDeterminer::DoDetermineMimeType() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!virtual_path_.empty());
  DCHECK(!local_path_.empty());
  DCHECK(mime_type_.empty());

  next_state_ = STATE_CHECK_DOWNLOAD_URL;
  if (virtual_path_ == local_path_
#if BUILDFLAG(IS_ANDROID)
      || local_path_.IsContentUri()
#endif  //  BUILDFLAG(IS_ANDROID)
  ) {
    delegate_->GetFileMimeType(
        local_path_,
        base::BindOnce(&DownloadTargetDeterminer::DetermineMimeTypeDone,
                       weak_ptr_factory_.GetWeakPtr()));
    return QUIT_DOLOOP;
  }

  return CONTINUE;
}

void DownloadTargetDeterminer::DetermineMimeTypeDone(
    const std::string& mime_type) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DVLOG(20) << "MIME type: " << mime_type;
  DCHECK_EQ(STATE_CHECK_DOWNLOAD_URL, next_state_);
  DCHECK(!local_path_.empty());
  DCHECK(!is_filetype_handled_safely_);

  mime_type_ = mime_type;
  if (!mime_type_.empty()) {
    is_filetype_handled_safely_ =
        DetermineIfHandledSafelyHelper(download_, local_path_, mime_type_);
    DVLOG(20) << "Is file type handled safely: " << is_filetype_handled_safely_;
  }

  DoLoop();
}

bool DownloadTargetDeterminer::DetermineIfHandledSafelyHelper(
    download::DownloadItem* download,
    const base::FilePath& local_path,
    const std::string& mime_type) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (blink::IsSupportedMimeType(mime_type)) {
    return true;
  }

#if BUILDFLAG(ENABLE_PLUGINS)
  DCHECK(!mime_type.empty());
  auto* plugin_service = content::PluginService::GetInstance();
  plugin_service->GetPlugins();
  return plugin_service->HasPlugin(
      content::DownloadItemUtils::GetBrowserContext(download),
      net::FilePathToFileURL(local_path), mime_type);
#else
  return false;
#endif
}

DownloadTargetDeterminer::Result
    DownloadTargetDeterminer::DoCheckDownloadUrl() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!virtual_path_.empty());
#if BUILDFLAG(IS_ANDROID)
  if (base::FeatureList::IsEnabled(
          safe_browsing::kGooglePlayProtectReducesWarnings)) {
    next_state_ = STATE_CHECK_APP_VERIFICATION;
  } else {
    next_state_ = STATE_CHECK_VISITED_REFERRER_BEFORE;
  }
#else
  next_state_ = STATE_CHECK_VISITED_REFERRER_BEFORE;
#endif

  // If user has validated a dangerous download, don't check.
  if (danger_type_ == download::DOWNLOAD_DANGER_TYPE_USER_VALIDATED)
    return CONTINUE;

  delegate_->CheckDownloadUrl(
      download_, virtual_path_,
      base::BindOnce(&DownloadTargetDeterminer::CheckDownloadUrlDone,
                     weak_ptr_factory_.GetWeakPtr()));
  return QUIT_DOLOOP;
}

void DownloadTargetDeterminer::CheckDownloadUrlDone(
    download::DownloadDangerType danger_type) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DVLOG(20) << "URL Check Result:" << danger_type;
#if BUILDFLAG(IS_ANDROID)
  DCHECK_EQ(base::FeatureList::IsEnabled(
                safe_browsing::kGooglePlayProtectReducesWarnings)
                ? STATE_CHECK_APP_VERIFICATION
                : STATE_CHECK_VISITED_REFERRER_BEFORE,
            next_state_);
#else
  DCHECK_EQ(STATE_CHECK_VISITED_REFERRER_BEFORE, next_state_);
#endif
  danger_type_ = danger_type;
  DoLoop();
}

#if BUILDFLAG(IS_ANDROID)
DownloadTargetDeterminer::Result
DownloadTargetDeterminer::DoCheckAppVerification() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  next_state_ = STATE_CHECK_VISITED_REFERRER_BEFORE;
  safe_browsing::SafeBrowsingApiHandlerBridge::GetInstance()
      .StartIsVerifyAppsEnabled(
          base::BindOnce(&DownloadTargetDeterminer::CheckAppVerificationDone,
                         weak_ptr_factory_.GetWeakPtr()));
  return QUIT_DOLOOP;
}

void DownloadTargetDeterminer::CheckAppVerificationDone(
    safe_browsing::VerifyAppsEnabledResult result) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK_EQ(STATE_CHECK_VISITED_REFERRER_BEFORE, next_state_);
  is_app_verification_enabled_ =
      result == safe_browsing::VerifyAppsEnabledResult::SUCCESS_ENABLED;
  DoLoop();
}
#endif

DownloadTargetDeterminer::Result
    DownloadTargetDeterminer::DoCheckVisitedReferrerBefore() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  next_state_ = STATE_DETERMINE_INTERMEDIATE_PATH;

  // Checking if there are prior visits to the referrer is only necessary if the
  // danger level of the download depends on the file type.
  if (danger_type_ != download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS &&
      danger_type_ != download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT &&
      danger_type_ != download::DOWNLOAD_DANGER_TYPE_ALLOWLISTED_BY_POLICY) {
    return CONTINUE;
  }

  // First determine the danger level assuming that the user doesn't have any
  // prior visits to the referrer recoreded in history. The resulting danger
  // level would be ALLOW_ON_USER_GESTURE if the level depends on the visit
  // history. In the latter case, we can query the history DB to determine if
  // there were prior requests and determine the danger level again once the
  // result is available.
  danger_level_ = GetDangerLevel(NO_VISITS_TO_REFERRER);

  if (danger_level_ == DownloadFileType::NOT_DANGEROUS)
    return CONTINUE;

  if (danger_level_ == DownloadFileType::ALLOW_ON_USER_GESTURE) {
#if BUILDFLAG(IS_ANDROID)
    if (base::FeatureList::IsEnabled(
            safe_browsing::kGooglePlayProtectReducesWarnings) &&
        is_app_verification_enabled_) {
      return CONTINUE;
    }
#endif

    // HistoryServiceFactory redirects incognito profiles to on-record profiles.
    // There's no history for on-record profiles in unit_tests.
    history::HistoryService* history_service =
        HistoryServiceFactory::GetForProfile(
            GetProfile(), ServiceAccessType::EXPLICIT_ACCESS);

    if (history_service && download_->GetReferrerUrl().is_valid()) {
      history_service->GetVisibleVisitCountToHost(
          download_->GetReferrerUrl(),
          base::BindOnce(
              &VisitCountsToVisitedBefore,
              base::BindOnce(
                  &DownloadTargetDeterminer::CheckVisitedReferrerBeforeDone,
                  weak_ptr_factory_.GetWeakPtr())),
          &history_tracker_);
      return QUIT_DOLOOP;
    }
  }

  // If the danger level doesn't depend on having visited the refererrer URL or
  // if original profile doesn't have a HistoryService or the referrer url is
  // invalid, then assume the referrer has not been visited before.
  if (danger_type_ == download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS)
    danger_type_ = download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE;
  return CONTINUE;
}

void DownloadTargetDeterminer::CheckVisitedReferrerBeforeDone(
    bool visited_referrer_before) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK_EQ(STATE_DETERMINE_INTERMEDIATE_PATH, next_state_);
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
  safe_browsing::RecordDownloadFileTypeAttributes(
      safe_browsing::FileTypePolicies::GetInstance()->GetFileDangerLevel(
          virtual_path_.BaseName(), download_->GetURL(),
          GetProfile()->GetPrefs()),
      download_->HasUserGesture(), visited_referrer_before,
      GetLastDownloadBypassTimestamp());
#endif
  danger_level_ = GetDangerLevel(
      visited_referrer_before ? VISITED_REFERRER : NO_VISITS_TO_REFERRER);
  if (danger_level_ != DownloadFileType::NOT_DANGEROUS &&
      danger_type_ == download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS)
    danger_type_ = download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE;
  DoLoop();
}

DownloadTargetDeterminer::Result
    DownloadTargetDeterminer::DoDetermineIntermediatePath() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!virtual_path_.empty());
  DCHECK(!local_path_.empty());
  DCHECK(intermediate_path_.empty());
  DCHECK(!virtual_path_.MatchesExtension(kCrdownloadSuffix));
  DCHECK(!local_path_.MatchesExtension(kCrdownloadSuffix));

  next_state_ = STATE_NONE;

#if BUILDFLAG(IS_ANDROID)
  // If the local path is a content URI, the download should be from resumption
  // and we can just use the current path.
  if (local_path_.IsContentUri()) {
    intermediate_path_ = local_path_;
    return COMPLETE;
  }
#endif

  // Note that the intermediate filename is always uniquified (i.e. if a file by
  // the same name exists, it is never overwritten). Therefore the code below
  // does not attempt to find a name that doesn't conflict with an existing
  // file.

  // If the actual target of the download is a virtual path, then the local path
  // is considered to point to a temporary path. A separate intermediate path is
  // unnecessary since the local path already serves that purpose.
  if (virtual_path_.BaseName() != local_path_.BaseName()) {
    intermediate_path_ = local_path_;
    return COMPLETE;
  }

  // If the download has a forced path and is safe, then just use the
  // target path. In practice the temporary download file that was created prior
  // to download filename determination is already named
  // download_->GetForcedFilePath().
  if (danger_type_ == download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS &&
      !download_->GetForcedFilePath().empty()) {
    DCHECK_EQ(download_->GetForcedFilePath().value(), local_path_.value());
    intermediate_path_ = local_path_;
    return COMPLETE;
  }

  // Transient downloads don't need to be renamed to intermediate file.
  if (danger_type_ == download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS &&
      download_->IsTransient()) {
    intermediate_path_ = local_path_;
    return COMPLETE;
  }

  // Other safe downloads get a .crdownload suffix for their intermediate name.
  if (danger_type_ == download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS) {
    intermediate_path_ = GetCrDownloadPath(local_path_);
    return COMPLETE;
  }

  // If this is a resumed download, then re-use the existing intermediate path
  // if one is available. A resumed download shouldn't cause a non-dangerous
  // download to be considered dangerous upon resumption. Therefore the
  // intermediate file should already be in the correct form.
  if (is_resumption_ && !download_->GetFullPath().empty() &&
      local_path_.DirName() == download_->GetFullPath().DirName()) {
    DCHECK_NE(download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
              download_->GetDangerType());
    DCHECK_EQ(kCrdownloadSuffix, download_->GetFullPath().Extension());
    intermediate_path_ = download_->GetFullPath();
    return COMPLETE;
  }

  // Dangerous downloads receive a random intermediate name that looks like:
  // 'Unconfirmed <random>.crdownload'.
  static constexpr char kUnconfirmedFormatSuffix[] = " %d.crdownload";
  // Range of the <random> uniquifier.
  constexpr int kUnconfirmedUniquifierRange = 1000000;

  std::string file_name =
      l10n_util::GetStringUTF8(IDS_DOWNLOAD_UNCONFIRMED_PREFIX) +
      base::StringPrintf(kUnconfirmedFormatSuffix,
                         base::RandInt(0, kUnconfirmedUniquifierRange));
  intermediate_path_ =
      local_path_.DirName().Append(base::FilePath::FromUTF8Unsafe(file_name));
  return COMPLETE;
}

void DownloadTargetDeterminer::ScheduleCallbackAndDeleteSelf(
    download::DownloadInterruptReason interrupt_reason) {
  DCHECK(download_);
  DVLOG(20) << "Scheduling callback. Virtual:" << virtual_path_.AsUTF8Unsafe()
            << " Local:" << local_path_.AsUTF8Unsafe()
            << " Intermediate:" << intermediate_path_.AsUTF8Unsafe()
            << " Confirmation reason:" << static_cast<int>(confirmation_reason_)
            << " Danger type:" << danger_type_
            << " Danger level:" << danger_level_
            << " Interrupt reason:" << static_cast<int>(interrupt_reason);
  download::DownloadTargetInfo target_info;

  target_info.target_path = local_path_;
  target_info.intermediate_path = intermediate_path_;
#if BUILDFLAG(IS_ANDROID)
  // If |virtual_path_| is content URI, there is no need to prompt the user.
  if (local_path_.IsContentUri() && !virtual_path_.IsContentUri()) {
    target_info.display_name = virtual_path_.BaseName();
  } else if (download_->GetDownloadFile() &&
             download_->GetDownloadFile()->IsMemoryFile()) {
    // Memory file doesn't have a proper display name. Generate one here.
    target_info.display_name = GenerateFileName();
  }
#endif
  target_info.mime_type = mime_type_;
#if BUILDFLAG(IS_MAC)
  target_info.file_tags = file_tags_;
#endif
  target_info.is_filetype_handled_safely = is_filetype_handled_safely_;
  target_info.target_disposition =
      (HasPromptedForPath() ||
               confirmation_reason_ != DownloadConfirmationReason::NONE
           ? DownloadItem::TARGET_DISPOSITION_PROMPT
           : DownloadItem::TARGET_DISPOSITION_OVERWRITE);
  target_info.danger_type = danger_type_;
  target_info.interrupt_reason = interrupt_reason;
  target_info.insecure_download_status = insecure_download_status_;

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(completion_callback_),
                                std::move(target_info), danger_level_));
  delete this;
}

Profile* DownloadTargetDeterminer::GetProfile() const {
  DCHECK(content::DownloadItemUtils::GetBrowserContext(download_));
  return Profile::FromBrowserContext(
      content::DownloadItemUtils::GetBrowserContext(download_));
}

DownloadConfirmationReason DownloadTargetDeterminer::NeedsConfirmation(
    const base::FilePath& filename) const {
  // Transient download never has user interaction.
  if (download_->IsTransient())
    return DownloadConfirmationReason::NONE;

  if (is_resumption_) {
    // For resumed downloads, if the target disposition or prefs require
    // prompting, the user has already been prompted. Try to respect the user's
    // selection, unless we've discovered that the target path cannot be used
    // for some reason.
    download::DownloadInterruptReason reason = download_->GetLastReason();
    switch (reason) {
      case download::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED:
        return DownloadConfirmationReason::TARGET_PATH_NOT_WRITEABLE;

      case download::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE:
      case download::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE:
        return DownloadConfirmationReason::TARGET_NO_SPACE;

      default:
        return DownloadConfirmationReason::NONE;
    }
  }

  // If the download path is forced, don't prompt.
  if (!download_->GetForcedFilePath().empty()) {
    // 'Save As' downloads shouldn't have a forced path.
    DCHECK(DownloadItem::TARGET_DISPOSITION_PROMPT !=
           download_->GetTargetDisposition());
    return DownloadConfirmationReason::NONE;
  }

  // If the download path is blocked by DLP, the user should be prompted even if
  // the path is managed or PromptForDownload is false.
  bool isDefaultPathDlpBlocked =
      IsDownloadDlpBlocked(download_prefs_->DownloadPath());

  // Don't ask where to save if the download path is managed. Even if the user
  // wanted to be prompted for "all" downloads, or if this was a 'Save As'
  // download. Ask if the default path is blocked by DLP.
  if (download_prefs_->IsDownloadPathManaged() && !isDefaultPathDlpBlocked)
    return DownloadConfirmationReason::NONE;

  // Prompt if this is a 'Save As' download.
  if (download_->GetTargetDisposition() ==
      DownloadItem::TARGET_DISPOSITION_PROMPT)
    return DownloadConfirmationReason::SAVE_AS;

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  // Don't prompt for extension downloads if the installation site is allow
  // listed.
  if (download_crx_util::IsTrustedExtensionDownload(GetProfile(), *download_))
    return DownloadConfirmationReason::NONE;
#endif

  // Don't prompt for file types that are marked for opening automatically.
  if (download_prefs_->IsAutoOpenEnabled(download_->GetURL(), filename))
    return DownloadConfirmationReason::NONE;

  // For everything else, prompting is controlled by the PromptForDownload pref.
  // The user may still be prompted even if this pref is disabled due to, for
  // example, there being an unresolvable filename conflict or the target path
  // is not writeable, or if the path is blocked by DLP.
  if (download_prefs_->PromptForDownload()) {
    return DownloadConfirmationReason::PREFERENCE;
  } else {
    return isDefaultPathDlpBlocked ? DownloadConfirmationReason::DLP_BLOCKED
                                   : DownloadConfirmationReason::NONE;
  }
}

bool DownloadTargetDeterminer::IsDownloadDlpBlocked(
    const base::FilePath& download_path) const {
#if BUILDFLAG(IS_CHROMEOS)
  auto* web_contents =
      download_ ? content::DownloadItemUtils::GetWebContents(download_)
                : nullptr;
  if (!web_contents)
    return false;
  policy::DlpRulesManager* rules_manager =
      policy::DlpRulesManagerFactory::GetForPrimaryProfile();
  if (!rules_manager)
    return false;
  policy::DlpFilesControllerAsh* files_controller =
      static_cast<policy::DlpFilesControllerAsh*>(
          rules_manager->GetDlpFilesController());
  if (!files_controller)
    return false;
  const GURL authority_url = download::BaseFile::GetEffectiveAuthorityURL(
      download_->GetURL(), download_->GetReferrerUrl());
  if (!authority_url.is_valid()) {
    return true;
  }
  return files_controller->ShouldPromptBeforeDownload(
      policy::DlpFileDestination(authority_url), download_path);
#else
  return false;
#endif
}

bool DownloadTargetDeterminer::HasPromptedForPath() const {
  return (is_resumption_ && download_->GetTargetDisposition() ==
                                DownloadItem::TARGET_DISPOSITION_PROMPT);
}

DownloadFileType::DangerLevel DownloadTargetDeterminer::GetDangerLevel(
    PriorVisitsToReferrer visits) const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // User-initiated extension downloads from pref-whitelisted sources are not
  // considered dangerous.
  if (download_->HasUserGesture() &&
      download_crx_util::IsTrustedExtensionDownload(GetProfile(), *download_)) {
    return DownloadFileType::NOT_DANGEROUS;
  }

#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
  DownloadFileType::DangerLevel danger_level =
      safe_browsing::FileTypePolicies::GetInstance()->GetFileDangerLevel(
          virtual_path_.BaseName(), download_->GetURL(),
          GetProfile()->GetPrefs());
  policy::DownloadRestriction download_restriction =
      static_cast<policy::DownloadRestriction>(
          GetProfile()->GetPrefs()->GetInteger(
              policy::policy_prefs::kDownloadRestrictions));

  // If the user has has been prompted or will be, assume that the user has
  // approved the download. A programmatic download is considered safe unless it
  // contains malware.
  bool user_approved_path =
      !download_->GetForcedFilePath().empty() &&
      // Drag and drop download paths are not approved by the user. See
      // https://crbug.com/1513639
      download_->GetDownloadSource() != download::DownloadSource::DRAG_AND_DROP;
  if (HasPromptedForPath() ||
      confirmation_reason_ != DownloadConfirmationReason::NONE ||
      user_approved_path) {
    // If the "DownloadRestrictions" enterprise policy explicitly disallows the
    // download, don't let the user gesture bypass the dangerous verdict.
    //
    // TODO(chlily) Need to verify how DownloadRestrictions policy functions
    // when there is no Safe Browsing in the build. The policy (or at least
    // the dangerous file types part of it) should still be active even if
    // Safe Browsing is unavailable. (!BUILDFLAG(SAFE_BROWSING_AVAILABLE))
    if ((download_restriction == policy::DownloadRestriction::DANGEROUS_FILES ||
         download_restriction ==
             policy::DownloadRestriction::POTENTIALLY_DANGEROUS_FILES) &&
        danger_level != DownloadFileType::NOT_DANGEROUS) {
      return DownloadFileType::DANGEROUS;
    }
    return DownloadFileType::NOT_DANGEROUS;
  }

  // Anything the user has marked auto-open is OK if it's user-initiated.
  if (download_prefs_->IsAutoOpenEnabled(download_->GetURL(), virtual_path_) &&
      download_->HasUserGesture())
    return DownloadFileType::NOT_DANGEROUS;

  // A danger level of ALLOW_ON_USER_GESTURE is used to label potentially
  // dangerous file types that have a high frequency of legitimate use. We would
  // like to avoid prompting for the legitimate cases as much as possible. To
  // that end, we consider a download to be legitimate if one of the following
  // is true, and avoid prompting:
  //
  // * The user navigated to the download URL via the omnibox (either by typing
  //   the URL, pasting it, or using search).
  //
  // * The navigation that initiated the download has a user gesture associated
  //   with it AND the user the user is familiar with the referring origin. A
  //   user is considered familiar with a referring origin if a visit for a page
  //   from the same origin was recorded on the previous day or earlier.
  if (danger_level == DownloadFileType::ALLOW_ON_USER_GESTURE &&
      ((download_->GetTransitionType() &
        ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) != 0 ||
       (download_->HasUserGesture() && visits == VISITED_REFERRER)))
    return DownloadFileType::NOT_DANGEROUS;
  return danger_level;
#else
  return DownloadFileType::NOT_DANGEROUS;
#endif
}

std::optional<base::Time>
DownloadTargetDeterminer::GetLastDownloadBypassTimestamp() const {
  safe_browsing::SafeBrowsingMetricsCollector* metrics_collector =
      safe_browsing::SafeBrowsingMetricsCollectorFactory::GetForProfile(
          GetProfile());
  // metrics_collector can be null in incognito.
  return metrics_collector ? metrics_collector->GetLatestEventTimestamp(
                                 safe_browsing::SafeBrowsingMetricsCollector::
                                     EventType::DANGEROUS_DOWNLOAD_BYPASS)
                           : std::nullopt;
}

void DownloadTargetDeterminer::OnDownloadDestroyed(
    DownloadItem* download) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK_EQ(download_, download);
  ScheduleCallbackAndDeleteSelf(
      download::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
}

// static
void DownloadTargetDeterminer::Start(
    download::DownloadItem* download,
    const base::FilePath& initial_virtual_path,
    DownloadPathReservationTracker::FilenameConflictAction conflict_action,
    DownloadPrefs* download_prefs,
    DownloadTargetDeterminerDelegate* delegate,
    CompletionCallback callback) {
  // DownloadTargetDeterminer owns itself and will self destruct when the job is
  // complete or the download item is destroyed. The callback is always invoked
  // asynchronously.
  new DownloadTargetDeterminer(download, initial_virtual_path, conflict_action,
                               download_prefs, delegate, std::move(callback));
}

// static
base::FilePath DownloadTargetDeterminer::GetCrDownloadPath(
    const base::FilePath& suggested_path) {
  return base::FilePath(suggested_path.value() + kCrdownloadSuffix);
}