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

#include "components/omnibox/browser/verbatim_match.h"

#include "base/containers/fixed_flat_set.h"
#include "components/omnibox/browser/autocomplete_classifier.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/omnibox/browser/autocomplete_match_classification.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/autocomplete_provider_client.h"
#include "components/omnibox/browser/in_memory_url_index_types.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/common/url_constants.h"
#include "extensions/buildflags/buildflags.h"
#include "third_party/metrics_proto/omnibox_scoring_signals.pb.h"
#include "url/gurl.h"
#include "url/url_constants.h"

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#include "extensions/common/constants.h"
#endif

namespace {
#if BUILDFLAG(IS_ANDROID)
// Note: On Android, restrict the verbatim URLs allowed to be default. We
// explicitly exclude schemes that may be used to execute Javascript code
// snippet in the context of the current page on mobile devices.
constexpr auto kAndroidNavigableSchemes =
    base::MakeFixedFlatSet<std::string_view>({
        url::kHttpScheme,
        url::kHttpsScheme,
        url::kAboutScheme,
        content::kChromeUIScheme,
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
        // On desktop, extensions are always enabled, `kAndroidNavigableSchemes`
        // is not used, and verbatim extension URLs are always allowed to be
        // default. On mobile android, extensions are disabled,
        // `ENABLE_EXTENSIONS_CORE` is false, and verbatim extension URLs are
        // not allowed to be default. On desktop android, extensions are enabled
        // and verbatim extension URLs are allowed to be default depending on
        // `ENABLE_EXTENSIONS_CORE`.
        extensions::kExtensionScheme,
#endif
    });
#endif  // BUILDFLAG(IS_ANDROID)
}  // namespace

AutocompleteMatch VerbatimMatchForURL(
    AutocompleteProvider* provider,
    AutocompleteProviderClient* client,
    const AutocompleteInput& input,
    const GURL& destination_url,
    const std::u16string& destination_description,
    int verbatim_relevance) {
  AutocompleteMatch match;
  // If the caller is a provider and already knows where the verbatim match
  // should go, construct the match directly, don't call Classify() on the
  // input. Classify() runs all providers' synchronous passes. Some providers
  // such as HistoryQuick can have a slow synchronous pass on some inputs.
  if (provider != nullptr && destination_url.is_valid()) {
    match =
        VerbatimMatchForInput(provider, client, input, destination_url,
                              !AutocompleteInput::HasHTTPScheme(input.text()));
    match.description = destination_description;
    if (!match.description.empty())
      match.description_class.push_back({0, ACMatchClassification::NONE});
  } else {
    client->Classify(input.text(), false, true,
                     input.current_page_classification(), &match, nullptr);
  }
  match.allowed_to_be_default_match = true;
  // The default relevance to use for relevance match. Should be greater than
  // all relevance matches returned by the ZeroSuggest server.
  const int kDefaultVerbatimRelevance = 1300;
  match.relevance =
      verbatim_relevance >= 0 ? verbatim_relevance : kDefaultVerbatimRelevance;
  return match;
}

