1 // Copyright 2013 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 "extensions/browser/api/feedback_private/feedback_private_api.h"
6
7 #include <string>
8 #include <utility>
9 #include <vector>
10
11 #include "base/bind.h"
12 #include "base/check.h"
13 #include "base/lazy_instance.h"
14 #include "base/metrics/histogram_base.h"
15 #include "base/metrics/statistics_recorder.h"
16 #include "base/metrics/user_metrics.h"
17 #include "base/notreached.h"
18 #include "base/stl_util.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/values.h"
24 #include "build/build_config.h"
25 #include "build/chromeos_buildflags.h"
26 #include "components/feedback/feedback_report.h"
27 #include "components/feedback/system_logs/system_logs_fetcher.h"
28 #include "components/feedback/tracing_manager.h"
29 #include "extensions/browser/api/extensions_api_client.h"
30 #include "extensions/browser/api/feedback_private/feedback_private_delegate.h"
31 #include "extensions/browser/api/feedback_private/feedback_service.h"
32 #include "extensions/browser/event_router.h"
33 #include "extensions/common/api/feedback_private.h"
34 #include "extensions/common/constants.h"
35 #include "google_apis/gaia/gaia_auth_util.h"
36
37 #if BUILDFLAG(IS_CHROMEOS_ASH)
38 #include "extensions/browser/api/feedback_private/log_source_access_manager.h"
39 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
40
41 using extensions::api::feedback_private::SystemInformation;
42 using feedback::FeedbackData;
43
44 namespace extensions {
45
46 namespace feedback_private = api::feedback_private;
47
48 using feedback_private::FeedbackInfo;
49 using feedback_private::FeedbackFlow;
50 using feedback_private::LogSource;
51 using feedback_private::SystemInformation;
52
53 using SystemInformationList =
54 std::vector<api::feedback_private::SystemInformation>;
55
56 static base::LazyInstance<BrowserContextKeyedAPIFactory<FeedbackPrivateAPI>>::
57 DestructorAtExit g_factory = LAZY_INSTANCE_INITIALIZER;
58
59 namespace {
60
61 constexpr base::FilePath::CharType kBluetoothLogsFilePath[] =
62 FILE_PATH_LITERAL("/var/log/bluetooth/log.bz2");
63 constexpr base::FilePath::CharType kBluetoothLogsFilePathOld[] =
64 FILE_PATH_LITERAL("/var/log/bluetooth/log.bz2.old");
65
66 constexpr char kBluetoothLogsAttachmentName[] = "bluetooth_logs.bz2";
67 constexpr char kBluetoothLogsAttachmentNameOld[] = "bluetooth_logs.old.bz2";
68
69 constexpr int kKaleidoscopeProductId = 5192933;
70
71 #if BUILDFLAG(IS_CHROMEOS_ASH)
72 constexpr char kLacrosHistogramsFilename[] = "lacros_histograms.zip";
73 #endif
74
75 // Getting the filename of a blob prepends a "C:\fakepath" to the filename.
76 // This is undesirable, strip it if it exists.
StripFakepath(const std::string & path)77 std::string StripFakepath(const std::string& path) {
78 constexpr char kFakePathStr[] = "C:\\fakepath\\";
79 if (base::StartsWith(path, kFakePathStr,
80 base::CompareCase::INSENSITIVE_ASCII))
81 return path.substr(base::size(kFakePathStr) - 1);
82 return path;
83 }
84
85 // Returns the type of the landing page which is shown to the user when the
86 // report is successfully sent.
GetLandingPageType(const feedback::FeedbackData & feedback_data)87 feedback_private::LandingPageType GetLandingPageType(
88 const feedback::FeedbackData& feedback_data) {
89 #if BUILDFLAG(IS_CHROMEOS_ASH)
90 return ExtensionsAPIClient::Get()
91 ->GetFeedbackPrivateDelegate()
92 ->GetLandingPageType(feedback_data);
93 #else
94 return feedback_private::LANDING_PAGE_TYPE_NORMAL;
95 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
96 }
97
98 } // namespace
99
100 // static
101 BrowserContextKeyedAPIFactory<FeedbackPrivateAPI>*
GetFactoryInstance()102 FeedbackPrivateAPI::GetFactoryInstance() {
103 return g_factory.Pointer();
104 }
105
FeedbackPrivateAPI(content::BrowserContext * context)106 FeedbackPrivateAPI::FeedbackPrivateAPI(content::BrowserContext* context)
107 : browser_context_(context),
108 #if !BUILDFLAG(IS_CHROMEOS_ASH)
109 service_(new FeedbackService(context)) {
110 #else
111 service_(new FeedbackService(context)),
112 log_source_access_manager_(new LogSourceAccessManager(context)){
113 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
114 }
115
116 FeedbackPrivateAPI::~FeedbackPrivateAPI() {}
117
118 FeedbackService* FeedbackPrivateAPI::GetService() const {
119 return service_.get();
120 }
121
122 #if BUILDFLAG(IS_CHROMEOS_ASH)
123 LogSourceAccessManager* FeedbackPrivateAPI::GetLogSourceAccessManager() const {
124 return log_source_access_manager_.get();
125 }
126 #endif
127
128 void FeedbackPrivateAPI::RequestFeedbackForFlow(
129 const std::string& description_template,
130 const std::string& description_placeholder_text,
131 const std::string& category_tag,
132 const std::string& extra_diagnostics,
133 const GURL& page_url,
134 api::feedback_private::FeedbackFlow flow,
135 bool from_assistant,
136 bool include_bluetooth_logs,
137 bool from_kaleidoscope) {
138 if (browser_context_ && EventRouter::Get(browser_context_)) {
139 FeedbackInfo info;
140 info.description = description_template;
141 info.description_placeholder =
142 std::make_unique<std::string>(description_placeholder_text);
143 info.category_tag = std::make_unique<std::string>(category_tag);
144 info.page_url = std::make_unique<std::string>(page_url.spec());
145 info.system_information = std::make_unique<SystemInformationList>();
146 #if BUILDFLAG(IS_CHROMEOS_ASH)
147 info.from_assistant = std::make_unique<bool>(from_assistant);
148 info.include_bluetooth_logs =
149 std::make_unique<bool>(include_bluetooth_logs);
150 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
151
152 // Any extra diagnostics information should be added to the sys info.
153 if (!extra_diagnostics.empty()) {
154 SystemInformation extra_info;
155 extra_info.key = "EXTRA_DIAGNOSTICS";
156 extra_info.value = extra_diagnostics;
157 info.system_information->emplace_back(std::move(extra_info));
158 }
159
160 // The manager is only available if tracing is enabled.
161 if (TracingManager* manager = TracingManager::Get()) {
162 info.trace_id = std::make_unique<int>(manager->RequestTrace());
163 }
164 info.flow = flow;
165 #if defined(OS_MAC)
166 const bool use_system_window_frame = true;
167 #else
168 const bool use_system_window_frame = false;
169 #endif
170 info.use_system_window_frame =
171 std::make_unique<bool>(use_system_window_frame);
172
173 // If the feedback is from Kaleidoscope then this should use a custom
174 // product ID.
175 if (from_kaleidoscope) {
176 info.product_id = std::make_unique<int>(kKaleidoscopeProductId);
177 }
178
179 std::unique_ptr<base::ListValue> args =
180 feedback_private::OnFeedbackRequested::Create(info);
181
182 auto event = std::make_unique<Event>(
183 events::FEEDBACK_PRIVATE_ON_FEEDBACK_REQUESTED,
184 feedback_private::OnFeedbackRequested::kEventName, std::move(args),
185 browser_context_);
186
187 // TODO(weidongg/754329): Using DispatchEventWithLazyListener() is a
188 // temporary fix to the bug. Investigate a better solution that applies to
189 // all scenarios.
190 EventRouter::Get(browser_context_)
191 ->DispatchEventWithLazyListener(extension_misc::kFeedbackExtensionId,
192 std::move(event));
193 }
194 }
195
196 // static
197 base::Closure* FeedbackPrivateGetStringsFunction::test_callback_ = NULL;
198
199 ExtensionFunction::ResponseAction FeedbackPrivateGetStringsFunction::Run() {
200 auto params = feedback_private::GetStrings::Params::Create(*args_);
201 EXTENSION_FUNCTION_VALIDATE(params.get());
202
203 FeedbackPrivateDelegate* feedback_private_delegate =
204 ExtensionsAPIClient::Get()->GetFeedbackPrivateDelegate();
205 DCHECK(feedback_private_delegate);
206 std::unique_ptr<base::DictionaryValue> dict =
207 feedback_private_delegate->GetStrings(
208 browser_context(),
209 params->flow == FeedbackFlow::FEEDBACK_FLOW_SADTABCRASH);
210
211 if (test_callback_ && !test_callback_->is_null())
212 test_callback_->Run();
213
214 return RespondNow(
215 OneArgument(base::Value::FromUniquePtrValue(std::move(dict))));
216 }
217
218 ExtensionFunction::ResponseAction FeedbackPrivateGetUserEmailFunction::Run() {
219 FeedbackPrivateDelegate* feedback_private_delegate =
220 ExtensionsAPIClient::Get()->GetFeedbackPrivateDelegate();
221 return RespondNow(OneArgument(base::Value(
222 feedback_private_delegate->GetSignedInUserEmail(browser_context()))));
223 }
224
225 ExtensionFunction::ResponseAction
226 FeedbackPrivateGetSystemInformationFunction::Run() {
227 // Self-deleting object.
228 system_logs::SystemLogsFetcher* fetcher =
229 ExtensionsAPIClient::Get()
230 ->GetFeedbackPrivateDelegate()
231 ->CreateSystemLogsFetcher(browser_context());
232 fetcher->Fetch(base::BindOnce(
233 &FeedbackPrivateGetSystemInformationFunction::OnCompleted, this));
234
235 return RespondLater();
236 }
237
238 void FeedbackPrivateGetSystemInformationFunction::OnCompleted(
239 std::unique_ptr<system_logs::SystemLogsResponse> sys_info) {
240 SystemInformationList sys_info_list;
241 if (sys_info) {
242 sys_info_list.reserve(sys_info->size());
243 const bool google_email = gaia::IsGoogleInternalAccountEmail(
244 ExtensionsAPIClient::Get()
245 ->GetFeedbackPrivateDelegate()
246 ->GetSignedInUserEmail(browser_context()));
247 for (auto& itr : *sys_info) {
248 // We only send the list of all the crash report IDs if the user has a
249 // @google.com email. We strip this here so that the system information
250 // view properly reflects what we will be uploading to the server. It is
251 // also stripped later on in the feedback processing for other code paths
252 // that don't go through this.
253 if (itr.first == feedback::FeedbackReport::kAllCrashReportIdsKey &&
254 !google_email) {
255 continue;
256 }
257
258 SystemInformation sys_info_entry;
259 sys_info_entry.key = std::move(itr.first);
260 sys_info_entry.value = std::move(itr.second);
261 sys_info_list.emplace_back(std::move(sys_info_entry));
262 }
263 }
264
265 Respond(ArgumentList(
266 feedback_private::GetSystemInformation::Results::Create(sys_info_list)));
267 }
268
269 ExtensionFunction::ResponseAction FeedbackPrivateReadLogSourceFunction::Run() {
270 #if BUILDFLAG(IS_CHROMEOS_ASH)
271 using Params = feedback_private::ReadLogSource::Params;
272 std::unique_ptr<Params> api_params = Params::Create(*args_);
273
274 LogSourceAccessManager* log_source_manager =
275 FeedbackPrivateAPI::GetFactoryInstance()
276 ->Get(browser_context())
277 ->GetLogSourceAccessManager();
278
279 if (!log_source_manager->FetchFromSource(
280 api_params->params, extension_id(),
281 base::BindOnce(&FeedbackPrivateReadLogSourceFunction::OnCompleted,
282 this))) {
283 return RespondNow(Error(base::StringPrintf(
284 "Unable to initiate fetch from log source %s.",
285 feedback_private::ToString(api_params->params.source))));
286 }
287
288 return RespondLater();
289 #else
290 NOTREACHED() << "API function is not supported on this platform.";
291 return RespondNow(Error("API function is not supported on this platform."));
292 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
293 }
294
295 #if BUILDFLAG(IS_CHROMEOS_ASH)
296 void FeedbackPrivateReadLogSourceFunction::OnCompleted(
297 std::unique_ptr<feedback_private::ReadLogSourceResult> result) {
298 Respond(
299 ArgumentList(feedback_private::ReadLogSource::Results::Create(*result)));
300 }
301 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
302
303 ExtensionFunction::ResponseAction FeedbackPrivateSendFeedbackFunction::Run() {
304 std::unique_ptr<feedback_private::SendFeedback::Params> params(
305 feedback_private::SendFeedback::Params::Create(*args_));
306 EXTENSION_FUNCTION_VALIDATE(params);
307
308 const FeedbackInfo& feedback_info = params->feedback;
309
310 // Populate feedback data.
311 FeedbackPrivateDelegate* delegate =
312 ExtensionsAPIClient::Get()->GetFeedbackPrivateDelegate();
313 scoped_refptr<FeedbackData> feedback_data =
314 base::MakeRefCounted<FeedbackData>(
315 delegate->GetFeedbackUploaderForContext(browser_context()));
316 feedback_data->set_context(browser_context());
317 feedback_data->set_description(feedback_info.description);
318
319 if (feedback_info.product_id)
320 feedback_data->set_product_id(*feedback_info.product_id);
321 if (feedback_info.category_tag)
322 feedback_data->set_category_tag(*feedback_info.category_tag);
323 if (feedback_info.page_url)
324 feedback_data->set_page_url(*feedback_info.page_url);
325 if (feedback_info.email)
326 feedback_data->set_user_email(*feedback_info.email);
327 if (feedback_info.trace_id)
328 feedback_data->set_trace_id(*feedback_info.trace_id);
329
330 if (feedback_info.attached_file_blob_uuid &&
331 !feedback_info.attached_file_blob_uuid->empty()) {
332 feedback_data->set_attached_filename(
333 StripFakepath((*feedback_info.attached_file).name));
334 feedback_data->set_attached_file_uuid(
335 *feedback_info.attached_file_blob_uuid);
336 }
337
338 if (feedback_info.screenshot_blob_uuid &&
339 !feedback_info.screenshot_blob_uuid->empty()) {
340 feedback_data->set_screenshot_uuid(*feedback_info.screenshot_blob_uuid);
341 }
342
343 #if BUILDFLAG(IS_CHROMEOS_ASH)
344 feedback_data->set_from_assistant(feedback_info.from_assistant &&
345 *feedback_info.from_assistant);
346 feedback_data->set_assistant_debug_info_allowed(
347 feedback_info.assistant_debug_info_allowed &&
348 *feedback_info.assistant_debug_info_allowed);
349 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
350
351 const bool send_histograms =
352 feedback_info.send_histograms && *feedback_info.send_histograms;
353 const bool send_bluetooth_logs =
354 feedback_info.send_bluetooth_logs && *feedback_info.send_bluetooth_logs;
355 const bool send_tab_titles =
356 feedback_info.send_tab_titles && *feedback_info.send_tab_titles;
357
358 if (params->feedback.system_information) {
359 for (SystemInformation& info : *params->feedback.system_information)
360 feedback_data->AddLog(std::move(info.key), std::move(info.value));
361 #if BUILDFLAG(IS_CHROMEOS_ASH)
362 delegate->FetchExtraLogs(
363 feedback_data,
364 base::BindOnce(&FeedbackPrivateSendFeedbackFunction::OnAshLogsFetched,
365 this, send_histograms, send_bluetooth_logs,
366 send_tab_titles));
367 return RespondLater();
368 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
369 }
370
371 OnAllLogsFetched(send_histograms, send_bluetooth_logs, send_tab_titles,
372 feedback_data);
373
374 return RespondLater();
375 }
376
377 void FeedbackPrivateSendFeedbackFunction::OnAllLogsFetched(
378 bool send_histograms,
379 bool send_bluetooth_logs,
380 bool send_tab_titles,
381 scoped_refptr<feedback::FeedbackData> feedback_data) {
382 if (!send_tab_titles) {
383 feedback_data->RemoveLog(
384 feedback::FeedbackReport::kMemUsageWithTabTitlesKey);
385 }
386 feedback_data->CompressSystemInfo();
387
388 if (send_histograms) {
389 std::string histograms =
390 base::StatisticsRecorder::ToJSON(base::JSON_VERBOSITY_LEVEL_FULL);
391 feedback_data->SetAndCompressHistograms(std::move(histograms));
392 }
393
394 if (send_bluetooth_logs) {
395 std::string bluetooth_logs;
396 if (base::ReadFileToString(base::FilePath(kBluetoothLogsFilePath),
397 &bluetooth_logs)) {
398 feedback_data->AddFile(kBluetoothLogsAttachmentName,
399 std::move(bluetooth_logs));
400 }
401 if (base::ReadFileToString(base::FilePath(kBluetoothLogsFilePathOld),
402 &bluetooth_logs)) {
403 feedback_data->AddFile(kBluetoothLogsAttachmentNameOld,
404 std::move(bluetooth_logs));
405 }
406 }
407
408 FeedbackService* service = FeedbackPrivateAPI::GetFactoryInstance()
409 ->Get(browser_context())
410 ->GetService();
411 DCHECK(service);
412
413 service->SendFeedback(
414 feedback_data,
415 base::Bind(&FeedbackPrivateSendFeedbackFunction::OnCompleted, this,
416 GetLandingPageType(*feedback_data)));
417 }
418
419 #if BUILDFLAG(IS_CHROMEOS_ASH)
420 void FeedbackPrivateSendFeedbackFunction::OnAshLogsFetched(
421 bool send_histograms,
422 bool send_bluetooth_logs,
423 bool send_tab_titles,
424 scoped_refptr<feedback::FeedbackData> feedback_data) {
425 FeedbackPrivateDelegate* feedback_private_delegate =
426 ExtensionsAPIClient::Get()->GetFeedbackPrivateDelegate();
427 feedback_private_delegate->GetLacrosHistograms(base::BindOnce(
428 &FeedbackPrivateSendFeedbackFunction::OnLacrosHistogramsFetched, this,
429 send_histograms, send_bluetooth_logs, send_tab_titles, feedback_data));
430 }
431
432 void FeedbackPrivateSendFeedbackFunction::OnLacrosHistogramsFetched(
433 bool send_histograms,
434 bool send_bluetooth_logs,
435 bool send_tab_titles,
436 scoped_refptr<feedback::FeedbackData> feedback_data,
437 const std::string& compressed_histograms) {
438 // Attach lacros histogram to feedback data.
439 if (!compressed_histograms.empty()) {
440 feedback_data->AddFile(kLacrosHistogramsFilename,
441 std::move(compressed_histograms));
442 }
443
444 OnAllLogsFetched(send_histograms, send_bluetooth_logs, send_tab_titles,
445 feedback_data);
446 }
447 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
448
449 void FeedbackPrivateSendFeedbackFunction::OnCompleted(
450 api::feedback_private::LandingPageType type,
451 bool success) {
452 Respond(TwoArguments(base::Value(feedback_private::ToString(
453 success ? feedback_private::STATUS_SUCCESS
454 : feedback_private::STATUS_DELAYED)),
455 base::Value(feedback_private::ToString(type))));
456 if (!success) {
457 ExtensionsAPIClient::Get()
458 ->GetFeedbackPrivateDelegate()
459 ->NotifyFeedbackDelayed();
460 }
461 }
462
463 ExtensionFunction::ResponseAction
464 FeedbackPrivateLoginFeedbackCompleteFunction::Run() {
465 #if BUILDFLAG(IS_CHROMEOS_ASH)
466 FeedbackPrivateDelegate* feedback_private_delegate =
467 ExtensionsAPIClient::Get()->GetFeedbackPrivateDelegate();
468 feedback_private_delegate->UnloadFeedbackExtension(browser_context());
469 #endif
470 return RespondNow(NoArguments());
471 }
472
473 } // namespace extensions
474