// Copyright 2014 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/search_engines/template_url_fetcher.h" #include "base/bind.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/ranges/algorithm.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "components/search_engines/template_url.h" #include "components/search_engines/template_url_parser.h" #include "components/search_engines/template_url_service.h" #include "net/base/load_flags.h" #include "net/traffic_annotation/network_traffic_annotation.h" #include "services/network/public/cpp/resource_request.h" #include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/mojom/fetch_api.mojom-shared.h" namespace { // In some network environments, silent failure can be avoided by retrying // request on network change. This helps OpenSearch get through in such cases. // See https://crbug.com/956689 for context. constexpr int kOpenSearchRetryCount = 3; // Timeout for OpenSearch description document (OSDD) fetch request. // Requests for a particular resource are limited to one. // Requests may not receive a response, and in that case no // further requests would be allowed. The timeout cleans up failed requests // so that later attempts to fetch the OSDD can be made. constexpr int kOpenSearchTimeoutSeconds = 30; // Traffic annotation for RequestDelegate. const net::NetworkTrafficAnnotationTag kTrafficAnnotation = net::DefineNetworkTrafficAnnotation("open_search", R"( semantics { sender: "Omnibox" description: "Web pages can include an OpenSearch description doc in their HTML. " "In this case Chromium downloads and parses the file. The " "corresponding search engine is added to the list in the browser " "settings (chrome://settings/searchEngines)." trigger: "User visits a web page containing a tag." data: "None" destination: WEBSITE } policy { cookies_allowed: YES cookies_store: "user" setting: "This feature cannot be disabled in settings." policy_exception_justification: "Not implemented, considered not useful as this feature does not " "upload any data." })"); } // namespace // RequestDelegate ------------------------------------------------------------ class TemplateURLFetcher::RequestDelegate { public: RequestDelegate(TemplateURLFetcher* fetcher, const std::u16string& keyword, const GURL& osdd_url, const GURL& favicon_url, const url::Origin& initiator, network::mojom::URLLoaderFactory* url_loader_factory, int render_frame_id, int32_t request_id); RequestDelegate(const RequestDelegate&) = delete; RequestDelegate& operator=(const RequestDelegate&) = delete; // If data contains a valid OSDD, a TemplateURL is created and added to // the TemplateURLService. void OnSimpleLoaderComplete(std::unique_ptr response_body); // URL of the OSDD. GURL url() const { return osdd_url_; } // Keyword to use. std::u16string keyword() const { return keyword_; } private: void OnTemplateURLParsed(std::unique_ptr template_url); void OnLoaded(); void AddSearchProvider(); std::unique_ptr simple_url_loader_; raw_ptr fetcher_; std::unique_ptr template_url_; std::u16string keyword_; const GURL osdd_url_; const GURL favicon_url_; base::CallbackListSubscription template_url_subscription_; base::WeakPtrFactory weak_factory_{this}; }; TemplateURLFetcher::RequestDelegate::RequestDelegate( TemplateURLFetcher* fetcher, const std::u16string& keyword, const GURL& osdd_url, const GURL& favicon_url, const url::Origin& initiator, network::mojom::URLLoaderFactory* url_loader_factory, int render_frame_id, int32_t request_id) : fetcher_(fetcher), keyword_(keyword), osdd_url_(osdd_url), favicon_url_(favicon_url) { TemplateURLService* model = fetcher_->template_url_service_; DCHECK(model); // TemplateURLFetcher::ScheduleDownload verifies this. if (!model->loaded()) { // Start the model load and set-up waiting for it. template_url_subscription_ = model->RegisterOnLoadedCallback( base::BindOnce(&TemplateURLFetcher::RequestDelegate::OnLoaded, weak_factory_.GetWeakPtr())); model->Load(); } auto resource_request = std::make_unique(); resource_request->url = osdd_url; resource_request->request_initiator = initiator; // TODO(crbug.com/1059639): Remove |resource_type| once the request is handled // with RequestDestination without ResourceType. resource_request->resource_type = /* blink::mojom::ResourceType::kSubResource */ 6; resource_request->destination = network::mojom::RequestDestination::kEmpty; resource_request->load_flags = net::LOAD_DO_NOT_SAVE_COOKIES; simple_url_loader_ = network::SimpleURLLoader::Create( std::move(resource_request), kTrafficAnnotation); simple_url_loader_->SetAllowHttpErrorResults(true); simple_url_loader_->SetTimeoutDuration( base::Seconds(kOpenSearchTimeoutSeconds)); simple_url_loader_->SetRetryOptions( kOpenSearchRetryCount, network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE); simple_url_loader_->SetRequestID(request_id); simple_url_loader_->DownloadToString( url_loader_factory, base::BindOnce( &TemplateURLFetcher::RequestDelegate::OnSimpleLoaderComplete, weak_factory_.GetWeakPtr()), 50000 /* max_body_size */); } void TemplateURLFetcher::RequestDelegate::OnTemplateURLParsed( std::unique_ptr template_url) { template_url_ = std::move(template_url); if (!template_url_ || !template_url_->url_ref().SupportsReplacement( fetcher_->template_url_service_->search_terms_data())) { fetcher_->RequestCompleted(this); // WARNING: RequestCompleted deletes us. return; } // Wait for the model to be loaded before adding the provider. if (!fetcher_->template_url_service_->loaded()) return; AddSearchProvider(); // WARNING: AddSearchProvider deletes us. } void TemplateURLFetcher::RequestDelegate::OnLoaded() { template_url_subscription_ = {}; if (!template_url_) return; AddSearchProvider(); // WARNING: AddSearchProvider deletes us. } void TemplateURLFetcher::RequestDelegate::OnSimpleLoaderComplete( std::unique_ptr response_body) { // Validation checks. // Make sure we can still replace the keyword, i.e. the fetch was successful. if (!response_body) { fetcher_->RequestCompleted(this); // WARNING: RequestCompleted deletes us. return; } TemplateURLParser::Parse( &fetcher_->template_url_service_->search_terms_data(), *response_body.get(), TemplateURLParser::ParameterFilter(), base::BindOnce(&RequestDelegate::OnTemplateURLParsed, weak_factory_.GetWeakPtr())); } void TemplateURLFetcher::RequestDelegate::AddSearchProvider() { DCHECK(template_url_); DCHECK(!keyword_.empty()); TemplateURLService* model = fetcher_->template_url_service_; DCHECK(model); DCHECK(model->loaded()); if (!model->CanAddAutogeneratedKeyword(keyword_, GURL(template_url_->url()))) { fetcher_->RequestCompleted(this); // WARNING: Deletes us! return; } // The short name is what is shown to the user. We preserve original names // since it is better when generated keyword in many cases. TemplateURLData data(template_url_->data()); data.SetKeyword(keyword_); data.originating_url = osdd_url_; // The page may have specified a URL to use for favicons, if not, set it. if (!data.favicon_url.is_valid()) data.favicon_url = favicon_url_; // Mark the keyword as replaceable so it can be removed if necessary. // Add() will automatically remove conflicting keyword replaceable engines. data.safe_for_autoreplace = true; // Autogenerated keywords are kUnspecified active status by default. When the // active search engine feature flag is enabled, kUnspecified keywords are // inactive and cannot be triggered in the omnibox until they are activated. data.is_active = TemplateURLData::ActiveStatus::kUnspecified; model->Add(std::make_unique(data)); fetcher_->RequestCompleted(this); // WARNING: RequestCompleted deletes us. } // TemplateURLFetcher --------------------------------------------------------- TemplateURLFetcher::TemplateURLFetcher(TemplateURLService* template_url_service) : template_url_service_(template_url_service) {} TemplateURLFetcher::~TemplateURLFetcher() { } void TemplateURLFetcher::ScheduleDownload( const std::u16string& keyword, const GURL& osdd_url, const GURL& favicon_url, const url::Origin& initiator, network::mojom::URLLoaderFactory* url_loader_factory, int render_frame_id, int32_t request_id) { DCHECK(osdd_url.is_valid()); DCHECK(!keyword.empty()); if (!template_url_service_->loaded()) { // We could try to set up a callback to this function again once the model // is loaded but meh. template_url_service_->Load(); return; } const TemplateURL* template_url = template_url_service_->GetTemplateURLForKeyword(keyword); if (template_url && (!template_url->safe_for_autoreplace() || template_url->originating_url() == osdd_url)) return; // Make sure we aren't already downloading this request. for (const auto& request : requests_) { if ((request->url() == osdd_url) || (request->keyword() == keyword)) return; } requests_.push_back(std::make_unique( this, keyword, osdd_url, favicon_url, initiator, url_loader_factory, render_frame_id, request_id)); } void TemplateURLFetcher::RequestCompleted(RequestDelegate* request) { auto i = base::ranges::find(requests_, request, &std::unique_ptr::get); DCHECK(i != requests_.end()); requests_.erase(i); }