AutocompleteMatch VerbatimMatchForInput(AutocompleteProvider* provider,
                                        AutocompleteProviderClient* client,
                                        const AutocompleteInput& input,
                                        const GURL& destination_url,
                                        bool trim_default_scheme) {
  AutocompleteMatch match(provider, 0, false,
                          AutocompleteMatchType::URL_WHAT_YOU_TYPED);

  if (destination_url.is_valid()) {
    match.destination_url = destination_url;
    // If the input explicitly contains "http://" or "https://", callers must
    // set |trim_default_scheme| to false. Otherwise, |trim_default_scheme| may
    // be either true or false.
    if (input.added_default_scheme_to_typed_url()) {
      DCHECK(!(trim_default_scheme &&
               AutocompleteInput::HasHTTPSScheme(input.text())));
    } else {
      DCHECK(!(trim_default_scheme &&
               AutocompleteInput::HasHTTPScheme(input.text())));
    }
    const url_formatter::FormatUrlType format_type =
        input.added_default_scheme_to_typed_url()
            ? url_formatter::kFormatUrlOmitHTTPS
            : url_formatter::kFormatUrlOmitHTTP;

    std::u16string display_string(url_formatter::FormatUrl(
        destination_url, url_formatter::kFormatUrlOmitDefaults & ~format_type,
        base::UnescapeRule::SPACES, nullptr, nullptr, nullptr));
    if (trim_default_scheme) {
      AutocompleteProvider::TrimSchemePrefix(
          &display_string, input.added_default_scheme_to_typed_url());
    }
    match.fill_into_edit =
        AutocompleteInput::FormattedStringWithEquivalentMeaning(
            destination_url, display_string, client->GetSchemeClassifier(),
            nullptr);
    // The what-you-typed match is generally only allowed to be default for
    // URL inputs or when there is no default search provider.  (It's also
    // allowed to be default for UNKNOWN inputs where the destination is a known
    // intranet site.  In this case, |allowed_to_be_default_match| is revised in
    // FixupExactSuggestion().)
    const bool has_default_search_provider =
        client->GetTemplateURLService() &&
        client->GetTemplateURLService()->GetDefaultSearchProvider();
    match.allowed_to_be_default_match =
        (input.type() == metrics::OmniboxInputType::URL) ||
        !has_default_search_provider;
#if BUILDFLAG(IS_ANDROID)
    // Disallow non-navigable schemes to be default. This prevents javascript:
    // snippets from being accidentally executed upon paste, refine, edit, etc.
    match.allowed_to_be_default_match &=
        kAndroidNavigableSchemes.contains(destination_url.GetScheme());
#endif

    // NOTE: Don't set match.inline_autocompletion to something non-empty here;
    // it's surprising and annoying.

    // Try to highlight "innermost" match location.  If we fix up "w" into
    // "www.w.com", we want to highlight the fifth character, not the first.
    // This relies on match.destination_url being the non-prefix-trimmed version
    // of match.contents.
    match.contents = display_string;

    TermMatches termMatches = {{0, 0, input.text().length()}};
    match.contents_class = ClassifyTermMatches(
        termMatches, match.contents.size(),
        ACMatchClassification::MATCH | ACMatchClassification::URL,
        ACMatchClassification::URL);

    // Only set scoring signals for eligible matches.
    if (match.IsMlSignalLoggingEligible()) {
      if (!match.scoring_signals) {
        match.scoring_signals =
            std::make_optional<::metrics::OmniboxScoringSignals>();
      }
      match.scoring_signals->set_is_verbatim(true);
    }
  }

  return match;
}

AutocompleteMatch VerbatimMatchForContext(AutocompleteProvider* provider,
                                          AutocompleteProviderClient* client,
                                          const AutocompleteInput& input,
                                          int relevance) {
  AutocompleteMatch match(provider, relevance, /*deletable=*/false,
                          AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED);
  match.allowed_to_be_default_match = true;

  if (client->GetTemplateURLService()) {
    const TemplateURL* default_search_provider =
        client->GetTemplateURLService()->GetDefaultSearchProvider();
    if (default_search_provider) {
      TemplateURLRef::SearchTermsArgs search_terms_args;
      search_terms_args.page_classification =
          input.current_page_classification();
      match.destination_url =
          GURL(default_search_provider->url_ref().ReplaceSearchTerms(
              search_terms_args,
              client->GetTemplateURLService()->search_terms_data()));
      match.keyword = default_search_provider->keyword();
      match.transition = ui::PAGE_TRANSITION_GENERATED;
      match.search_terms_args =
          std::make_unique<TemplateURLRef::SearchTermsArgs>(search_terms_args);
    }
  }

  if (match.IsMlSignalLoggingEligible()) {
    if (!match.scoring_signals) {
      match.scoring_signals =
          std::make_optional<::metrics::OmniboxScoringSignals>();
    }
    match.scoring_signals->set_is_verbatim(true);
  }

  return match;
}
