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