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