blob: 13eea620eabdd6ebc9c9e3270c1b59a2d7f0f37f [file] [log] [blame]
glevin5dd01a72016-03-23 23:08:121// Copyright 2016 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 "components/quirks/quirks_client.h"
6
7#include "base/base64.h"
8#include "base/files/file_util.h"
9#include "base/json/json_reader.h"
10#include "base/strings/stringprintf.h"
11#include "base/task_runner_util.h"
glevin5dd01a72016-03-23 23:08:1212#include "components/prefs/scoped_user_pref_update.h"
13#include "components/quirks/quirks_manager.h"
14#include "components/version_info/version_info.h"
glevin2aa9dd12017-03-16 21:07:5915#include "net/base/escape.h"
glevin5dd01a72016-03-23 23:08:1216#include "net/base/load_flags.h"
17#include "net/http/http_status_code.h"
18#include "net/url_request/url_fetcher.h"
19#include "net/url_request/url_request_context_getter.h"
20
21namespace quirks {
22
23namespace {
24
25const char kQuirksUrlFormat[] =
glevin54ee8062016-04-06 13:40:4926 "https://chromeosquirksserver-pa.googleapis.com/v2/display/%s/clients"
glevin2aa9dd12017-03-16 21:07:5927 "/chromeos/M%d?";
glevin5dd01a72016-03-23 23:08:1228
glevinb207f7a2016-06-03 14:50:0229const int kMaxServerFailures = 10;
30
glevin5dd01a72016-03-23 23:08:1231const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
32 1, // Initial errors before applying backoff
33 10000, // 10 seconds.
34 2, // Factor by which the waiting time will be multiplied.
35 0, // Random fuzzing percentage.
36 1000 * 3600 * 6, // Max wait between requests = 6 hours.
37 -1, // Don't discard entry.
38 true, // Use initial delay after first error.
39};
40
41bool WriteIccFile(const base::FilePath file_path, const std::string& data) {
42 int bytes_written = base::WriteFile(file_path, data.data(), data.length());
43 if (bytes_written == -1)
44 LOG(ERROR) << "Write failed: " << file_path.value() << ", err = " << errno;
45 else
46 VLOG(1) << bytes_written << "bytes written to: " << file_path.value();
47
48 return (bytes_written != -1);
49}
50
51} // namespace
52
53////////////////////////////////////////////////////////////////////////////////
54// QuirksClient
55
56QuirksClient::QuirksClient(int64_t product_id,
glevin2aa9dd12017-03-16 21:07:5957 const std::string& display_name,
glevin5dd01a72016-03-23 23:08:1258 const RequestFinishedCallback& on_request_finished,
59 QuirksManager* manager)
60 : product_id_(product_id),
glevin2aa9dd12017-03-16 21:07:5961 display_name_(display_name),
glevin5dd01a72016-03-23 23:08:1262 on_request_finished_(on_request_finished),
63 manager_(manager),
glevinf9f834f2016-10-25 22:52:1564 icc_path_(manager->delegate()->GetDisplayProfileDirectory().Append(
65 IdToFileName(product_id))),
glevin5dd01a72016-03-23 23:08:1266 backoff_entry_(&kDefaultBackoffPolicy),
67 weak_ptr_factory_(this) {}
68
69QuirksClient::~QuirksClient() {}
70
71void QuirksClient::StartDownload() {
72 DCHECK(thread_checker_.CalledOnValidThread());
73
74 // URL of icc file on Quirks Server.
75 int major_version = atoi(version_info::GetVersionNumber().c_str());
76 std::string url = base::StringPrintf(
77 kQuirksUrlFormat, IdToHexString(product_id_).c_str(), major_version);
78
glevin2aa9dd12017-03-16 21:07:5979 if (!display_name_.empty()) {
80 url +=
81 "display_name=" + net::EscapeQueryParamValue(display_name_, true) + "&";
82 }
83
glevin5dd01a72016-03-23 23:08:1284 VLOG(2) << "Preparing to download\n " << url << "\nto file "
85 << icc_path_.value();
86
glevin2aa9dd12017-03-16 21:07:5987 url += "key=" + manager_->delegate()->GetApiKey();
glevin5dd01a72016-03-23 23:08:1288
89 url_fetcher_ = manager_->CreateURLFetcher(GURL(url), this);
90 url_fetcher_->SetRequestContext(manager_->url_context_getter());
91 url_fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
92 net::LOAD_DO_NOT_SAVE_COOKIES |
93 net::LOAD_DO_NOT_SEND_COOKIES |
94 net::LOAD_DO_NOT_SEND_AUTH_DATA);
95 url_fetcher_->Start();
96}
97
98void QuirksClient::OnURLFetchComplete(const net::URLFetcher* source) {
99 DCHECK(thread_checker_.CalledOnValidThread());
100 DCHECK_EQ(url_fetcher_.get(), source);
101
102 const int HTTP_INTERNAL_SERVER_ERROR_LAST =
103 net::HTTP_INTERNAL_SERVER_ERROR + 99;
104 const net::URLRequestStatus status = source->GetStatus();
105 const int response_code = source->GetResponseCode();
106 const bool server_error = !status.is_success() ||
107 (response_code >= net::HTTP_INTERNAL_SERVER_ERROR &&
108 response_code <= HTTP_INTERNAL_SERVER_ERROR_LAST);
109
110 VLOG(2) << "QuirksClient::OnURLFetchComplete():"
111 << " status=" << status.status()
112 << ", response_code=" << response_code
113 << ", server_error=" << server_error;
114
115 if (response_code == net::HTTP_NOT_FOUND) {
116 VLOG(1) << IdToFileName(product_id_) << " not found on Quirks server.";
117 Shutdown(false);
118 return;
119 }
120
121 if (server_error) {
glevinb207f7a2016-06-03 14:50:02122 if (backoff_entry_.failure_count() >= kMaxServerFailures) {
123 // After 10 retires (5+ hours), give up, and try again in a month.
124 VLOG(1) << "Too many retries; Quirks Client shutting down.";
125 Shutdown(false);
126 return;
127 }
glevin5dd01a72016-03-23 23:08:12128 url_fetcher_.reset();
129 Retry();
130 return;
131 }
132
133 std::string response;
134 url_fetcher_->GetResponseAsString(&response);
135 VLOG(2) << "Quirks server response:\n" << response;
136
137 // Parse response data and write to file on file thread.
138 std::string data;
139 if (!ParseResult(response, &data)) {
140 Shutdown(false);
141 return;
142 }
143
144 base::PostTaskAndReplyWithResult(
Daniel Erat661b99f2017-07-06 15:57:59145 manager_->task_runner(), FROM_HERE,
glevin5dd01a72016-03-23 23:08:12146 base::Bind(&WriteIccFile, icc_path_, data),
147 base::Bind(&QuirksClient::Shutdown, weak_ptr_factory_.GetWeakPtr()));
148}
149
150void QuirksClient::Shutdown(bool success) {
151 DCHECK(thread_checker_.CalledOnValidThread());
152 on_request_finished_.Run(success ? icc_path_ : base::FilePath(), true);
153 manager_->ClientFinished(this);
154}
155
156void QuirksClient::Retry() {
157 DCHECK(thread_checker_.CalledOnValidThread());
158 backoff_entry_.InformOfRequest(false);
159 const base::TimeDelta delay = backoff_entry_.GetTimeUntilRelease();
160
161 VLOG(1) << "Schedule next Quirks download attempt in " << delay.InSecondsF()
162 << " seconds (retry = " << backoff_entry_.failure_count() << ").";
163 request_scheduled_.Start(FROM_HERE, delay, this,
164 &QuirksClient::StartDownload);
165}
166
167bool QuirksClient::ParseResult(const std::string& result, std::string* data) {
168 std::string data64;
169 const base::DictionaryValue* dict;
dcheng82beb4f2016-04-26 00:35:02170 std::unique_ptr<base::Value> json = base::JSONReader::Read(result);
glevin5dd01a72016-03-23 23:08:12171 if (!json || !json->GetAsDictionary(&dict) ||
172 !dict->GetString("icc", &data64)) {
173 VLOG(1) << "Failed to parse JSON icc data";
174 return false;
175 }
176
177 if (!base::Base64Decode(data64, data)) {
178 VLOG(1) << "Failed to decode Base64 icc data";
179 return false;
180 }
181
182 return true;
183}
184
185} // namespace quirks