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