1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/search/one_google_bar/one_google_bar_loader_impl.h"
6 
7 #include <string>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/command_line.h"
13 #include "base/json/json_writer.h"
14 #include "base/strings/string_util.h"
15 #include "base/values.h"
16 #include "build/build_config.h"
17 #include "chrome/browser/search/one_google_bar/one_google_bar_data.h"
18 #include "chrome/common/chrome_content_client.h"
19 #include "chrome/common/webui_url_constants.h"
20 #include "components/google/core/common/google_util.h"
21 #include "components/variations/net/variations_http_headers.h"
22 #include "net/base/load_flags.h"
23 #include "net/base/url_util.h"
24 #include "net/http/http_status_code.h"
25 #include "net/traffic_annotation/network_traffic_annotation.h"
26 #include "services/data_decoder/public/cpp/data_decoder.h"
27 #include "services/network/public/cpp/resource_request.h"
28 #include "services/network/public/cpp/shared_url_loader_factory.h"
29 #include "services/network/public/cpp/simple_url_loader.h"
30 #include "url/gurl.h"
31 
32 #if defined(OS_CHROMEOS)
33 #include "chrome/browser/signin/chrome_signin_helper.h"
34 #include "components/signin/core/browser/chrome_connected_header_helper.h"
35 #include "components/signin/core/browser/signin_header_helper.h"
36 #endif
37 
38 namespace {
39 
40 const char kNewTabOgbApiPath[] = "/async/newtab_ogb";
41 
42 const char kResponsePreamble[] = ")]}'";
43 
44 // This namespace contains helpers to extract SafeHtml-wrapped strings (see
45 // https://github.com/google/safe-html-types) from the response json. If there
46 // is ever a C++ version of the SafeHtml types, we should consider using that
47 // instead of these custom functions.
48 namespace safe_html {
49 
GetImpl(const base::DictionaryValue & dict,const std::string & name,const std::string & wrapped_field_name,std::string * out)50 bool GetImpl(const base::DictionaryValue& dict,
51              const std::string& name,
52              const std::string& wrapped_field_name,
53              std::string* out) {
54   const base::DictionaryValue* value = nullptr;
55   if (!dict.GetDictionary(name, &value)) {
56     out->clear();
57     return false;
58   }
59 
60   if (!value->GetString(wrapped_field_name, out)) {
61     out->clear();
62     return false;
63   }
64 
65   return true;
66 }
67 
GetHtml(const base::DictionaryValue & dict,const std::string & name,std::string * out)68 bool GetHtml(const base::DictionaryValue& dict,
69              const std::string& name,
70              std::string* out) {
71   return GetImpl(dict, name,
72                  "private_do_not_access_or_else_safe_html_wrapped_value", out);
73 }
74 
GetScript(const base::DictionaryValue & dict,const std::string & name,std::string * out)75 bool GetScript(const base::DictionaryValue& dict,
76                const std::string& name,
77                std::string* out) {
78   return GetImpl(dict, name,
79                  "private_do_not_access_or_else_safe_script_wrapped_value",
80                  out);
81 }
82 
GetStyleSheet(const base::DictionaryValue & dict,const std::string & name,std::string * out)83 bool GetStyleSheet(const base::DictionaryValue& dict,
84                    const std::string& name,
85                    std::string* out) {
86   return GetImpl(dict, name,
87                  "private_do_not_access_or_else_safe_style_sheet_wrapped_value",
88                  out);
89 }
90 
91 }  // namespace safe_html
92 
JsonToOGBData(const base::Value & value)93 base::Optional<OneGoogleBarData> JsonToOGBData(const base::Value& value) {
94   const base::DictionaryValue* dict = nullptr;
95   if (!value.GetAsDictionary(&dict)) {
96     DVLOG(1) << "Parse error: top-level dictionary not found";
97     return base::nullopt;
98   }
99 
100   const base::DictionaryValue* update = nullptr;
101   if (!dict->GetDictionary("update", &update)) {
102     DVLOG(1) << "Parse error: no update";
103     return base::nullopt;
104   }
105 
106   const base::Value* language = nullptr;
107   std::string language_code;
108   if (update->Get("language_code", &language)) {
109     language_code = language->GetString();
110   }
111 
112   const base::DictionaryValue* one_google_bar = nullptr;
113   if (!update->GetDictionary("ogb", &one_google_bar)) {
114     DVLOG(1) << "Parse error: no ogb";
115     return base::nullopt;
116   }
117 
118   OneGoogleBarData result;
119   result.language_code = language_code;
120 
121   if (!safe_html::GetHtml(*one_google_bar, "html", &result.bar_html)) {
122     DVLOG(1) << "Parse error: no html";
123     return base::nullopt;
124   }
125 
126   const base::DictionaryValue* page_hooks = nullptr;
127   if (!one_google_bar->GetDictionary("page_hooks", &page_hooks)) {
128     DVLOG(1) << "Parse error: no page_hooks";
129     return base::nullopt;
130   }
131 
132   safe_html::GetScript(*page_hooks, "in_head_script", &result.in_head_script);
133   safe_html::GetStyleSheet(*page_hooks, "in_head_style", &result.in_head_style);
134   safe_html::GetScript(*page_hooks, "after_bar_script",
135                        &result.after_bar_script);
136   safe_html::GetHtml(*page_hooks, "end_of_body_html", &result.end_of_body_html);
137   safe_html::GetScript(*page_hooks, "end_of_body_script",
138                        &result.end_of_body_script);
139 
140   return result;
141 }
142 
143 }  // namespace
144 
145 class OneGoogleBarLoaderImpl::AuthenticatedURLLoader {
146  public:
147   using LoadDoneCallback =
148       base::OnceCallback<void(const network::SimpleURLLoader* simple_loader,
149                               std::unique_ptr<std::string> response_body)>;
150 
151   AuthenticatedURLLoader(
152       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
153       GURL api_url,
154       bool account_consistency_mirror_required,
155       LoadDoneCallback callback);
156   ~AuthenticatedURLLoader() = default;
157 
158   void Start();
159 
160  private:
161   void SetRequestHeaders(network::ResourceRequest* request) const;
162 
163   void OnURLLoaderComplete(std::unique_ptr<std::string> response_body);
164 
165   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
166   const GURL api_url_;
167 #if defined(OS_CHROMEOS)
168   const bool account_consistency_mirror_required_;
169 #endif
170 
171   LoadDoneCallback callback_;
172 
173   // The underlying SimpleURLLoader which does the actual load.
174   std::unique_ptr<network::SimpleURLLoader> simple_loader_;
175 };
176 
AuthenticatedURLLoader(scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,GURL api_url,bool account_consistency_mirror_required,LoadDoneCallback callback)177 OneGoogleBarLoaderImpl::AuthenticatedURLLoader::AuthenticatedURLLoader(
178     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
179     GURL api_url,
180     bool account_consistency_mirror_required,
181     LoadDoneCallback callback)
182     : url_loader_factory_(url_loader_factory),
183       api_url_(std::move(api_url)),
184 #if defined(OS_CHROMEOS)
185       account_consistency_mirror_required_(account_consistency_mirror_required),
186 #endif
187       callback_(std::move(callback)) {
188 }
189 
SetRequestHeaders(network::ResourceRequest * request) const190 void OneGoogleBarLoaderImpl::AuthenticatedURLLoader::SetRequestHeaders(
191     network::ResourceRequest* request) const {
192   variations::AppendVariationsHeaderUnknownSignedIn(
193       api_url_, variations::InIncognito::kNo, request);
194 #if defined(OS_CHROMEOS)
195   signin::ChromeConnectedHeaderHelper chrome_connected_header_helper(
196       account_consistency_mirror_required_
197           ? signin::AccountConsistencyMethod::kMirror
198           : signin::AccountConsistencyMethod::kDisabled);
199   int profile_mode = signin::PROFILE_MODE_DEFAULT;
200   if (account_consistency_mirror_required_) {
201     // For the child account case (where currently
202     // |account_consistency_mirror_required_| is true on Chrome OS), we always
203     // want to disable adding an account and going to incognito.
204     profile_mode = signin::PROFILE_MODE_INCOGNITO_DISABLED |
205                    signin::PROFILE_MODE_ADD_ACCOUNT_DISABLED;
206   }
207 
208   // TODO(crbug.com/1134045): Check whether the child account status should also
209   // be sent in the Mirror request header when loading the local version of
210   // OneGoogleBar.
211   std::string chrome_connected_header_value =
212       chrome_connected_header_helper.BuildRequestHeader(
213           /*is_header_request=*/true, api_url_,
214           // Gaia ID is only needed for (drive|docs).google.com.
215           /*gaia_id=*/std::string(),
216           /* is_child_account=*/base::nullopt, profile_mode,
217           signin::kChromeMirrorHeaderSource,
218           /*force_account_consistency=*/false);
219   if (!chrome_connected_header_value.empty()) {
220     request->headers.SetHeader(signin::kChromeConnectedHeader,
221                                chrome_connected_header_value);
222   }
223 #endif
224 }
225 
Start()226 void OneGoogleBarLoaderImpl::AuthenticatedURLLoader::Start() {
227   net::NetworkTrafficAnnotationTag traffic_annotation =
228       net::DefineNetworkTrafficAnnotation("one_google_bar_service", R"(
229         semantics {
230           sender: "One Google Bar Service"
231           description: "Downloads the 'One Google' bar."
232           trigger:
233             "Displaying the new tab page on Desktop, if Google is the "
234             "configured search provider."
235           data: "Credentials if user is signed in."
236           destination: GOOGLE_OWNED_SERVICE
237         }
238         policy {
239           cookies_allowed: YES
240           cookies_store: "user"
241           setting:
242             "Users can control this feature via selecting a non-Google default "
243             "search engine in Chrome settings under 'Search Engine'."
244           chrome_policy {
245             DefaultSearchProviderEnabled {
246               policy_options {mode: MANDATORY}
247               DefaultSearchProviderEnabled: false
248             }
249           }
250         })");
251 
252   auto resource_request = std::make_unique<network::ResourceRequest>();
253   resource_request->url = api_url_;
254   resource_request->credentials_mode =
255       network::mojom::CredentialsMode::kInclude;
256   SetRequestHeaders(resource_request.get());
257   resource_request->request_initiator =
258       url::Origin::Create(GURL(chrome::kChromeUINewTabURL));
259 
260   simple_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
261                                                     traffic_annotation);
262   simple_loader_->DownloadToString(
263       url_loader_factory_.get(),
264       base::BindOnce(
265           &OneGoogleBarLoaderImpl::AuthenticatedURLLoader::OnURLLoaderComplete,
266           base::Unretained(this)),
267       1024 * 1024);
268 }
269 
OnURLLoaderComplete(std::unique_ptr<std::string> response_body)270 void OneGoogleBarLoaderImpl::AuthenticatedURLLoader::OnURLLoaderComplete(
271     std::unique_ptr<std::string> response_body) {
272   std::move(callback_).Run(simple_loader_.get(), std::move(response_body));
273 }
274 
OneGoogleBarLoaderImpl(scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,const std::string & application_locale,bool account_consistency_mirror_required)275 OneGoogleBarLoaderImpl::OneGoogleBarLoaderImpl(
276     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
277     const std::string& application_locale,
278     bool account_consistency_mirror_required)
279     : url_loader_factory_(url_loader_factory),
280       application_locale_(application_locale),
281       account_consistency_mirror_required_(
282           account_consistency_mirror_required) {}
283 
284 OneGoogleBarLoaderImpl::~OneGoogleBarLoaderImpl() = default;
285 
Load(OneGoogleCallback callback)286 void OneGoogleBarLoaderImpl::Load(OneGoogleCallback callback) {
287   callbacks_.push_back(std::move(callback));
288 
289   // Note: If there is an ongoing request, abandon it. It's possible that
290   // something has changed in the meantime (e.g. signin state) that would make
291   // the result obsolete.
292   pending_request_ = std::make_unique<AuthenticatedURLLoader>(
293       url_loader_factory_, GetApiUrl(), account_consistency_mirror_required_,
294       base::BindOnce(&OneGoogleBarLoaderImpl::LoadDone,
295                      base::Unretained(this)));
296   pending_request_->Start();
297 }
298 
GetLoadURLForTesting() const299 GURL OneGoogleBarLoaderImpl::GetLoadURLForTesting() const {
300   return GetApiUrl();
301 }
302 
SetAdditionalQueryParams(const std::string & value)303 bool OneGoogleBarLoaderImpl::SetAdditionalQueryParams(
304     const std::string& value) {
305   if (additional_query_params_ == value) {
306     return false;
307   }
308   additional_query_params_ = value;
309   return true;
310 }
311 
GetApiUrl() const312 GURL OneGoogleBarLoaderImpl::GetApiUrl() const {
313   GURL api_url;
314   GURL google_base_url = google_util::CommandLineGoogleBaseURL();
315   if (!google_base_url.is_valid()) {
316     google_base_url = GURL(google_util::kGoogleHomepageURL);
317   }
318 
319   api_url = google_base_url.Resolve(kNewTabOgbApiPath);
320 
321   // Add the "hl=" parameter.
322   if (additional_query_params_.find("&hl=") == std::string::npos) {
323     api_url = net::AppendQueryParameter(api_url, "hl", application_locale_);
324   }
325 
326   // Add the "async=" parameter. We can't use net::AppendQueryParameter for
327   // this because we need the ":" to remain unescaped.
328   GURL::Replacements replacements;
329   std::string query = api_url.query();
330   query += additional_query_params_;
331   if (additional_query_params_.find("&async=") == std::string::npos) {
332     query += "&async=fixed:0";
333   }
334   if (query.at(0) == '&') {
335     query = query.substr(1);
336   }
337   replacements.SetQueryStr(query);
338   return api_url.ReplaceComponents(replacements);
339 }
340 
LoadDone(const network::SimpleURLLoader * simple_loader,std::unique_ptr<std::string> response_body)341 void OneGoogleBarLoaderImpl::LoadDone(
342     const network::SimpleURLLoader* simple_loader,
343     std::unique_ptr<std::string> response_body) {
344   // The loader will be deleted when the request is handled.
345   std::unique_ptr<AuthenticatedURLLoader> deleter(std::move(pending_request_));
346 
347   if (!response_body) {
348     // This represents network errors (i.e. the server did not provide a
349     // response).
350     DVLOG(1) << "Request failed with error: " << simple_loader->NetError();
351     Respond(Status::TRANSIENT_ERROR, base::nullopt);
352     return;
353   }
354 
355   std::string response;
356   response.swap(*response_body);
357 
358   // The response may start with )]}'. Ignore this.
359   if (base::StartsWith(response, kResponsePreamble,
360                        base::CompareCase::SENSITIVE)) {
361     response = response.substr(strlen(kResponsePreamble));
362   }
363 
364   data_decoder::DataDecoder::ParseJsonIsolated(
365       response, base::BindOnce(&OneGoogleBarLoaderImpl::JsonParsed,
366                                weak_ptr_factory_.GetWeakPtr()));
367 }
368 
JsonParsed(data_decoder::DataDecoder::ValueOrError result)369 void OneGoogleBarLoaderImpl::JsonParsed(
370     data_decoder::DataDecoder::ValueOrError result) {
371   if (!result.value) {
372     DVLOG(1) << "Parsing JSON failed: " << *result.error;
373     Respond(Status::FATAL_ERROR, base::nullopt);
374     return;
375   }
376 
377   base::Optional<OneGoogleBarData> data = JsonToOGBData(*result.value);
378   Respond(data.has_value() ? Status::OK : Status::FATAL_ERROR, data);
379 }
380 
Respond(Status status,const base::Optional<OneGoogleBarData> & data)381 void OneGoogleBarLoaderImpl::Respond(
382     Status status,
383     const base::Optional<OneGoogleBarData>& data) {
384   for (auto& callback : callbacks_) {
385     std::move(callback).Run(status, data);
386   }
387   callbacks_.clear();
388 }
389