|  | // Copyright 2016 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/quirks/quirks_client.h" | 
|  |  | 
|  | #include "base/base64.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/json/json_reader.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/task_runner_util.h" | 
|  | #include "components/prefs/scoped_user_pref_update.h" | 
|  | #include "components/quirks/quirks_manager.h" | 
|  | #include "components/version_info/version_info.h" | 
|  | #include "net/base/escape.h" | 
|  | #include "net/base/load_flags.h" | 
|  | #include "net/http/http_status_code.h" | 
|  | #include "services/network/public/cpp/simple_url_loader.h" | 
|  |  | 
|  | namespace quirks { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char kQuirksUrlFormat[] = | 
|  | "https://chromeosquirksserver-pa.googleapis.com/v2/display/%s/clients" | 
|  | "/chromeos/M%d?"; | 
|  |  | 
|  | const int kMaxServerFailures = 10; | 
|  |  | 
|  | const net::BackoffEntry::Policy kDefaultBackoffPolicy = { | 
|  | 1,                // Initial errors before applying backoff | 
|  | 10000,            // 10 seconds. | 
|  | 2,                // Factor by which the waiting time will be multiplied. | 
|  | 0,                // Random fuzzing percentage. | 
|  | 1000 * 3600 * 6,  // Max wait between requests = 6 hours. | 
|  | -1,               // Don't discard entry. | 
|  | true,             // Use initial delay after first error. | 
|  | }; | 
|  |  | 
|  | bool WriteIccFile(const base::FilePath file_path, const std::string& data) { | 
|  | int bytes_written = base::WriteFile(file_path, data.data(), data.length()); | 
|  | if (bytes_written == -1) | 
|  | PLOG(ERROR) << "Write failed: " << file_path.value(); | 
|  | else | 
|  | VLOG(1) << bytes_written << "bytes written to: " << file_path.value(); | 
|  |  | 
|  | return (bytes_written != -1); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | //////////////////////////////////////////////////////////////////////////////// | 
|  | // QuirksClient | 
|  |  | 
|  | QuirksClient::QuirksClient(int64_t product_id, | 
|  | const std::string& display_name, | 
|  | const RequestFinishedCallback& on_request_finished, | 
|  | QuirksManager* manager) | 
|  | : product_id_(product_id), | 
|  | display_name_(display_name), | 
|  | on_request_finished_(on_request_finished), | 
|  | manager_(manager), | 
|  | icc_path_(manager->delegate()->GetDisplayProfileDirectory().Append( | 
|  | IdToFileName(product_id))), | 
|  | backoff_entry_(&kDefaultBackoffPolicy), | 
|  | weak_ptr_factory_(this) {} | 
|  |  | 
|  | QuirksClient::~QuirksClient() {} | 
|  |  | 
|  | void QuirksClient::StartDownload() { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  |  | 
|  | // URL of icc file on Quirks Server. | 
|  | int major_version = atoi(version_info::GetVersionNumber().c_str()); | 
|  | std::string url = base::StringPrintf( | 
|  | kQuirksUrlFormat, IdToHexString(product_id_).c_str(), major_version); | 
|  |  | 
|  | if (!display_name_.empty()) { | 
|  | url += | 
|  | "display_name=" + net::EscapeQueryParamValue(display_name_, true) + "&"; | 
|  | } | 
|  |  | 
|  | VLOG(2) << "Preparing to download\n  " << url << "\nto file " | 
|  | << icc_path_.value(); | 
|  |  | 
|  | url += "key=" + manager_->delegate()->GetApiKey(); | 
|  |  | 
|  | auto resource_request = std::make_unique<network::ResourceRequest>(); | 
|  | resource_request->url = GURL(url); | 
|  | resource_request->load_flags = | 
|  | net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE; | 
|  | resource_request->allow_credentials = false; | 
|  |  | 
|  | net::NetworkTrafficAnnotationTag traffic_annotation = | 
|  | net::DefineNetworkTrafficAnnotation("quirks_display_fetcher", R"( | 
|  | semantics { | 
|  | sender: "Quirks" | 
|  | description: "Download custom display calibration file." | 
|  | trigger: | 
|  | "Chrome OS attempts to download monitor calibration files on" | 
|  | "first device login, and then once every 30 days." | 
|  | data: "ICC files to calibrate and improve the quality of a display." | 
|  | destination: GOOGLE_OWNED_SERVICE | 
|  | } | 
|  | policy { | 
|  | cookies_allowed: NO | 
|  | chrome_policy { | 
|  | DeviceQuirksDownloadEnabled { | 
|  | DeviceQuirksDownloadEnabled: false | 
|  | } | 
|  | } | 
|  | } | 
|  | )"); | 
|  |  | 
|  | url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request), | 
|  | traffic_annotation); | 
|  | url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie( | 
|  | manager_->url_loader_factory(), | 
|  | base::BindOnce(&QuirksClient::OnDownloadComplete, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void QuirksClient::OnDownloadComplete( | 
|  | std::unique_ptr<std::string> response_body) { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  |  | 
|  | // Take ownership of the loader in this scope. | 
|  | std::unique_ptr<network::SimpleURLLoader> url_loader = std::move(url_loader_); | 
|  |  | 
|  | int response_code = 0; | 
|  | if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers) | 
|  | response_code = url_loader->ResponseInfo()->headers->response_code(); | 
|  |  | 
|  | VLOG(2) << "QuirksClient::OnURLFetchComplete():" | 
|  | << "  net_error=" << url_loader->NetError() | 
|  | << ", response_code=" << response_code; | 
|  |  | 
|  | if (response_code == net::HTTP_NOT_FOUND) { | 
|  | VLOG(1) << IdToFileName(product_id_) << " not found on Quirks server."; | 
|  | Shutdown(false); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (url_loader->NetError() != net::OK) { | 
|  | if (backoff_entry_.failure_count() >= kMaxServerFailures) { | 
|  | // After 10 retires (5+ hours), give up, and try again in a month. | 
|  | VLOG(1) << "Too many retries; Quirks Client shutting down."; | 
|  | Shutdown(false); | 
|  | return; | 
|  | } | 
|  | Retry(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | DCHECK(response_body);  // Guaranteed to be valid if NetError() is net::OK. | 
|  | VLOG(2) << "Quirks server response:\n" << *response_body; | 
|  |  | 
|  | // Parse response data and write to file on file thread. | 
|  | std::string data; | 
|  | if (!ParseResult(*response_body, &data)) { | 
|  | Shutdown(false); | 
|  | return; | 
|  | } | 
|  |  | 
|  | base::PostTaskAndReplyWithResult( | 
|  | manager_->task_runner(), FROM_HERE, | 
|  | base::Bind(&WriteIccFile, icc_path_, data), | 
|  | base::Bind(&QuirksClient::Shutdown, weak_ptr_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | void QuirksClient::Shutdown(bool success) { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  | on_request_finished_.Run(success ? icc_path_ : base::FilePath(), true); | 
|  | manager_->ClientFinished(this); | 
|  | } | 
|  |  | 
|  | void QuirksClient::Retry() { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  | backoff_entry_.InformOfRequest(false); | 
|  | const base::TimeDelta delay = backoff_entry_.GetTimeUntilRelease(); | 
|  |  | 
|  | VLOG(1) << "Schedule next Quirks download attempt in " << delay.InSecondsF() | 
|  | << " seconds (retry = " << backoff_entry_.failure_count() << ")."; | 
|  | request_scheduled_.Start(FROM_HERE, delay, this, | 
|  | &QuirksClient::StartDownload); | 
|  | } | 
|  |  | 
|  | bool QuirksClient::ParseResult(const std::string& result, std::string* data) { | 
|  | std::string data64; | 
|  | const base::DictionaryValue* dict; | 
|  | std::unique_ptr<base::Value> json = base::JSONReader::Read(result); | 
|  | if (!json || !json->GetAsDictionary(&dict) || | 
|  | !dict->GetString("icc", &data64)) { | 
|  | VLOG(1) << "Failed to parse JSON icc data"; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!base::Base64Decode(data64, data)) { | 
|  | VLOG(1) << "Failed to decode Base64 icc data"; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | }  // namespace quirks |