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 "chrome/browser/ui/webui/media/webrtc_logs_ui.h"
6 
7 #include <memory>
8 #include <utility>
9 #include <vector>
10 
11 #include "base/bind.h"
12 #include "base/callback_helpers.h"
13 #include "base/i18n/time_formatting.h"
14 #include "base/macros.h"
15 #include "base/memory/ref_counted_memory.h"
16 #include "base/memory/weak_ptr.h"
17 #include "base/strings/string16.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/values.h"
21 #include "build/build_config.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/ui/webui/webui_util.h"
26 #include "chrome/common/url_constants.h"
27 #include "chrome/grit/generated_resources.h"
28 #include "chrome/grit/webrtc_logs_resources.h"
29 #include "components/prefs/pref_service.h"
30 #include "components/upload_list/upload_list.h"
31 #include "components/version_info/version_info.h"
32 #include "components/webrtc_logging/browser/text_log_list.h"
33 #include "content/public/browser/browser_thread.h"
34 #include "content/public/browser/web_contents.h"
35 #include "content/public/browser/web_ui.h"
36 #include "content/public/browser/web_ui_data_source.h"
37 #include "content/public/browser/web_ui_message_handler.h"
38 #include "ui/base/webui/web_ui_util.h"
39 
40 #if defined(OS_CHROMEOS)
41 #include "chrome/browser/chromeos/settings/cros_settings.h"
42 #endif
43 
44 using content::WebContents;
45 using content::WebUIMessageHandler;
46 
47 namespace {
48 
CreateWebRtcLogsUIHTMLSource()49 content::WebUIDataSource* CreateWebRtcLogsUIHTMLSource() {
50   content::WebUIDataSource* source =
51       content::WebUIDataSource::Create(chrome::kChromeUIWebRtcLogsHost);
52 
53   static constexpr webui::LocalizedString kStrings[] = {
54       {"webrtcLogsTitle", IDS_WEBRTC_LOGS_TITLE},
55       {"webrtcTextLogCountFormat",
56        IDS_WEBRTC_TEXT_LOGS_LOG_COUNT_BANNER_FORMAT},
57       {"webrtcEventLogCountFormat",
58        IDS_WEBRTC_EVENT_LOGS_LOG_COUNT_BANNER_FORMAT},
59       {"webrtcLogHeaderFormat", IDS_WEBRTC_LOGS_LOG_HEADER_FORMAT},
60       {"webrtcLogLocalFileLabelFormat",
61        IDS_WEBRTC_LOGS_LOG_LOCAL_FILE_LABEL_FORMAT},
62       {"noLocalLogFileMessage", IDS_WEBRTC_LOGS_NO_LOCAL_LOG_FILE_MESSAGE},
63       {"webrtcLogUploadTimeFormat", IDS_WEBRTC_LOGS_LOG_UPLOAD_TIME_FORMAT},
64       {"webrtcLogFailedUploadTimeFormat",
65        IDS_WEBRTC_LOGS_LOG_FAILED_UPLOAD_TIME_FORMAT},
66       {"webrtcLogReportIdFormat", IDS_WEBRTC_LOGS_LOG_REPORT_ID_FORMAT},
67       {"webrtcEventLogLocalLogIdFormat",
68        IDS_WEBRTC_LOGS_EVENT_LOG_LOCAL_LOG_ID},
69       {"bugLinkText", IDS_WEBRTC_LOGS_BUG_LINK_LABEL},
70       {"webrtcLogNotUploadedMessage", IDS_WEBRTC_LOGS_LOG_NOT_UPLOADED_MESSAGE},
71       {"webrtcLogPendingMessage", IDS_WEBRTC_LOGS_LOG_PENDING_MESSAGE},
72       {"webrtcLogActivelyUploadedMessage",
73        IDS_WEBRTC_LOGS_LOG_ACTIVELY_UPLOADED_MESSAGE},
74       {"noTextLogsMessage", IDS_WEBRTC_LOGS_NO_TEXT_LOGS_MESSAGE},
75       {"noEventLogsMessage", IDS_WEBRTC_LOGS_NO_EVENT_LOGS_MESSAGE},
76   };
77   AddLocalizedStringsBulk(source, kStrings);
78 
79   source->UseStringsJs();
80   source->AddResourcePath("webrtc_logs.js", IDR_WEBRTC_LOGS_JS);
81   source->SetDefaultResource(IDR_WEBRTC_LOGS_HTML);
82   return source;
83 }
84 
85 ////////////////////////////////////////////////////////////////////////////////
86 //
87 // WebRtcLogsDOMHandler
88 //
89 ////////////////////////////////////////////////////////////////////////////////
90 
91 // The handler for Javascript messages for the chrome://webrtc-logs/ page.
92 class WebRtcLogsDOMHandler final : public WebUIMessageHandler {
93  public:
94   explicit WebRtcLogsDOMHandler(Profile* profile);
95   ~WebRtcLogsDOMHandler() override;
96 
97   // WebUIMessageHandler implementation.
98   void RegisterMessages() override;
99 
100  private:
101   using WebRtcEventLogManager = webrtc_event_logging::WebRtcEventLogManager;
102 
103   // Asynchronously fetches the list of upload WebRTC logs. Called from JS.
104   void HandleRequestWebRtcLogs(const base::ListValue* args);
105 
106   // Asynchronously load WebRTC text logs.
107   void LoadWebRtcTextLogs();
108 
109   // Callback for when WebRTC text logs have been asynchronously loaded.
110   void OnWebRtcTextLogsLoaded();
111 
112   // Asynchronously load WebRTC event logs.
113   void LoadWebRtcEventLogs();
114 
115   // Callback for when WebRTC event logs have been asynchronously loaded.
116   void OnWebRtcEventLogsLoaded(
117       const std::vector<UploadList::UploadInfo>& event_logs);
118 
119   // Update the chrome://webrtc-logs/ page.
120   void UpdateUI();
121 
122   // Update the text/event logs part of the forementioned page.
123   void UpdateUIWithTextLogs(base::ListValue* text_logs_list) const;
124   void UpdateUIWithEventLogs(base::ListValue* event_logs_list) const;
125 
126   // Convert a history entry about a captured WebRTC event log into a
127   // DictionaryValue of the type expected by updateWebRtcLogsList().
128   std::unique_ptr<base::DictionaryValue> EventLogUploadInfoToDictionaryValue(
129       const UploadList::UploadInfo& info) const;
130 
131   // Helpers for EventLogUploadInfoToDictionaryValue().
132   std::unique_ptr<base::DictionaryValue> FromPendingLog(
133       const UploadList::UploadInfo& info) const;
134   std::unique_ptr<base::DictionaryValue> FromActivelyUploadedLog(
135       const UploadList::UploadInfo& info) const;
136   std::unique_ptr<base::DictionaryValue> FromNotUploadedLog(
137       const UploadList::UploadInfo& info) const;
138   std::unique_ptr<base::DictionaryValue> FromUploadUnsuccessfulLog(
139       const UploadList::UploadInfo& info) const;
140   std::unique_ptr<base::DictionaryValue> FromUploadSuccessfulLog(
141       const UploadList::UploadInfo& info) const;
142 
143   bool SanityCheckOnUploadInfo(const UploadList::UploadInfo& info) const;
144 
145   // The directories where the (text/event) logs are stored.
146   const base::FilePath text_log_dir_;
147   const base::FilePath event_log_dir_;
148 
149   // Identifies to WebRtcEventLogManager the profile with which |this| is
150   // associated. Technically, we should be able to just keep Profile*,
151   // but avoiding it makes less lifetime assumptions.
152   // Note that the profile->GetOriginalProfile() is used, to make sure that
153   // for incognito profiles, the parent profile's event logs are shown,
154   // as is the behavior for text logs.
155   const WebRtcEventLogManager::BrowserContextId original_browser_context_id_;
156 
157   // Loads, parses and stores the list of uploaded text WebRTC logs.
158   scoped_refptr<UploadList> text_log_upload_list_;
159 
160   // List of WebRTC logs captured and possibly uploaded to Crash.
161   std::vector<UploadList::UploadInfo> event_logs_;
162 
163   // Factory for creating weak references to instances of this class.
164   base::WeakPtrFactory<WebRtcLogsDOMHandler> weak_ptr_factory_{this};
165 
166   DISALLOW_COPY_AND_ASSIGN(WebRtcLogsDOMHandler);
167 };
168 
WebRtcLogsDOMHandler(Profile * profile)169 WebRtcLogsDOMHandler::WebRtcLogsDOMHandler(Profile* profile)
170     : text_log_dir_(
171           webrtc_logging::TextLogList::
172               GetWebRtcLogDirectoryForBrowserContextPath(profile->GetPath())),
173       event_log_dir_(
174           WebRtcEventLogManager::GetRemoteBoundWebRtcEventLogsDir(profile)),
175       original_browser_context_id_(webrtc_event_logging::GetBrowserContextId(
176           profile->GetOriginalProfile())),
177       text_log_upload_list_(
178           webrtc_logging::TextLogList::CreateWebRtcLogList(profile)) {
179   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
180 }
181 
~WebRtcLogsDOMHandler()182 WebRtcLogsDOMHandler::~WebRtcLogsDOMHandler() {
183   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
184   text_log_upload_list_->CancelLoadCallback();
185 }
186 
RegisterMessages()187 void WebRtcLogsDOMHandler::RegisterMessages() {
188   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
189 
190   web_ui()->RegisterMessageCallback(
191       "requestWebRtcLogsList",
192       base::BindRepeating(&WebRtcLogsDOMHandler::HandleRequestWebRtcLogs,
193                           weak_ptr_factory_.GetWeakPtr()));
194 }
195 
HandleRequestWebRtcLogs(const base::ListValue * args)196 void WebRtcLogsDOMHandler::HandleRequestWebRtcLogs(
197     const base::ListValue* args) {
198   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
199   LoadWebRtcTextLogs();
200 }
201 
LoadWebRtcTextLogs()202 void WebRtcLogsDOMHandler::LoadWebRtcTextLogs() {
203   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
204   text_log_upload_list_->Load(
205       base::BindOnce(&WebRtcLogsDOMHandler::OnWebRtcTextLogsLoaded,
206                      weak_ptr_factory_.GetWeakPtr()));
207 }
208 
OnWebRtcTextLogsLoaded()209 void WebRtcLogsDOMHandler::OnWebRtcTextLogsLoaded() {
210   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
211   LoadWebRtcEventLogs();  // Text logs loaded; on to the event logs.
212 }
213 
LoadWebRtcEventLogs()214 void WebRtcLogsDOMHandler::LoadWebRtcEventLogs() {
215   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
216 
217   WebRtcEventLogManager* manager = WebRtcEventLogManager::GetInstance();
218   if (manager) {
219     manager->GetHistory(
220         original_browser_context_id_,
221         base::BindOnce(&WebRtcLogsDOMHandler::OnWebRtcEventLogsLoaded,
222                        weak_ptr_factory_.GetWeakPtr()));
223   } else {
224     OnWebRtcEventLogsLoaded(std::vector<UploadList::UploadInfo>());
225   }
226 }
227 
OnWebRtcEventLogsLoaded(const std::vector<UploadList::UploadInfo> & event_logs)228 void WebRtcLogsDOMHandler::OnWebRtcEventLogsLoaded(
229     const std::vector<UploadList::UploadInfo>& event_logs) {
230   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
231 
232   event_logs_ = event_logs;
233 
234   UpdateUI();  // All log histories loaded asynchronously; time to display.
235 }
236 
UpdateUI()237 void WebRtcLogsDOMHandler::UpdateUI() {
238   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
239 
240   base::ListValue text_logs_list;
241   UpdateUIWithTextLogs(&text_logs_list);
242 
243   base::ListValue event_logs_list;
244   UpdateUIWithEventLogs(&event_logs_list);
245 
246   base::Value version(version_info::GetVersionNumber());
247 
248   web_ui()->CallJavascriptFunctionUnsafe("updateWebRtcLogsList", text_logs_list,
249                                          event_logs_list, version);
250 }
251 
UpdateUIWithTextLogs(base::ListValue * upload_list) const252 void WebRtcLogsDOMHandler::UpdateUIWithTextLogs(
253     base::ListValue* upload_list) const {
254   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
255 
256   std::vector<UploadList::UploadInfo> uploads;
257   text_log_upload_list_->GetUploads(50, &uploads);
258 
259   for (auto i = uploads.begin(); i != uploads.end(); ++i) {
260     std::unique_ptr<base::DictionaryValue> upload(new base::DictionaryValue());
261     upload->SetString("id", i->upload_id);
262 
263     base::string16 value_w;
264     if (!i->upload_time.is_null())
265       value_w = base::TimeFormatFriendlyDateAndTime(i->upload_time);
266     upload->SetString("upload_time", value_w);
267 
268     base::FilePath::StringType value;
269     if (!i->local_id.empty())
270       value = text_log_dir_.AppendASCII(i->local_id)
271                   .AddExtension(FILE_PATH_LITERAL(".gz"))
272                   .value();
273     upload->SetString("local_file", value);
274 
275     // In october 2015, capture time was added to the log list, previously the
276     // local ID was used as capture time. The local ID has however changed so
277     // that it might not be a time. We fall back on the local ID if it traslates
278     // to a time within reasonable bounds, otherwise we fall back on the upload
279     // time.
280     // TODO(grunell): Use |capture_time| only.
281     if (!i->capture_time.is_null()) {
282       value_w = base::TimeFormatFriendlyDateAndTime(i->capture_time);
283     } else {
284       // Fall back on local ID as time. We need to check that it's within
285       // resonable bounds, since the ID may not represent time. Check between
286       // 2012 when the feature was introduced and now.
287       double seconds_since_epoch;
288       if (base::StringToDouble(i->local_id, &seconds_since_epoch)) {
289         base::Time capture_time = base::Time::FromDoubleT(seconds_since_epoch);
290         const base::Time::Exploded lower_limit = {2012, 1, 0, 1, 0, 0, 0, 0};
291         base::Time out_time;
292         bool conversion_success =
293             base::Time::FromUTCExploded(lower_limit, &out_time);
294         DCHECK(conversion_success);
295         if (capture_time > out_time && capture_time < base::Time::Now()) {
296           value_w = base::TimeFormatFriendlyDateAndTime(capture_time);
297         }
298       }
299     }
300     // If we haven't set |value_w| above, we fall back on the upload time, which
301     // was already in the variable. In case it's empty set the string to
302     // inform that the time is unknown.
303     if (value_w.empty())
304       value_w = base::string16(base::ASCIIToUTF16("(unknown time)"));
305     upload->SetString("capture_time", value_w);
306 
307     upload_list->Append(std::move(upload));
308   }
309 }
310 
UpdateUIWithEventLogs(base::ListValue * event_logs_list) const311 void WebRtcLogsDOMHandler::UpdateUIWithEventLogs(
312     base::ListValue* event_logs_list) const {
313   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
314   for (auto it = event_logs_.crbegin(); it != event_logs_.crend(); ++it) {
315     event_logs_list->Append(EventLogUploadInfoToDictionaryValue(*it));
316   }
317 }
318 
319 std::unique_ptr<base::DictionaryValue>
EventLogUploadInfoToDictionaryValue(const UploadList::UploadInfo & info) const320 WebRtcLogsDOMHandler::EventLogUploadInfoToDictionaryValue(
321     const UploadList::UploadInfo& info) const {
322   switch (info.state) {
323     case UploadList::UploadInfo::State::Pending:
324       // TODO(crbug.com/775415): Display actively-written logs differently
325       // than fully captured pending logs.
326       return info.upload_time.is_null() ? FromPendingLog(info)
327                                         : FromActivelyUploadedLog(info);
328     case UploadList::UploadInfo::State::NotUploaded:
329       return info.upload_time.is_null() ? FromNotUploadedLog(info)
330                                         : FromUploadUnsuccessfulLog(info);
331     case UploadList::UploadInfo::State::Uploaded:
332       return FromUploadSuccessfulLog(info);
333     case UploadList::UploadInfo::State::Pending_UserRequested:
334       NOTREACHED();
335   }
336 
337   LOG(ERROR) << "Unrecognized state (" << static_cast<int>(info.state) << ").";
338   return nullptr;
339 }
340 
FromPendingLog(const UploadList::UploadInfo & info) const341 std::unique_ptr<base::DictionaryValue> WebRtcLogsDOMHandler::FromPendingLog(
342     const UploadList::UploadInfo& info) const {
343   DCHECK_EQ(info.state, UploadList::UploadInfo::State::Pending);
344   DCHECK(info.upload_time.is_null());
345 
346   if (!SanityCheckOnUploadInfo(info)) {
347     return nullptr;
348   }
349 
350   std::unique_ptr<base::DictionaryValue> log(new base::DictionaryValue());
351   log->SetString("state", "pending");
352   log->SetString("capture_time",
353                  base::TimeFormatFriendlyDateAndTime(info.capture_time));
354   log->SetString("local_file",
355                  event_log_dir_.AppendASCII(info.local_id).value());
356   return log;
357 }
358 
359 std::unique_ptr<base::DictionaryValue>
FromActivelyUploadedLog(const UploadList::UploadInfo & info) const360 WebRtcLogsDOMHandler::FromActivelyUploadedLog(
361     const UploadList::UploadInfo& info) const {
362   DCHECK_EQ(info.state, UploadList::UploadInfo::State::Pending);
363   DCHECK(!info.upload_time.is_null());
364 
365   if (!SanityCheckOnUploadInfo(info)) {
366     return nullptr;
367   }
368 
369   std::unique_ptr<base::DictionaryValue> log(new base::DictionaryValue());
370   log->SetString("state", "actively_uploaded");
371   log->SetString("capture_time",
372                  base::TimeFormatFriendlyDateAndTime(info.capture_time));
373   log->SetString("local_file",
374                  event_log_dir_.AppendASCII(info.local_id).value());
375   return log;
376 }
377 
FromNotUploadedLog(const UploadList::UploadInfo & info) const378 std::unique_ptr<base::DictionaryValue> WebRtcLogsDOMHandler::FromNotUploadedLog(
379     const UploadList::UploadInfo& info) const {
380   DCHECK_EQ(info.state, UploadList::UploadInfo::State::NotUploaded);
381   DCHECK(info.upload_time.is_null());
382 
383   if (!SanityCheckOnUploadInfo(info)) {
384     return nullptr;
385   }
386 
387   std::unique_ptr<base::DictionaryValue> log(new base::DictionaryValue());
388   log->SetString("state", "not_uploaded");
389   log->SetString("capture_time",
390                  base::TimeFormatFriendlyDateAndTime(info.capture_time));
391   log->SetString("local_id", info.local_id);
392   return log;
393 }
394 
395 std::unique_ptr<base::DictionaryValue>
FromUploadUnsuccessfulLog(const UploadList::UploadInfo & info) const396 WebRtcLogsDOMHandler::FromUploadUnsuccessfulLog(
397     const UploadList::UploadInfo& info) const {
398   DCHECK_EQ(info.state, UploadList::UploadInfo::State::NotUploaded);
399   DCHECK(!info.upload_time.is_null());
400 
401   if (!SanityCheckOnUploadInfo(info)) {
402     return nullptr;
403   }
404 
405   if (!info.upload_id.empty()) {
406     LOG(ERROR) << "Unexpected upload ID.";
407     return nullptr;
408   }
409 
410   std::unique_ptr<base::DictionaryValue> log(new base::DictionaryValue());
411   log->SetString("state", "upload_unsuccessful");
412   log->SetString("capture_time",
413                  base::TimeFormatFriendlyDateAndTime(info.capture_time));
414   log->SetString("local_id", info.local_id);
415   log->SetString("upload_time",
416                  base::TimeFormatFriendlyDateAndTime(info.upload_time));
417   return log;
418 }
419 
420 std::unique_ptr<base::DictionaryValue>
FromUploadSuccessfulLog(const UploadList::UploadInfo & info) const421 WebRtcLogsDOMHandler::FromUploadSuccessfulLog(
422     const UploadList::UploadInfo& info) const {
423   DCHECK_EQ(info.state, UploadList::UploadInfo::State::Uploaded);
424   DCHECK(!info.upload_time.is_null());
425 
426   if (!SanityCheckOnUploadInfo(info)) {
427     return nullptr;
428   }
429 
430   if (info.upload_id.empty()) {
431     LOG(ERROR) << "Unknown upload ID.";
432     return nullptr;
433   }
434 
435   std::unique_ptr<base::DictionaryValue> log(new base::DictionaryValue());
436   log->SetString("state", "upload_successful");
437   log->SetString("capture_time",
438                  base::TimeFormatFriendlyDateAndTime(info.capture_time));
439   log->SetString("local_id", info.local_id);
440   log->SetString("upload_id", info.upload_id);
441   log->SetString("upload_time",
442                  base::TimeFormatFriendlyDateAndTime(info.upload_time));
443   return log;
444 }
445 
SanityCheckOnUploadInfo(const UploadList::UploadInfo & info) const446 bool WebRtcLogsDOMHandler::SanityCheckOnUploadInfo(
447     const UploadList::UploadInfo& info) const {
448   if (info.capture_time.is_null()) {
449     LOG(ERROR) << "Unknown capture time.";
450     return false;
451   }
452 
453   if (info.local_id.empty()) {
454     LOG(ERROR) << "Unknown local ID.";
455     return false;
456   }
457 
458   return true;
459 }
460 
461 }  // namespace
462 
463 ///////////////////////////////////////////////////////////////////////////////
464 //
465 // WebRtcLogsUI
466 //
467 ///////////////////////////////////////////////////////////////////////////////
468 
WebRtcLogsUI(content::WebUI * web_ui)469 WebRtcLogsUI::WebRtcLogsUI(content::WebUI* web_ui) : WebUIController(web_ui) {
470   Profile* profile = Profile::FromWebUI(web_ui);
471   web_ui->AddMessageHandler(std::make_unique<WebRtcLogsDOMHandler>(profile));
472 
473   // Set up the chrome://webrtc-logs/ source.
474   content::WebUIDataSource::Add(profile, CreateWebRtcLogsUIHTMLSource());
475 }
476