blob: 0444091a9b8fa9b2561d98e22306aded057ed64d [file] [log] [blame]
Avi Drissman9f7c8d72022-09-13 19:22:361// Copyright 2014 The Chromium Authors
[email protected]28548b02014-05-09 02:17:192// 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/feedback/feedback_uploader.h"
6
[email protected]28548b02014-05-09 02:17:197#include "base/command_line.h"
Avi Drissman90f86422023-01-11 09:16:098#include "base/functional/bind.h"
9#include "base/functional/callback.h"
xiangdong kong12ec0482021-12-17 00:06:4410#include "base/metrics/histogram_macros.h"
Sean Maher365bbe92023-01-09 21:42:2811#include "base/task/single_thread_task_runner.h"
Sylvain Defresneae05fd82021-02-22 11:23:3712#include "base/task/task_traits.h"
13#include "base/task/thread_pool.h"
Anton Swiftond7e62922022-10-21 21:42:4014#include "components/feedback/features.h"
[email protected]28548b02014-05-09 02:17:1915#include "components/feedback/feedback_report.h"
Ahmed Fakhry435b15e2017-07-21 21:39:0716#include "components/feedback/feedback_switches.h"
Ahmed Fakhryb0e32d82017-08-22 20:11:4517#include "components/variations/net/variations_http_headers.h"
Ahmed Fakhryb0e32d82017-08-22 20:11:4518#include "net/base/load_flags.h"
Robbie McElratheb1ff132018-07-09 22:01:0219#include "services/network/public/cpp/resource_request.h"
Robbie McElratheb1ff132018-07-09 22:01:0220#include "services/network/public/cpp/simple_url_loader.h"
Matt Menke24bc7992021-07-17 20:30:0021#include "services/network/public/mojom/url_response_head.mojom.h"
[email protected]28548b02014-05-09 02:17:1922
23namespace feedback {
Ahmed Fakhry435b15e2017-07-21 21:39:0724
[email protected]28548b02014-05-09 02:17:1925namespace {
26
xiangdong kong12ec0482021-12-17 00:06:4427constexpr char kReportSendingResultHistogramName[] =
28 "Feedback.ReportSending.Result";
29// These values are persisted to logs. Entries should not be renumbered and
30// numeric values should never be reused.
31enum class FeedbackReportSendingResult {
32 kSuccessAtFirstTry = 0, // The report was uploaded successfully without retry
33 kSuccessAfterRetry = 1, // The report was uploaded successfully after retry
34 kDropped = 2, // The report is corrupt or invalid and was dropped
35 kMaxValue = kDropped,
36};
37
Ahmed Fakhryb0e32d82017-08-22 20:11:4538constexpr base::FilePath::CharType kFeedbackReportPath[] =
39 FILE_PATH_LITERAL("Feedback Reports");
40
Ahmed Fakhry435b15e2017-07-21 21:39:0741constexpr char kFeedbackPostUrl[] =
[email protected]28548b02014-05-09 02:17:1942 "https://www.google.com/tools/feedback/chrome/__submit";
43
Ahmed Fakhryb0e32d82017-08-22 20:11:4544constexpr char kProtoBufMimeType[] = "application/x-protobuf";
[email protected]28548b02014-05-09 02:17:1945
Robbie McElratheb1ff132018-07-09 22:01:0246constexpr int kHttpPostSuccessNoContent = 204;
47constexpr int kHttpPostFailNoConnection = -1;
48constexpr int kHttpPostFailClientError = 400;
49constexpr int kHttpPostFailServerError = 500;
50
Ahmed Fakhry435b15e2017-07-21 21:39:0751// The minimum time to wait before uploading reports are retried. Exponential
52// backoff delay is applied on successive failures.
53// This value can be overriden by tests by calling
54// FeedbackUploader::SetMinimumRetryDelayForTesting().
Peter Kasting9dbb7932021-10-02 03:06:3555base::TimeDelta g_minimum_retry_delay = base::Minutes(60);
Ahmed Fakhry435b15e2017-07-21 21:39:0756
Ahmed Fakhryb0e32d82017-08-22 20:11:4557// If a new report is queued to be dispatched immediately while another is being
58// dispatched, this is the time to wait for the on-going dispatching to finish.
Peter Kasting9dbb7932021-10-02 03:06:3559base::TimeDelta g_dispatching_wait_delay = base::Seconds(4);
Ahmed Fakhryb0e32d82017-08-22 20:11:4560
Ahmed Fakhry435b15e2017-07-21 21:39:0761GURL GetFeedbackPostGURL() {
62 const base::CommandLine& command_line =
63 *base::CommandLine::ForCurrentProcess();
64 return GURL(command_line.HasSwitch(switches::kFeedbackServer)
65 ? command_line.GetSwitchValueASCII(switches::kFeedbackServer)
66 : kFeedbackPostUrl);
67}
68
Sylvain Defresneae05fd82021-02-22 11:23:3769// Creates a new SingleThreadTaskRunner that is used to run feedback blocking
70// background work.
71scoped_refptr<base::SingleThreadTaskRunner> CreateUploaderTaskRunner() {
72 // Uses a BLOCK_SHUTDOWN file task runner to prevent losing reports or
73 // corrupting report's files.
74 return base::ThreadPool::CreateSingleThreadTaskRunner(
75 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
76 base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
77}
78
[email protected]28548b02014-05-09 02:17:1979} // namespace
80
Sylvain Defresne444aaea2021-02-23 09:37:3981FeedbackUploader::FeedbackUploader(
82 bool is_off_the_record,
83 const base::FilePath& state_path,
84 SharedURLLoaderFactoryGetter shared_url_loader_factory_getter)
85 : FeedbackUploader(is_off_the_record,
86 state_path,
87 std::move(shared_url_loader_factory_getter),
88 nullptr) {}
89
90FeedbackUploader::FeedbackUploader(
91 bool is_off_the_record,
92 const base::FilePath& state_path,
93 scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory)
94 : FeedbackUploader(is_off_the_record,
95 state_path,
96 SharedURLLoaderFactoryGetter(),
97 shared_url_loader_factory) {}
Ahmed Fakhry435b15e2017-07-21 21:39:0798
Sorin Jianu221a3b32024-10-09 17:56:0699FeedbackUploader::~FeedbackUploader() = default;
Ahmed Fakhry435b15e2017-07-21 21:39:07100
101// static
102void FeedbackUploader::SetMinimumRetryDelayForTesting(base::TimeDelta delay) {
103 g_minimum_retry_delay = delay;
104}
105
Miriam Zimmermanf6a61aa2020-08-06 01:10:01106void FeedbackUploader::QueueReport(std::unique_ptr<std::string> data,
Darren Shen9221ba52024-01-08 03:23:11107 bool has_email,
108 int product_id) {
Jeffrey Kardatzkef7e1c952019-08-29 19:38:30109 reports_queue_.emplace(base::MakeRefCounted<FeedbackReport>(
Miriam Zimmermanf6a61aa2020-08-06 01:10:01110 feedback_reports_path_, base::Time::Now(), std::move(data), task_runner_,
Darren Shen9221ba52024-01-08 03:23:11111 has_email, product_id));
Jeffrey Kardatzkef7e1c952019-08-29 19:38:30112 UpdateUploadTimer();
113}
114
115void FeedbackUploader::RequeueReport(scoped_refptr<FeedbackReport> report) {
116 DCHECK_EQ(task_runner_, report->reports_task_runner());
117 report->set_upload_at(base::Time::Now());
118 reports_queue_.emplace(std::move(report));
119 UpdateUploadTimer();
Ahmed Fakhry435b15e2017-07-21 21:39:07120}
121
Ahmed Fakhryb0e32d82017-08-22 20:11:45122void FeedbackUploader::StartDispatchingReport() {
123 DispatchReport();
124}
125
Ahmed Fakhry435b15e2017-07-21 21:39:07126void FeedbackUploader::OnReportUploadSuccess() {
xiangdong kong12ec0482021-12-17 00:06:44127 if (retry_delay_ == g_minimum_retry_delay) {
128 UMA_HISTOGRAM_ENUMERATION(kReportSendingResultHistogramName,
129 FeedbackReportSendingResult::kSuccessAtFirstTry);
130 } else {
131 UMA_HISTOGRAM_ENUMERATION(kReportSendingResultHistogramName,
132 FeedbackReportSendingResult::kSuccessAfterRetry);
133 }
Ahmed Fakhry435b15e2017-07-21 21:39:07134 retry_delay_ = g_minimum_retry_delay;
Ahmed Fakhryb0e32d82017-08-22 20:11:45135 is_dispatching_ = false;
136 // Explicitly release the successfully dispatched report.
Ahmed Fakhryeea11db2018-06-14 03:22:02137 report_being_dispatched_->DeleteReportOnDisk();
Ahmed Fakhryb0e32d82017-08-22 20:11:45138 report_being_dispatched_ = nullptr;
Ahmed Fakhry435b15e2017-07-21 21:39:07139 UpdateUploadTimer();
140}
141
Ahmed Fakhryb0e32d82017-08-22 20:11:45142void FeedbackUploader::OnReportUploadFailure(bool should_retry) {
143 if (should_retry) {
144 // Implement a backoff delay by doubling the retry delay on each failure.
145 retry_delay_ *= 2;
146 report_being_dispatched_->set_upload_at(retry_delay_ + base::Time::Now());
147 reports_queue_.emplace(report_being_dispatched_);
Ahmed Fakhryeea11db2018-06-14 03:22:02148 } else {
149 // The report won't be retried, hence explicitly delete its file on disk.
150 report_being_dispatched_->DeleteReportOnDisk();
xiangdong kong12ec0482021-12-17 00:06:44151 UMA_HISTOGRAM_ENUMERATION(kReportSendingResultHistogramName,
152 FeedbackReportSendingResult::kDropped);
Ahmed Fakhryb0e32d82017-08-22 20:11:45153 }
154
155 // The report dispatching failed, and should either be retried or not. In all
156 // cases, we need to release |report_being_dispatched_|. If it was up for
157 // retry, then it has already been re-enqueued and will be kept alive.
158 // Otherwise we're done with it and it should destruct.
159 report_being_dispatched_ = nullptr;
160 is_dispatching_ = false;
Ahmed Fakhry435b15e2017-07-21 21:39:07161 UpdateUploadTimer();
162}
163
[email protected]28548b02014-05-09 02:17:19164bool FeedbackUploader::ReportsUploadTimeComparator::operator()(
dchengcf57b442014-09-04 04:22:38165 const scoped_refptr<FeedbackReport>& a,
166 const scoped_refptr<FeedbackReport>& b) const {
[email protected]28548b02014-05-09 02:17:19167 return a->upload_at() > b->upload_at();
168}
169
Sylvain Defresne444aaea2021-02-23 09:37:39170FeedbackUploader::FeedbackUploader(
171 bool is_off_the_record,
172 const base::FilePath& state_path,
173 SharedURLLoaderFactoryGetter url_loader_factory_getter,
174 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
175 : url_loader_factory_getter_(std::move(url_loader_factory_getter)),
176 url_loader_factory_(url_loader_factory),
177 feedback_reports_path_(state_path.Append(kFeedbackReportPath)),
178 task_runner_(CreateUploaderTaskRunner()),
179 feedback_post_url_(GetFeedbackPostGURL()),
180 retry_delay_(g_minimum_retry_delay),
181 is_off_the_record_(is_off_the_record) {
182 DCHECK(!!url_loader_factory_getter_ != !!url_loader_factory_);
183}
184
Ahmed Fakhryb0e32d82017-08-22 20:11:45185void FeedbackUploader::AppendExtraHeadersToUploadRequest(
Robbie McElratheb1ff132018-07-09 22:01:02186 network::ResourceRequest* resource_request) {}
Ahmed Fakhryb0e32d82017-08-22 20:11:45187
188void FeedbackUploader::DispatchReport() {
189 net::NetworkTrafficAnnotationTag traffic_annotation =
190 net::DefineNetworkTrafficAnnotation("chrome_feedback_report_app", R"(
191 semantics {
192 sender: "Chrome Feedback Report App"
193 description:
194 "Users can press Alt+Shift+i to report a bug or a feedback in "
195 "general. Along with the free-form text they entered, system logs "
196 "that helps in diagnosis of the issue are sent to Google. This "
197 "service uploads the report to Google Feedback server."
198 trigger:
199 "When user chooses to send a feedback to Google."
200 data:
201 "The free-form text that user has entered and useful debugging "
202 "logs (UI logs, Chrome logs, kernel logs, auto update engine logs, "
Nicolas Ouellet-Payeur72e51712021-07-14 20:48:59203 "ARC++ logs, etc.). The logs are redacted to remove any "
Ahmed Fakhryb0e32d82017-08-22 20:11:45204 "user-private data. The user can view the system information "
205 "before sending, and choose to send the feedback report without "
206 "system information and the logs (unchecking 'Send system "
207 "information' prevents sending logs as well), the screenshot, or "
208 "even his/her email address."
209 destination: GOOGLE_OWNED_SERVICE
Aiden Chiavatti35866542023-08-17 20:36:54210 internal {
211 contacts {
Chad Duffine779cbe2024-10-01 20:53:17212 email: "[email protected]"
Aiden Chiavatti35866542023-08-17 20:36:54213 }
214 }
215 user_data {
216 type: ARBITRARY_DATA
217 type: EMAIL
218 type: IMAGE
219 type: USER_CONTENT
220 }
221 last_reviewed: "2023-08-14"
Ahmed Fakhryb0e32d82017-08-22 20:11:45222 }
223 policy {
224 cookies_allowed: NO
225 setting:
226 "This feature cannot be disabled by settings and is only activated "
227 "by direct user request."
Nicolas Ouellet-Payeur72e51712021-07-14 20:48:59228 chrome_policy {
229 UserFeedbackAllowed {
230 UserFeedbackAllowed: false
231 }
232 }
Ahmed Fakhryb0e32d82017-08-22 20:11:45233 })");
Robbie McElratheb1ff132018-07-09 22:01:02234 auto resource_request = std::make_unique<network::ResourceRequest>();
235 resource_request->url = feedback_post_url_;
Yutaka Hiranod04cfe22019-07-29 06:41:34236 resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
Robbie McElratheb1ff132018-07-09 22:01:02237 resource_request->method = "POST";
238
Ahmed Fakhryb0e32d82017-08-22 20:11:45239 // Tell feedback server about the variation state of this install.
Darren Shen9221ba52024-01-08 03:23:11240 if (report_being_dispatched_->should_include_variations()) {
241 variations::AppendVariationsHeaderUnknownSignedIn(
242 feedback_post_url_,
243 is_off_the_record_ ? variations::InIncognito::kYes
244 : variations::InIncognito::kNo,
245 resource_request.get());
246 }
Ahmed Fakhryb0e32d82017-08-22 20:11:45247
Miriam Zimmermanf6a61aa2020-08-06 01:10:01248 if (report_being_dispatched_->has_email()) {
249 AppendExtraHeadersToUploadRequest(resource_request.get());
250 }
Ahmed Fakhryb0e32d82017-08-22 20:11:45251
Robbie McElratheb1ff132018-07-09 22:01:02252 std::unique_ptr<network::SimpleURLLoader> simple_url_loader =
253 network::SimpleURLLoader::Create(std::move(resource_request),
254 traffic_annotation);
255 network::SimpleURLLoader* simple_url_loader_ptr = simple_url_loader.get();
256 simple_url_loader->AttachStringForUpload(report_being_dispatched_->data(),
257 kProtoBufMimeType);
258 auto it = uploads_in_progress_.insert(uploads_in_progress_.begin(),
259 std::move(simple_url_loader));
Evan Stadeffd20892019-10-09 01:15:36260
Evan Stadeffd20892019-10-09 01:15:36261 if (!url_loader_factory_) {
Sylvain Defresne444aaea2021-02-23 09:37:39262 // Lazily create the URLLoaderFactory.
263 url_loader_factory_ = std::move(url_loader_factory_getter_).Run();
264 DCHECK(url_loader_factory_);
Evan Stadeffd20892019-10-09 01:15:36265 }
266
Robbie McElratheb1ff132018-07-09 22:01:02267 simple_url_loader_ptr->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
268 url_loader_factory_.get(),
269 base::BindOnce(&FeedbackUploader::OnDispatchComplete,
270 base::Unretained(this), std::move(it)));
271}
Ahmed Fakhryb0e32d82017-08-22 20:11:45272
Robbie McElratheb1ff132018-07-09 22:01:02273void FeedbackUploader::OnDispatchComplete(
274 UrlLoaderList::iterator it,
275 std::unique_ptr<std::string> response_body) {
276 std::stringstream error_stream;
277 network::SimpleURLLoader* simple_url_loader = it->get();
278 int response_code = kHttpPostFailNoConnection;
279 if (simple_url_loader->ResponseInfo() &&
280 simple_url_loader->ResponseInfo()->headers) {
281 response_code = simple_url_loader->ResponseInfo()->headers->response_code();
282 }
283 if (response_code == kHttpPostSuccessNoContent) {
284 error_stream << "Success";
285 OnReportUploadSuccess();
286 } else {
287 bool should_retry = true;
288 // Process the error for debug output
289 if (response_code == kHttpPostFailNoConnection) {
290 error_stream << "No connection to server.";
291 } else if ((response_code >= kHttpPostFailClientError) &&
292 (response_code < kHttpPostFailServerError)) {
293 // Client errors mean that the server failed to parse the proto that was
294 // sent, or that some requirements weren't met by the server side
295 // validation, and hence we should NOT retry sending this corrupt report
296 // and give up.
297 should_retry = false;
298
299 error_stream << "Client error: HTTP response code " << response_code;
300 } else if (response_code >= kHttpPostFailServerError) {
301 error_stream << "Server error: HTTP response code " << response_code;
302 } else {
303 error_stream << "Unknown error: HTTP response code " << response_code;
304 }
305
306 OnReportUploadFailure(should_retry);
307 }
308
309 LOG(WARNING) << "FEEDBACK: Submission to feedback server ("
310 << simple_url_loader->GetFinalURL()
311 << ") status: " << error_stream.str();
312 uploads_in_progress_.erase(it);
Ahmed Fakhryb0e32d82017-08-22 20:11:45313}
314
[email protected]28548b02014-05-09 02:17:19315void FeedbackUploader::UpdateUploadTimer() {
316 if (reports_queue_.empty())
317 return;
318
319 scoped_refptr<FeedbackReport> report = reports_queue_.top();
Anton Swiftond7e62922022-10-21 21:42:40320
321 // Don't send reports in Tast tests so that they don't spam Listnr.
322 if (feedback::features::IsSkipSendingFeedbackReportInTastTestsEnabled()) {
323 report->DeleteReportOnDisk();
324 reports_queue_.pop();
325 return;
326 }
327
Ahmed Fakhry435b15e2017-07-21 21:39:07328 const base::Time now = base::Time::Now();
Ahmed Fakhryb0e32d82017-08-22 20:11:45329 if (report->upload_at() <= now && !is_dispatching_) {
[email protected]28548b02014-05-09 02:17:19330 reports_queue_.pop();
Ahmed Fakhryb0e32d82017-08-22 20:11:45331 is_dispatching_ = true;
332 report_being_dispatched_ = report;
333 StartDispatchingReport();
[email protected]28548b02014-05-09 02:17:19334 } else {
335 // Stop the old timer and start an updated one.
Ahmed Fakhryb0e32d82017-08-22 20:11:45336 const base::TimeDelta delay = (is_dispatching_ || now > report->upload_at())
337 ? g_dispatching_wait_delay
338 : report->upload_at() - now;
Ahmed Fakhry435b15e2017-07-21 21:39:07339 upload_timer_.Stop();
Ahmed Fakhryb0e32d82017-08-22 20:11:45340 upload_timer_.Start(FROM_HERE, delay, this,
341 &FeedbackUploader::UpdateUploadTimer);
[email protected]28548b02014-05-09 02:17:19342 }
343}
344
[email protected]28548b02014-05-09 02:17:19345} // namespace feedback