blob: a57495e2aa668f4a85e7fad131846b6a5361f232 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.h"
#include <memory>
#include "base/command_line.h"
#include "base/hash.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/supports_user_data.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/guest_view/web_view/web_view_guest.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/error_utils.h"
#if defined(OS_LINUX) || defined(OS_CHROMEOS)
#include "extensions/common/permissions/permissions_data.h"
#endif
namespace {
bool CanEnableAudioDebugRecordingsFromExtension(
const extensions::Extension* extension) {
bool enabled_by_permissions = false;
#if defined(OS_LINUX) || defined(OS_CHROMEOS)
if (extension) {
enabled_by_permissions =
extension->permissions_data()->active_permissions().HasAPIPermission(
extensions::APIPermission::kWebrtcLoggingPrivateAudioDebug);
}
#endif
return base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableAudioDebugRecordingsFromExtension) ||
enabled_by_permissions;
}
} // namespace
namespace extensions {
using api::webrtc_logging_private::MetaDataEntry;
using api::webrtc_logging_private::RequestInfo;
using content::BrowserThread;
namespace Discard = api::webrtc_logging_private::Discard;
namespace SetMetaData = api::webrtc_logging_private::SetMetaData;
namespace SetUploadOnRenderClose =
api::webrtc_logging_private::SetUploadOnRenderClose;
namespace Start = api::webrtc_logging_private::Start;
namespace StartRtpDump = api::webrtc_logging_private::StartRtpDump;
namespace Stop = api::webrtc_logging_private::Stop;
namespace StopRtpDump = api::webrtc_logging_private::StopRtpDump;
namespace Store = api::webrtc_logging_private::Store;
namespace Upload = api::webrtc_logging_private::Upload;
namespace UploadStored = api::webrtc_logging_private::UploadStored;
namespace StartAudioDebugRecordings =
api::webrtc_logging_private::StartAudioDebugRecordings;
namespace StopAudioDebugRecordings =
api::webrtc_logging_private::StopAudioDebugRecordings;
namespace StartEventLogging = api::webrtc_logging_private::StartEventLogging;
namespace GetLogsDirectory = api::webrtc_logging_private::GetLogsDirectory;
namespace {
std::string HashIdWithOrigin(const std::string& security_origin,
const std::string& log_id) {
return base::UintToString(base::Hash(security_origin + log_id));
}
} // namespace
// TODO(hlundin): Consolidate with WebrtcAudioPrivateFunction and improve.
// http://crbug.com/710371
content::RenderProcessHost* WebrtcLoggingPrivateFunction::RphFromRequest(
const RequestInfo& request, const std::string& security_origin) {
// There are 2 ways these API functions can get called.
//
// 1. From a whitelisted component extension on behalf of a page with the
// appropriate origin via a message from that page. In this case, either the
// guest process id or the tab id is on the message received by the component
// extension, and the extension can pass that along in RequestInfo as
// |guest_process_id| or |tab_id|.
//
// 2. From a whitelisted app that hosts a page in a webview. In this case,
// the app should call these API functions with the |target_webview| flag
// set, from a web contents that has exactly 1 webview .
// If |target_webview| is set, lookup the guest content's render process in
// the sender's web contents. There should be exactly 1 guest.
if (request.target_webview.get()) {
content::RenderProcessHost* target_host = nullptr;
int guests_found = 0;
auto get_guest = [](int* guests_found,
content::RenderProcessHost** target_host,
content::WebContents* guest_contents) {
*guests_found = *guests_found + 1;
*target_host = guest_contents->GetMainFrame()->GetProcess();
// Don't short-circuit, so we can count how many other guest contents
// there are.
return false;
};
guest_view::GuestViewManager::FromBrowserContext(browser_context())
->ForEachGuest(
GetSenderWebContents(),
base::BindRepeating(get_guest, &guests_found, &target_host));
if (!target_host) {
SetError("No webview render process found");
return nullptr;
}
if (guests_found > 1) {
SetError("Multiple webviews found");
return nullptr;
}
return target_host;
}
// If |guest_process_id| is defined, directly use this id to find the
// corresponding RenderProcessHost.
if (request.guest_process_id.get()) {
content::RenderProcessHost* rph =
content::RenderProcessHost::FromID(*request.guest_process_id);
if (!rph) {
SetError(
base::StringPrintf("Failed to get RPH fro guest proccess ID (%d).",
*request.guest_process_id));
}
return rph;
}
// Otherwise, use the |tab_id|. If there's no |target_viewview|, no |tab_id|,
// and no |guest_process_id|, we can't look up the RenderProcessHost.
if (!request.tab_id.get()) {
SetError("No webview, tab ID, or guest process ID specified.");
return nullptr;
}
int tab_id = *request.tab_id;
content::WebContents* contents = nullptr;
if (!ExtensionTabUtil::GetTabById(tab_id, GetProfile(), true, nullptr,
nullptr, &contents, nullptr)) {
SetError(extensions::ErrorUtils::FormatErrorMessage(
extensions::tabs_constants::kTabNotFoundError,
base::IntToString(tab_id)));
return nullptr;
}
if (!contents) {
SetError("Web contents for tab not found.");
return nullptr;
}
GURL expected_origin = contents->GetLastCommittedURL().GetOrigin();
if (expected_origin.spec() != security_origin) {
SetError(base::StringPrintf(
"Invalid security origin. Expected=%s, actual=%s",
expected_origin.spec().c_str(), security_origin.c_str()));
return nullptr;
}
content::RenderProcessHost* rph = contents->GetMainFrame()->GetProcess();
if (!rph) {
SetError("Failed to get RPH.");
}
return rph;
}
scoped_refptr<WebRtcLoggingHandlerHost>
WebrtcLoggingPrivateFunction::LoggingHandlerFromRequest(
const api::webrtc_logging_private::RequestInfo& request,
const std::string& security_origin) {
content::RenderProcessHost* host = RphFromRequest(request, security_origin);
if (!host) {
// SetError() will have been called by RphFromRequest().
return nullptr;
}
return base::UserDataAdapter<WebRtcLoggingHandlerHost>::Get(
host, WebRtcLoggingHandlerHost::kWebRtcLoggingHandlerHostKey);
}
scoped_refptr<WebRtcLoggingHandlerHost>
WebrtcLoggingPrivateFunctionWithGenericCallback::PrepareTask(
const RequestInfo& request, const std::string& security_origin,
WebRtcLoggingHandlerHost::GenericDoneCallback* callback) {
*callback = base::Bind(
&WebrtcLoggingPrivateFunctionWithGenericCallback::FireCallback, this);
return LoggingHandlerFromRequest(request, security_origin);
}
void WebrtcLoggingPrivateFunctionWithGenericCallback::FireCallback(
bool success, const std::string& error_message) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!success)
SetError(error_message);
SendResponse(success);
}
void WebrtcLoggingPrivateFunctionWithUploadCallback::FireCallback(
bool success, const std::string& report_id,
const std::string& error_message) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (success) {
api::webrtc_logging_private::UploadResult result;
result.report_id = report_id;
SetResult(result.ToValue());
} else {
SetError(error_message);
}
SendResponse(success);
}
void WebrtcLoggingPrivateFunctionWithRecordingDoneCallback::FireErrorCallback(
const std::string& error_message) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetError(error_message);
SendResponse(false);
}
void WebrtcLoggingPrivateFunctionWithRecordingDoneCallback::FireCallback(
const std::string& prefix_path,
bool did_stop,
bool did_manual_stop) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
api::webrtc_logging_private::RecordingInfo result;
result.prefix_path = prefix_path;
result.did_stop = did_stop;
result.did_manual_stop = did_manual_stop;
SetResult(result.ToValue());
SendResponse(true);
}
bool WebrtcLoggingPrivateSetMetaDataFunction::RunAsync() {
std::unique_ptr<SetMetaData::Params> params(
SetMetaData::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
WebRtcLoggingHandlerHost::GenericDoneCallback callback;
scoped_refptr<WebRtcLoggingHandlerHost> webrtc_logging_handler_host =
PrepareTask(params->request, params->security_origin, &callback);
if (!webrtc_logging_handler_host.get())
return false;
std::unique_ptr<MetaDataMap> meta_data(new MetaDataMap());
for (const MetaDataEntry& entry : params->meta_data)
(*meta_data)[entry.key] = entry.value;
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::BindOnce(&WebRtcLoggingHandlerHost::SetMetaData,
webrtc_logging_handler_host,
std::move(meta_data), callback));
return true;
}
bool WebrtcLoggingPrivateStartFunction::RunAsync() {
std::unique_ptr<Start::Params> params(Start::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
WebRtcLoggingHandlerHost::GenericDoneCallback callback;
scoped_refptr<WebRtcLoggingHandlerHost> webrtc_logging_handler_host =
PrepareTask(params->request, params->security_origin, &callback);
if (!webrtc_logging_handler_host.get())
return false;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&WebRtcLoggingHandlerHost::StartLogging,
webrtc_logging_handler_host, callback));
return true;
}
bool WebrtcLoggingPrivateSetUploadOnRenderCloseFunction::RunAsync() {
std::unique_ptr<SetUploadOnRenderClose::Params> params(
SetUploadOnRenderClose::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
scoped_refptr<WebRtcLoggingHandlerHost> webrtc_logging_handler_host(
LoggingHandlerFromRequest(params->request, params->security_origin));
if (!webrtc_logging_handler_host.get())
return false;
webrtc_logging_handler_host->set_upload_log_on_render_close(
params->should_upload);
// Post a task since this is an asynchronous extension function.
// TODO(devlin): This is unneccessary; this should just be a
// UIThreadExtensionFunction. Fix this.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(
&WebrtcLoggingPrivateSetUploadOnRenderCloseFunction::SendResponse,
this, true));
return true;
}
bool WebrtcLoggingPrivateStopFunction::RunAsync() {
std::unique_ptr<Stop::Params> params(Stop::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
WebRtcLoggingHandlerHost::GenericDoneCallback callback;
scoped_refptr<WebRtcLoggingHandlerHost> webrtc_logging_handler_host =
PrepareTask(params->request, params->security_origin, &callback);
if (!webrtc_logging_handler_host.get())
return false;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&WebRtcLoggingHandlerHost::StopLogging,
webrtc_logging_handler_host, callback));
return true;
}
bool WebrtcLoggingPrivateStoreFunction::RunAsync() {
std::unique_ptr<Store::Params> params(Store::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
WebRtcLoggingHandlerHost::GenericDoneCallback callback;
scoped_refptr<WebRtcLoggingHandlerHost> webrtc_logging_handler_host =
PrepareTask(params->request, params->security_origin, &callback);
if (!webrtc_logging_handler_host.get())
return false;
const std::string local_log_id(HashIdWithOrigin(params->security_origin,
params->log_id));
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&WebRtcLoggingHandlerHost::StoreLog,
webrtc_logging_handler_host, local_log_id, callback));
return true;
}
bool WebrtcLoggingPrivateUploadStoredFunction::RunAsync() {
std::unique_ptr<UploadStored::Params> params(
UploadStored::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
scoped_refptr<WebRtcLoggingHandlerHost> logging_handler(
LoggingHandlerFromRequest(params->request, params->security_origin));
if (!logging_handler.get())
return false;
WebRtcLoggingHandlerHost::UploadDoneCallback callback = base::Bind(
&WebrtcLoggingPrivateUploadStoredFunction::FireCallback, this);
const std::string local_log_id(HashIdWithOrigin(params->security_origin,
params->log_id));
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&WebRtcLoggingHandlerHost::UploadStoredLog,
logging_handler, local_log_id, callback));
return true;
}
bool WebrtcLoggingPrivateUploadFunction::RunAsync() {
std::unique_ptr<Upload::Params> params(Upload::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
scoped_refptr<WebRtcLoggingHandlerHost> logging_handler(
LoggingHandlerFromRequest(params->request, params->security_origin));
if (!logging_handler.get())
return false;
WebRtcLoggingHandlerHost::UploadDoneCallback callback = base::Bind(
&WebrtcLoggingPrivateUploadFunction::FireCallback, this);
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::BindOnce(&WebRtcLoggingHandlerHost::UploadLog,
logging_handler, callback));
return true;
}
bool WebrtcLoggingPrivateDiscardFunction::RunAsync() {
std::unique_ptr<Discard::Params> params(Discard::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
WebRtcLoggingHandlerHost::GenericDoneCallback callback;
scoped_refptr<WebRtcLoggingHandlerHost> webrtc_logging_handler_host =
PrepareTask(params->request, params->security_origin, &callback);
if (!webrtc_logging_handler_host.get())
return false;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&WebRtcLoggingHandlerHost::DiscardLog,
webrtc_logging_handler_host, callback));
return true;
}
bool WebrtcLoggingPrivateStartRtpDumpFunction::RunAsync() {
std::unique_ptr<StartRtpDump::Params> params(
StartRtpDump::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
if (!params->incoming && !params->outgoing) {
FireCallback(false, "Either incoming or outgoing must be true.");
return true;
}
RtpDumpType type =
(params->incoming && params->outgoing)
? RTP_DUMP_BOTH
: (params->incoming ? RTP_DUMP_INCOMING : RTP_DUMP_OUTGOING);
content::RenderProcessHost* host =
RphFromRequest(params->request, params->security_origin);
if (!host) {
// SetError() will have been called by RphFromRequest().
return false;
}
scoped_refptr<WebRtcLoggingHandlerHost> webrtc_logging_handler_host(
base::UserDataAdapter<WebRtcLoggingHandlerHost>::Get(
host, WebRtcLoggingHandlerHost::kWebRtcLoggingHandlerHostKey));
WebRtcLoggingHandlerHost::GenericDoneCallback callback = base::Bind(
&WebrtcLoggingPrivateStartRtpDumpFunction::FireCallback, this);
// This call cannot fail.
content::RenderProcessHost::WebRtcStopRtpDumpCallback stop_callback =
host->StartRtpDump(params->incoming,
params->outgoing,
base::Bind(&WebRtcLoggingHandlerHost::OnRtpPacket,
webrtc_logging_handler_host));
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&WebRtcLoggingHandlerHost::StartRtpDump,
webrtc_logging_handler_host, type, callback,
stop_callback));
return true;
}
bool WebrtcLoggingPrivateStopRtpDumpFunction::RunAsync() {
std::unique_ptr<StopRtpDump::Params> params(
StopRtpDump::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
if (!params->incoming && !params->outgoing) {
FireCallback(false, "Either incoming or outgoing must be true.");
return true;
}
RtpDumpType type =
(params->incoming && params->outgoing)
? RTP_DUMP_BOTH
: (params->incoming ? RTP_DUMP_INCOMING : RTP_DUMP_OUTGOING);
content::RenderProcessHost* host =
RphFromRequest(params->request, params->security_origin);
if (!host) {
// SetError() will have been called by RphFromRequest().
return false;
}
scoped_refptr<WebRtcLoggingHandlerHost> webrtc_logging_handler_host(
base::UserDataAdapter<WebRtcLoggingHandlerHost>::Get(
host, WebRtcLoggingHandlerHost::kWebRtcLoggingHandlerHostKey));
WebRtcLoggingHandlerHost::GenericDoneCallback callback = base::Bind(
&WebrtcLoggingPrivateStopRtpDumpFunction::FireCallback, this);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&WebRtcLoggingHandlerHost::StopRtpDump,
webrtc_logging_handler_host, type, callback));
return true;
}
bool WebrtcLoggingPrivateStartAudioDebugRecordingsFunction::RunAsync() {
if (!CanEnableAudioDebugRecordingsFromExtension(extension())) {
return false;
}
std::unique_ptr<StartAudioDebugRecordings::Params> params(
StartAudioDebugRecordings::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
if (params->seconds < 0) {
FireErrorCallback("seconds must be greater than or equal to 0");
return true;
}
content::RenderProcessHost* host =
RphFromRequest(params->request, params->security_origin);
if (!host) {
// SetError() will have been called by RphFromRequest().
return false;
}
scoped_refptr<AudioDebugRecordingsHandler> audio_debug_recordings_handler(
base::UserDataAdapter<AudioDebugRecordingsHandler>::Get(
host, AudioDebugRecordingsHandler::kAudioDebugRecordingsHandlerKey));
audio_debug_recordings_handler->StartAudioDebugRecordings(
host, base::TimeDelta::FromSeconds(params->seconds),
base::Bind(
&WebrtcLoggingPrivateStartAudioDebugRecordingsFunction::FireCallback,
this),
base::Bind(&WebrtcLoggingPrivateStartAudioDebugRecordingsFunction::
FireErrorCallback,
this));
return true;
}
bool WebrtcLoggingPrivateStopAudioDebugRecordingsFunction::RunAsync() {
if (!CanEnableAudioDebugRecordingsFromExtension(extension())) {
return false;
}
std::unique_ptr<StopAudioDebugRecordings::Params> params(
StopAudioDebugRecordings::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
content::RenderProcessHost* host =
RphFromRequest(params->request, params->security_origin);
if (!host) {
// SetError() will have been called by RphFromRequest().
return false;
}
scoped_refptr<AudioDebugRecordingsHandler> audio_debug_recordings_handler(
base::UserDataAdapter<AudioDebugRecordingsHandler>::Get(
host, AudioDebugRecordingsHandler::kAudioDebugRecordingsHandlerKey));
audio_debug_recordings_handler->StopAudioDebugRecordings(
host,
base::Bind(
&WebrtcLoggingPrivateStopAudioDebugRecordingsFunction::FireCallback,
this),
base::Bind(&WebrtcLoggingPrivateStopAudioDebugRecordingsFunction::
FireErrorCallback,
this));
return true;
}
bool WebrtcLoggingPrivateStartEventLoggingFunction::RunAsync() {
std::unique_ptr<StartEventLogging::Params> params(
StartEventLogging::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
content::RenderProcessHost* host =
RphFromRequest(params->request, params->security_origin);
if (!host) {
// SetError() will have been called by RphFromRequest().
return false;
}
scoped_refptr<WebRtcLoggingHandlerHost> webrtc_logging_handler_host(
base::UserDataAdapter<WebRtcLoggingHandlerHost>::Get(
host, WebRtcLoggingHandlerHost::kWebRtcLoggingHandlerHostKey));
if (!webrtc_logging_handler_host.get()) {
SetError("WebRTC logging handler not found.");
return false;
}
WebRtcLoggingHandlerHost::GenericDoneCallback callback = base::Bind(
&WebrtcLoggingPrivateStartEventLoggingFunction::FireCallback, this);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&WebRtcLoggingHandlerHost::StartEventLogging,
webrtc_logging_handler_host, params->peer_connection_id,
params->max_log_size_bytes, params->metadata, callback));
return true;
}
bool WebrtcLoggingPrivateGetLogsDirectoryFunction::RunAsync() {
#if defined(OS_LINUX) || defined(OS_CHROMEOS)
// Unlike other WebrtcLoggingPrivate functions that take a RequestInfo object,
// this function shouldn't be called by a component extension on behalf of
// some web code. It returns a DirectoryEntry for use directly in the calling
// app or extension. Consequently, this function should run with the
// extension's render process host, since that is the caller's render process
// that should be granted access to the logs directory.
content::RenderProcessHost* host = render_frame_host()->GetProcess();
scoped_refptr<WebRtcLoggingHandlerHost> webrtc_logging_handler_host(
base::UserDataAdapter<WebRtcLoggingHandlerHost>::Get(
host, WebRtcLoggingHandlerHost::kWebRtcLoggingHandlerHostKey));
if (!webrtc_logging_handler_host.get()) {
FireErrorCallback("WebRTC logging handler not found.");
return true;
}
webrtc_logging_handler_host->GetLogsDirectory(
base::Bind(&WebrtcLoggingPrivateGetLogsDirectoryFunction::FireCallback,
this),
base::Bind(
&WebrtcLoggingPrivateGetLogsDirectoryFunction::FireErrorCallback,
this));
return true;
#else // defined(OS_LINUX) || defined(OS_CHROMEOS)
SetError("Not supported on the current OS");
SendResponse(false);
return false;
#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
}
void WebrtcLoggingPrivateGetLogsDirectoryFunction::FireCallback(
const std::string& filesystem_id,
const std::string& base_name) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetString("fileSystemId", filesystem_id);
dict->SetString("baseName", base_name);
Respond(OneArgument(std::move(dict)));
}
void WebrtcLoggingPrivateGetLogsDirectoryFunction::FireErrorCallback(
const std::string& error_message) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetError(error_message);
SendResponse(false);
}
} // namespace extensions