1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/search_engines/template_url_fetcher.h"
7 #include "base/functional/bind.h"
8 #include "base/memory/raw_ptr.h"
9 #include "base/memory/weak_ptr.h"
10 #include "base/ranges/algorithm.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "build/build_config.h"
14 #include "components/search_engines/template_url.h"
15 #include "components/search_engines/template_url_parser.h"
16 #include "components/search_engines/template_url_service.h"
17 #include "net/base/load_flags.h"
18 #include "net/traffic_annotation/network_traffic_annotation.h"
19 #include "services/network/public/cpp/resource_request.h"
20 #include "services/network/public/cpp/simple_url_loader.h"
21 #include "services/network/public/mojom/fetch_api.mojom-shared.h"
25 // In some network environments, silent failure can be avoided by retrying
26 // request on network change. This helps OpenSearch get through in such cases.
27 // See https://crbug.com/956689 for context.
28 constexpr int kOpenSearchRetryCount = 3;
30 // Timeout for OpenSearch description document (OSDD) fetch request.
31 // Requests for a particular resource are limited to one.
32 // Requests may not receive a response, and in that case no
33 // further requests would be allowed. The timeout cleans up failed requests
34 // so that later attempts to fetch the OSDD can be made.
35 constexpr int kOpenSearchTimeoutSeconds = 30;
37 // Traffic annotation for RequestDelegate.
38 const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
39 net::DefineNetworkTrafficAnnotation("open_search", R"(
43 "Web pages can include an OpenSearch description doc in their HTML. "
44 "In this case Chromium downloads and parses the file. The "
45 "corresponding search engine is added to the list in the browser "
46 "settings (chrome://settings/searchEngines)."
48 "User visits a web page containing a <link rel=\"search\"> tag."
55 setting: "This feature cannot be disabled in settings."
56 policy_exception_justification:
57 "Not implemented, considered not useful as this feature does not "
63 // RequestDelegate ------------------------------------------------------------
64 class TemplateURLFetcher::RequestDelegate {
66 RequestDelegate(TemplateURLFetcher* fetcher,
67 const std::u16string& keyword,
69 const GURL& favicon_url,
70 const url::Origin& initiator,
71 network::mojom::URLLoaderFactory* url_loader_factory,
75 RequestDelegate(const RequestDelegate&) = delete;
76 RequestDelegate& operator=(const RequestDelegate&) = delete;
78 // If data contains a valid OSDD, a TemplateURL is created and added to
79 // the TemplateURLService.
80 void OnSimpleLoaderComplete(std::unique_ptr<std::string> response_body);
83 GURL url() const { return osdd_url_; }
86 std::u16string keyword() const { return keyword_; }
89 void OnTemplateURLParsed(std::unique_ptr<TemplateURL> template_url);
91 void AddSearchProvider();
93 std::unique_ptr<network::SimpleURLLoader> simple_url_loader_;
94 raw_ptr<TemplateURLFetcher> fetcher_;
95 std::unique_ptr<TemplateURL> template_url_;
96 std::u16string keyword_;
98 const GURL favicon_url_;
100 base::CallbackListSubscription template_url_subscription_;
102 base::WeakPtrFactory<RequestDelegate> weak_factory_{this};
105 TemplateURLFetcher::RequestDelegate::RequestDelegate(
106 TemplateURLFetcher* fetcher,
107 const std::u16string& keyword,
108 const GURL& osdd_url,
109 const GURL& favicon_url,
110 const url::Origin& initiator,
111 network::mojom::URLLoaderFactory* url_loader_factory,
117 favicon_url_(favicon_url) {
118 TemplateURLService* model = fetcher_->template_url_service_;
119 DCHECK(model); // TemplateURLFetcher::ScheduleDownload verifies this.
121 if (!model->loaded()) {
122 // Start the model load and set-up waiting for it.
123 template_url_subscription_ = model->RegisterOnLoadedCallback(
124 base::BindOnce(&TemplateURLFetcher::RequestDelegate::OnLoaded,
125 weak_factory_.GetWeakPtr()));
129 auto resource_request = std::make_unique<network::ResourceRequest>();
130 resource_request->url = osdd_url;
131 resource_request->request_initiator = initiator;
132 // TODO(crbug.com/1059639): Remove |resource_type| once the request is handled
133 // with RequestDestination without ResourceType.
134 resource_request->resource_type =
135 /* blink::mojom::ResourceType::kSubResource */ 6;
136 resource_request->destination = network::mojom::RequestDestination::kEmpty;
137 resource_request->load_flags = net::LOAD_DO_NOT_SAVE_COOKIES;
138 simple_url_loader_ = network::SimpleURLLoader::Create(
139 std::move(resource_request), kTrafficAnnotation);
140 simple_url_loader_->SetAllowHttpErrorResults(true);
141 simple_url_loader_->SetTimeoutDuration(
142 base::Seconds(kOpenSearchTimeoutSeconds));
143 simple_url_loader_->SetRetryOptions(
144 kOpenSearchRetryCount, network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
145 simple_url_loader_->SetRequestID(request_id);
146 simple_url_loader_->DownloadToString(
149 &TemplateURLFetcher::RequestDelegate::OnSimpleLoaderComplete,
150 weak_factory_.GetWeakPtr()),
151 50000 /* max_body_size */);
154 void TemplateURLFetcher::RequestDelegate::OnTemplateURLParsed(
155 std::unique_ptr<TemplateURL> template_url) {
156 template_url_ = std::move(template_url);
158 if (!template_url_ ||
159 !template_url_->url_ref().SupportsReplacement(
160 fetcher_->template_url_service_->search_terms_data())) {
161 fetcher_->RequestCompleted(this);
162 // WARNING: RequestCompleted deletes us.
166 // Wait for the model to be loaded before adding the provider.
167 if (!fetcher_->template_url_service_->loaded())
170 // WARNING: AddSearchProvider deletes us.
173 void TemplateURLFetcher::RequestDelegate::OnLoaded() {
174 template_url_subscription_ = {};
178 // WARNING: AddSearchProvider deletes us.
181 void TemplateURLFetcher::RequestDelegate::OnSimpleLoaderComplete(
182 std::unique_ptr<std::string> response_body) {
183 // Validation checks.
184 // Make sure we can still replace the keyword, i.e. the fetch was successful.
185 if (!response_body) {
186 fetcher_->RequestCompleted(this);
187 // WARNING: RequestCompleted deletes us.
191 TemplateURLParser::Parse(
192 &fetcher_->template_url_service_->search_terms_data(),
193 *response_body.get(), TemplateURLParser::ParameterFilter(),
194 base::BindOnce(&RequestDelegate::OnTemplateURLParsed,
195 weak_factory_.GetWeakPtr()));
198 void TemplateURLFetcher::RequestDelegate::AddSearchProvider() {
199 DCHECK(template_url_);
200 DCHECK(!keyword_.empty());
201 TemplateURLService* model = fetcher_->template_url_service_;
203 DCHECK(model->loaded());
205 if (!model->CanAddAutogeneratedKeyword(keyword_,
206 GURL(template_url_->url()))) {
207 fetcher_->RequestCompleted(this); // WARNING: Deletes us!
211 // The short name is what is shown to the user. We preserve original names
212 // since it is better when generated keyword in many cases.
213 TemplateURLData data(template_url_->data());
214 data.SetKeyword(keyword_);
215 data.originating_url = osdd_url_;
217 // The page may have specified a URL to use for favicons, if not, set it.
218 if (!data.favicon_url.is_valid())
219 data.favicon_url = favicon_url_;
221 // Mark the keyword as replaceable so it can be removed if necessary.
222 // Add() will automatically remove conflicting keyword replaceable engines.
223 data.safe_for_autoreplace = true;
225 // Autogenerated keywords are kUnspecified active status by default. When the
226 // active search engine feature flag is enabled, kUnspecified keywords are
227 // inactive and cannot be triggered in the omnibox until they are activated.
228 data.is_active = TemplateURLData::ActiveStatus::kUnspecified;
230 model->Add(std::make_unique<TemplateURL>(data));
232 fetcher_->RequestCompleted(this);
233 // WARNING: RequestCompleted deletes us.
236 // TemplateURLFetcher ---------------------------------------------------------
238 TemplateURLFetcher::TemplateURLFetcher(TemplateURLService* template_url_service)
239 : template_url_service_(template_url_service) {}
241 TemplateURLFetcher::~TemplateURLFetcher() {
244 void TemplateURLFetcher::ScheduleDownload(
245 const std::u16string& keyword,
246 const GURL& osdd_url,
247 const GURL& favicon_url,
248 const url::Origin& initiator,
249 network::mojom::URLLoaderFactory* url_loader_factory,
251 int32_t request_id) {
252 DCHECK(osdd_url.is_valid());
253 DCHECK(!keyword.empty());
255 if (!template_url_service_->loaded()) {
256 // We could try to set up a callback to this function again once the model
257 // is loaded but meh.
258 template_url_service_->Load();
262 const TemplateURL* template_url =
263 template_url_service_->GetTemplateURLForKeyword(keyword);
264 if (template_url && (!template_url->safe_for_autoreplace() ||
265 template_url->originating_url() == osdd_url))
268 // Make sure we aren't already downloading this request.
269 for (const auto& request : requests_) {
270 if ((request->url() == osdd_url) || (request->keyword() == keyword))
274 requests_.push_back(std::make_unique<RequestDelegate>(
275 this, keyword, osdd_url, favicon_url, initiator, url_loader_factory,
276 render_frame_id, request_id));
279 void TemplateURLFetcher::RequestCompleted(RequestDelegate* request) {
280 auto i = base::ranges::find(requests_, request,
281 &std::unique_ptr<RequestDelegate>::get);
282 DCHECK(i != requests_.end());