| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/query_tiles/internal/tile_fetcher.h" |
| |
| #include <utility> |
| |
| #include "base/lazy_instance.h" |
| #include "components/query_tiles/internal/stats.h" |
| #include "net/base/url_util.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_status_code.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| |
| namespace query_tiles { |
| namespace { |
| |
| // An override server URL for testing. |
| base::LazyInstance<GURL>::Leaky g_override_url_for_testing; |
| |
| const char kRequestContentType[] = "application/x-protobuf"; |
| |
| constexpr net::NetworkTrafficAnnotationTag kQueryTilesFetcherTrafficAnnotation = |
| net::DefineNetworkTrafficAnnotation("query_tiles_fetcher", R"( |
| semantics { |
| sender: "Query Tiles Fetcher" |
| description: |
| "Fetches RPC for query tiles on Android NTP and omnibox." |
| trigger: |
| "A priodic TileBackgroundTask will always be scheduled to " |
| "fetch RPC from server, unless the feature is disabled " |
| "or suspended." |
| data: "Country code and accepted languages will be sent via " |
| "the header. No user information is sent." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: "Disabled if a non-Google search engine is used." |
| chrome_policy { |
| DefaultSearchProviderEnabled { |
| DefaultSearchProviderEnabled: false |
| } |
| } |
| } |
| )"); |
| |
| class TileFetcherImpl : public TileFetcher { |
| public: |
| TileFetcherImpl( |
| const GURL& url, |
| const std::string& country_code, |
| const std::string& accept_languages, |
| const std::string& api_key, |
| const std::string& experiment_tag, |
| const std::string& client_version, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) |
| : url_loader_factory_(url_loader_factory), |
| url_(url), |
| country_code_(country_code), |
| accept_languages_(accept_languages), |
| api_key_(api_key), |
| experiment_tag_(experiment_tag), |
| client_version_(client_version), |
| tile_info_request_status_(TileInfoRequestStatus::kInit) {} |
| |
| private: |
| // TileFetcher implementation. |
| void StartFetchForTiles(FinishedCallback callback) override { |
| auto resource_request = BuildGetRequest(); |
| if (!resource_request) |
| return; |
| url_loader_ = network::SimpleURLLoader::Create( |
| std::move(resource_request), kQueryTilesFetcherTrafficAnnotation); |
| url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie( |
| url_loader_factory_.get(), |
| base::BindOnce(&TileFetcherImpl::OnDownloadComplete, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| // Build the request to get tile info. |
| std::unique_ptr<network::ResourceRequest> BuildGetRequest() { |
| if (url_.is_empty() && g_override_url_for_testing.Get().is_empty()) |
| return nullptr; |
| auto request = std::make_unique<network::ResourceRequest>(); |
| request->method = net::HttpRequestHeaders::kGetMethod; |
| request->headers.SetHeader("x-goog-api-key", api_key_); |
| request->headers.SetHeader("X-Client-Version", client_version_); |
| request->headers.SetHeader(net::HttpRequestHeaders::kContentType, |
| kRequestContentType); |
| if (!g_override_url_for_testing.Get().is_empty()) { |
| request->url = g_override_url_for_testing.Get(); |
| } else { |
| request->url = net::AppendOrReplaceQueryParameter(url_, "country_code", |
| country_code_); |
| if (!experiment_tag_.empty()) { |
| request->url = net::AppendOrReplaceQueryParameter( |
| request->url, "experiment_tag", experiment_tag_); |
| } |
| } |
| if (!accept_languages_.empty()) { |
| request->headers.SetHeader(net::HttpRequestHeaders::kAcceptLanguage, |
| accept_languages_); |
| } |
| |
| return request; |
| } |
| |
| bool ShouldSuspendDueToNetError() { |
| auto error_code = url_loader_->NetError(); |
| stats::RecordTileFetcherNetErrorCode(error_code); |
| switch (error_code) { |
| case net::ERR_BLOCKED_BY_ADMINISTRATOR: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool ShouldSuspend(int response_code) { |
| switch (response_code) { |
| case net::HTTP_NOT_IMPLEMENTED: |
| case net::HTTP_FORBIDDEN: |
| return true; |
| default: |
| return ShouldSuspendDueToNetError(); |
| } |
| } |
| |
| // Called after receiving HTTP response. Processes the response code and net |
| // error. |
| void OnDownloadComplete(FinishedCallback callback, |
| std::unique_ptr<std::string> response_body) { |
| int response_code = -1; |
| if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers) |
| response_code = url_loader_->ResponseInfo()->headers->response_code(); |
| |
| if (response_code >= 200 && response_code < 300 && response_body) { |
| tile_info_request_status_ = TileInfoRequestStatus::kSuccess; |
| } else { |
| tile_info_request_status_ = ShouldSuspend(response_code) |
| ? TileInfoRequestStatus::kShouldSuspend |
| : TileInfoRequestStatus::kFailure; |
| } |
| stats::RecordTileFetcherResponseCode(response_code); |
| std::move(callback).Run(tile_info_request_status_, |
| std::move(response_body)); |
| tile_info_request_status_ = TileInfoRequestStatus::kInit; |
| url_loader_.reset(); |
| } |
| |
| void SetServerUrl(const GURL& url) override { url_ = url; } |
| |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_; |
| |
| // Simple URL loader to fetch proto from network. |
| std::unique_ptr<network::SimpleURLLoader> url_loader_; |
| |
| // Params of resource request. |
| GURL url_; |
| std::string country_code_; |
| std::string accept_languages_; |
| std::string api_key_; |
| std::string experiment_tag_; |
| std::string client_version_; |
| |
| // Status of the tile info request. |
| TileInfoRequestStatus tile_info_request_status_; |
| |
| base::WeakPtrFactory<TileFetcherImpl> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<TileFetcher> TileFetcher::Create( |
| const GURL& url, |
| const std::string& country_code, |
| const std::string& accept_languages, |
| const std::string& api_key, |
| const std::string& experiment_tag, |
| const std::string& client_version, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) { |
| return std::make_unique<TileFetcherImpl>(url, country_code, accept_languages, |
| api_key, experiment_tag, |
| client_version, url_loader_factory); |
| } |
| |
| // static |
| void TileFetcher::SetOverrideURLForTesting(const GURL& url) { |
| g_override_url_for_testing.Get() = url; |
| } |
| |
| TileFetcher::TileFetcher() = default; |
| TileFetcher::~TileFetcher() = default; |
| |
| } // namespace query_tiles |