blob: 326de18080f5dfb5bd13c9d705fd13f654632817 [file]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/api/web_request/web_request_event_details.h"
#include <string>
#include <utility>
#include <vector>
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/browser/api/extensions_api_client.h"
#include "extensions/browser/api/web_request/upload_data_presenter.h"
#include "extensions/browser/api/web_request/web_request_api_constants.h"
#include "extensions/browser/api/web_request/web_request_api_helpers.h"
#include "extensions/browser/api/web_request/web_request_info.h"
#include "extensions/browser/api/web_request/web_request_permissions.h"
#include "extensions/browser/api/web_request/web_request_resource_type.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/permissions/permissions_data.h"
#include "net/base/auth.h"
#include "net/base/upload_data_stream.h"
#include "net/cert/cert_status_flags.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
using extension_web_request_api_helpers::ExtraInfoSpec;
namespace helpers = extension_web_request_api_helpers;
namespace keys = extension_web_request_api_constants;
namespace extensions {
namespace {
// Removes all headers for which predicate(header_name) returns true.
void EraseHeadersIf(
base::ListValue& headers,
base::RepeatingCallback<bool(const std::string&)> predicate) {
headers.EraseIf([&predicate](const base::Value& v) {
return predicate.Run(*v.GetDict().FindString(keys::kHeaderNameKey));
});
}
void FilterSecurityInfo(base::DictValue& result, int extra_info_spec) {
if (!(extra_info_spec & ExtraInfoSpec::SECURITY_INFO)) {
result.Remove(keys::kSecurityInfoKey);
return;
}
if (!(extra_info_spec & ExtraInfoSpec::SECURITY_INFO_RAW_DER)) {
auto* security_info = result.FindDict(keys::kSecurityInfoKey);
if (!security_info) {
return;
}
auto* certificates = security_info->FindList(keys::kCertificatesKey);
if (!certificates || certificates->size() <= 0) {
return;
}
if (certificates->front().is_dict()) {
certificates->front().GetDict().Remove(keys::kRawDerKey);
}
}
}
std::string StringifyCertificateFingerprintBytes(
base::span<uint8_t, 32> bytes) {
// Reserve memory for 32 bytes * 3 chars - 1 = 95.
std::string fingerprint_str;
fingerprint_str.reserve(95);
for (size_t i = 0; i < bytes.size(); ++i) {
if (i > 0) {
fingerprint_str.push_back(':');
}
// %02X ensures uppercase hex with leading zero padding
base::StringAppendF(&fingerprint_str, "%02X", bytes[i]);
}
return fingerprint_str;
}
} // namespace
WebRequestEventDetails::WebRequestEventDetails(const WebRequestInfo& request,
int extra_info_spec)
: extra_info_spec_(extra_info_spec),
render_process_id_(content::ChildProcessHost::kInvalidUniqueID) {
dict_.Set(keys::kMethodKey, request.method);
dict_.Set(keys::kRequestIdKey, base::NumberToString(request.id));
dict_.Set(keys::kTimeStampKey,
base::Time::Now().InMillisecondsFSinceUnixEpoch());
dict_.Set(keys::kTypeKey,
WebRequestResourceTypeToString(request.web_request_type));
dict_.Set(keys::kUrlKey, request.url.spec());
dict_.Set(keys::kTabIdKey, request.frame_data.tab_id);
dict_.Set(keys::kFrameIdKey, request.frame_data.frame_id);
dict_.Set(keys::kParentFrameIdKey, request.frame_data.parent_frame_id);
if (request.frame_data.document_id) {
dict_.Set(keys::kDocumentIdKey, request.frame_data.document_id.ToString());
}
if (request.frame_data.parent_document_id) {
dict_.Set(keys::kParentDocumentIdKey,
request.frame_data.parent_document_id.ToString());
}
if (request.frame_data.frame_id >= 0) {
dict_.Set(keys::kFrameTypeKey, ToString(request.frame_data.frame_type));
dict_.Set(keys::kDocumentLifecycleKey,
ToString(request.frame_data.document_lifecycle));
}
initiator_ = request.initiator;
render_process_id_ = request.render_process_id;
}
WebRequestEventDetails::~WebRequestEventDetails() = default;
void WebRequestEventDetails::SetRequestBody(WebRequestInfo* request) {
if (!(extra_info_spec_ & ExtraInfoSpec::REQUEST_BODY)) {
return;
}
request_body_ = std::nullopt;
if (request->request_body_data) {
request_body_ = std::move(request->request_body_data);
request->request_body_data.reset();
}
}
void WebRequestEventDetails::SetRequestHeaders(
const net::HttpRequestHeaders& request_headers) {
if (!(extra_info_spec_ & ExtraInfoSpec::REQUEST_HEADERS)) {
return;
}
request_headers_ = base::ListValue();
for (net::HttpRequestHeaders::Iterator it(request_headers); it.GetNext();) {
request_headers_->Append(
helpers::CreateHeaderDictionary(it.name(), it.value()));
}
}
void WebRequestEventDetails::SetAuthInfo(
const net::AuthChallengeInfo& auth_info) {
dict_.Set(keys::kIsProxyKey, auth_info.is_proxy);
if (!auth_info.scheme.empty()) {
dict_.Set(keys::kSchemeKey, auth_info.scheme);
}
if (!auth_info.realm.empty()) {
dict_.Set(keys::kRealmKey, auth_info.realm);
}
base::DictValue challenger;
challenger.Set(keys::kHostKey, auth_info.challenger.host());
challenger.Set(keys::kPortKey, auth_info.challenger.port());
dict_.Set(keys::kChallengerKey, std::move(challenger));
}
void WebRequestEventDetails::SetResponseHeaders(
const WebRequestInfo& request,
const net::HttpResponseHeaders* response_headers) {
if (!response_headers) {
// Not all URLRequestJobs specify response headers. E.g. URLRequestFTPJob,
// URLRequestFileJob and some redirects.
dict_.Set(keys::kStatusCodeKey, request.response_code);
dict_.Set(keys::kStatusLineKey, "");
} else {
dict_.Set(keys::kStatusCodeKey, response_headers->response_code());
dict_.Set(keys::kStatusLineKey, response_headers->GetStatusLine());
}
if (extra_info_spec_ & ExtraInfoSpec::RESPONSE_HEADERS) {
response_headers_ = base::ListValue();
if (response_headers) {
size_t iter = 0;
std::string name;
std::string value;
while (response_headers->EnumerateHeaderLines(&iter, &name, &value)) {
if (ExtensionsAPIClient::Get()->ShouldHideResponseHeader(request.url,
name)) {
continue;
}
response_headers_->Append(helpers::CreateHeaderDictionary(name, value));
}
}
}
}
void WebRequestEventDetails::SetSecurityInfo(const WebRequestInfo& request) {
if (!(extra_info_spec_ & ExtraInfoSpec::SECURITY_INFO)) {
return;
}
base::DictValue security_info;
if (!request.ssl_info || !request.ssl_info->cert) {
security_info.Set(keys::kStateKey, "insecure");
} else {
if (net::IsCertStatusError(request.ssl_info->cert_status)) {
security_info.Set(keys::kStateKey, "broken");
} else {
security_info.Set(keys::kStateKey, "secure");
}
base::DictValue leaf_cert;
if (extra_info_spec_ & ExtraInfoSpec::SECURITY_INFO_RAW_DER) {
base::span<const uint8_t> cert_span = request.ssl_info->cert->cert_span();
leaf_cert.Set(keys::kRawDerKey, base::Value::BlobStorage(
cert_span.begin(), cert_span.end()));
}
base::DictValue fingerprint;
std::array<uint8_t, 32> sha256_bytes =
net::X509Certificate::CalculateFingerprint256(
request.ssl_info->cert->cert_buffer());
fingerprint.Set(keys::kSha256Key,
StringifyCertificateFingerprintBytes(sha256_bytes));
leaf_cert.Set(keys::kFingerprintKey, std::move(fingerprint));
base::ListValue certificates;
certificates.Append(std::move(leaf_cert));
security_info.Set(keys::kCertificatesKey, std::move(certificates));
}
dict_.Set(keys::kSecurityInfoKey, std::move(security_info));
}
void WebRequestEventDetails::SetResponseSource(const WebRequestInfo& request) {
dict_.Set(keys::kFromCache, request.response_from_cache);
if (!request.response_ip.empty()) {
dict_.Set(keys::kIpKey, request.response_ip);
}
}
base::DictValue WebRequestEventDetails::GetFilteredDict(
int extra_info_spec,
PermissionHelper* permission_helper,
const extensions::ExtensionId& extension_id,
bool crosses_incognito) const {
base::DictValue result = dict_.Clone();
if ((extra_info_spec & ExtraInfoSpec::REQUEST_BODY) && request_body_) {
result.Set(keys::kRequestBodyKey, request_body_->Clone());
}
if ((extra_info_spec & ExtraInfoSpec::REQUEST_HEADERS) && request_headers_) {
content::RenderProcessHost* process =
content::RenderProcessHost::FromID(render_process_id_);
content::BrowserContext* browser_context =
process ? process->GetBrowserContext() : nullptr;
base::ListValue request_headers = request_headers_->Clone();
EraseHeadersIf(request_headers,
base::BindRepeating(helpers::ShouldHideRequestHeader,
browser_context, extra_info_spec));
result.Set(keys::kRequestHeadersKey, std::move(request_headers));
}
if ((extra_info_spec & ExtraInfoSpec::RESPONSE_HEADERS) &&
response_headers_) {
base::ListValue response_headers = response_headers_->Clone();
EraseHeadersIf(response_headers,
base::BindRepeating(helpers::ShouldHideResponseHeader,
extra_info_spec));
result.Set(keys::kResponseHeadersKey, std::move(response_headers));
}
FilterSecurityInfo(result, extra_info_spec);
// Only listeners with a permission for the initiator should receive it.
if (initiator_) {
int tab_id = dict_.FindInt(keys::kTabIdKey).value_or(-1);
if (initiator_->opaque() ||
WebRequestPermissions::CanExtensionAccessInitiator(
permission_helper, extension_id, initiator_, tab_id,
crosses_incognito)) {
result.Set(keys::kInitiatorKey, initiator_->Serialize());
}
}
return result;
}
base::DictValue WebRequestEventDetails::GetAndClearDict() {
return std::move(dict_);
}
} // namespace extensions