blob: bc7969facf821e23f7b07980acf0e9259a752249 [file] [log] [blame]
// Copyright 2017 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 "extensions/browser/api/web_request/web_request_info.h"
#include <memory>
#include <string>
#include "base/files/file_path.h"
#include "base/values.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/websocket_handshake_request_info.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/extension_api_frame_id_map.h"
#include "extensions/browser/extension_navigation_ui_data.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_data_stream.h"
#include "net/base/upload_file_element_reader.h"
#include "net/log/net_log_with_source.h"
#include "net/url_request/url_request.h"
#include "services/network/public/cpp/resource_response.h"
namespace keys = extension_web_request_api_constants;
namespace extensions {
namespace {
// UploadDataSource abstracts an interface for feeding an arbitrary data element
// to an UploadDataPresenter. This is helpful because in the Network Service vs
// non-Network Service case, upload data comes from different types of source
// objects, but we'd like to share parsing code.
class UploadDataSource {
public:
virtual ~UploadDataSource() {}
virtual void FeedToPresenter(UploadDataPresenter* presenter) = 0;
};
class BytesUploadDataSource : public UploadDataSource {
public:
BytesUploadDataSource(const base::StringPiece& bytes) : bytes_(bytes) {}
~BytesUploadDataSource() override = default;
// UploadDataSource:
void FeedToPresenter(UploadDataPresenter* presenter) override {
presenter->FeedBytes(bytes_);
}
private:
base::StringPiece bytes_;
DISALLOW_COPY_AND_ASSIGN(BytesUploadDataSource);
};
class FileUploadDataSource : public UploadDataSource {
public:
FileUploadDataSource(const base::FilePath& path) : path_(path) {}
~FileUploadDataSource() override = default;
// UploadDataSource:
void FeedToPresenter(UploadDataPresenter* presenter) override {
presenter->FeedFile(path_);
}
private:
base::FilePath path_;
DISALLOW_COPY_AND_ASSIGN(FileUploadDataSource);
};
std::unique_ptr<base::Value> NetLogExtensionIdCallback(
const std::string& extension_id,
net::NetLogCaptureMode capture_mode) {
auto params = std::make_unique<base::DictionaryValue>();
params->SetString("extension_id", extension_id);
return params;
}
// Implements Logger using NetLog, mirroring the logging facilities used prior
// to the introduction of WebRequestInfo.
// TODO(crbug.com/721414): Transition away from using NetLog.
class NetLogLogger : public WebRequestInfo::Logger {
public:
explicit NetLogLogger(net::URLRequest* request) : request_(request) {}
~NetLogLogger() override = default;
// WebRequestInfo::Logger:
void LogEvent(net::NetLogEventType event_type,
const std::string& extension_id) override {
request_->net_log().AddEvent(
event_type,
base::BindRepeating(&NetLogExtensionIdCallback, extension_id));
}
void LogBlockedBy(const std::string& blocker_info) override {
// LogAndReport allows extensions that block requests to be displayed in the
// load status bar.
request_->LogAndReportBlockedBy(blocker_info.c_str());
}
void LogUnblocked() override { request_->LogUnblocked(); }
private:
net::URLRequest* const request_;
DISALLOW_COPY_AND_ASSIGN(NetLogLogger);
};
// TODO(https://crbug.com/721414): Need a real implementation here to support
// the Network Service case. For now this is only to prevent crashing.
class NetworkServiceLogger : public WebRequestInfo::Logger {
public:
NetworkServiceLogger() = default;
~NetworkServiceLogger() override = default;
// WebRequestInfo::Logger:
void LogEvent(net::NetLogEventType event_type,
const std::string& extension_id) override {}
void LogBlockedBy(const std::string& blocker_info) override {}
void LogUnblocked() override {}
private:
DISALLOW_COPY_AND_ASSIGN(NetworkServiceLogger);
};
bool CreateUploadDataSourcesFromURLRequest(
net::URLRequest* url_request,
std::vector<std::unique_ptr<UploadDataSource>>* data_sources) {
const net::UploadDataStream* upload_data = url_request->get_upload();
if (!upload_data)
return false;
const std::vector<std::unique_ptr<net::UploadElementReader>>* readers =
upload_data->GetElementReaders();
if (!readers)
return true;
for (const auto& reader : *readers) {
if (const auto* bytes_reader = reader->AsBytesReader()) {
data_sources->push_back(std::make_unique<BytesUploadDataSource>(
base::StringPiece(bytes_reader->bytes(), bytes_reader->length())));
} else if (const auto* file_reader = reader->AsFileReader()) {
data_sources->push_back(
std::make_unique<FileUploadDataSource>(file_reader->path()));
} else {
DVLOG(1) << "Ignoring upsupported upload data type for WebRequest API.";
}
}
return true;
}
bool CreateUploadDataSourcesFromResourceRequest(
const network::ResourceRequest& request,
std::vector<std::unique_ptr<UploadDataSource>>* data_sources) {
if (!request.request_body)
return false;
for (auto& element : *request.request_body->elements()) {
switch (element.type()) {
case network::DataElement::TYPE_DATA_PIPE:
// TODO(https://crbug.com/721414): Support data pipe elements.
break;
case network::DataElement::TYPE_BYTES:
data_sources->push_back(std::make_unique<BytesUploadDataSource>(
base::StringPiece(element.bytes(), element.length())));
break;
case network::DataElement::TYPE_FILE:
// Should not be hit in the Network Service case.
NOTREACHED();
break;
default:
NOTIMPLEMENTED();
break;
}
}
return true;
}
std::unique_ptr<base::DictionaryValue> CreateRequestBodyData(
const std::string& method,
const net::HttpRequestHeaders& request_headers,
const std::vector<std::unique_ptr<UploadDataSource>>& data_sources) {
if (method != "POST" && method != "PUT")
return nullptr;
auto request_body_data = std::make_unique<base::DictionaryValue>();
// Get the data presenters, ordered by how specific they are.
ParsedDataPresenter parsed_data_presenter(request_headers);
RawDataPresenter raw_data_presenter;
UploadDataPresenter* const presenters[] = {
&parsed_data_presenter, // 1: any parseable forms? (Specific to forms.)
&raw_data_presenter // 2: any data at all? (Non-specific.)
};
// Keys for the results of the corresponding presenters.
static const char* const kKeys[] = {keys::kRequestBodyFormDataKey,
keys::kRequestBodyRawKey};
bool some_succeeded = false;
if (!data_sources.empty()) {
for (size_t i = 0; i < arraysize(presenters); ++i) {
for (auto& source : data_sources)
source->FeedToPresenter(presenters[i]);
if (presenters[i]->Succeeded()) {
request_body_data->Set(kKeys[i], presenters[i]->Result());
some_succeeded = true;
break;
}
}
}
if (!some_succeeded)
request_body_data->SetString(keys::kRequestBodyErrorKey, "Unknown error.");
return request_body_data;
}
} // namespace
WebRequestInfo::WebRequestInfo() = default;
WebRequestInfo::WebRequestInfo(net::URLRequest* url_request)
: id(url_request->identifier()),
url(url_request->url()),
site_for_cookies(url_request->site_for_cookies()),
method(url_request->method()),
initiator(url_request->initiator()),
extra_request_headers(url_request->extra_request_headers()),
is_pac_request(url_request->is_pac_request()),
logger(std::make_unique<NetLogLogger>(url_request)) {
if (url.SchemeIsWSOrWSS()) {
web_request_type = WebRequestResourceType::WEB_SOCKET;
// TODO(pkalinnikov): Consider embedding WebSocketHandshakeRequestInfo into
// UrlRequestUserData.
const content::WebSocketHandshakeRequestInfo* ws_info =
content::WebSocketHandshakeRequestInfo::ForRequest(url_request);
if (ws_info) {
render_process_id = ws_info->GetChildId();
frame_id = ws_info->GetRenderFrameId();
}
} else if (auto* info =
content::ResourceRequestInfo::ForRequest(url_request)) {
render_process_id = info->GetChildID();
routing_id = info->GetRouteID();
frame_id = info->GetRenderFrameID();
type = info->GetResourceType();
web_request_type = ToWebRequestResourceType(type.value());
is_async = info->IsAsync();
resource_context = info->GetContext();
} else {
// There may be basic process and frame info associated with the request
// even when |info| is null. Attempt to grab it as a last ditch effort. If
// this fails, we have no frame info.
content::ResourceRequestInfo::GetRenderFrameForRequest(
url_request, &render_process_id, &frame_id);
}
ExtensionsBrowserClient* browser_client = ExtensionsBrowserClient::Get();
ExtensionNavigationUIData* navigation_ui_data =
browser_client ? browser_client->GetExtensionNavigationUIData(url_request)
: nullptr;
if (navigation_ui_data)
is_browser_side_navigation = true;
InitializeWebViewAndFrameData(navigation_ui_data);
std::vector<std::unique_ptr<UploadDataSource>> data_sources;
if (CreateUploadDataSourcesFromURLRequest(url_request, &data_sources)) {
request_body_data =
CreateRequestBodyData(method, extra_request_headers, data_sources);
}
}
WebRequestInfo::WebRequestInfo(
uint64_t request_id,
int render_process_id,
int render_frame_id,
std::unique_ptr<ExtensionNavigationUIData> navigation_ui_data,
int32_t routing_id,
content::ResourceContext* resource_context,
const network::ResourceRequest& request)
: id(request_id),
url(request.url),
site_for_cookies(request.site_for_cookies),
render_process_id(render_process_id),
routing_id(routing_id),
frame_id(render_frame_id),
method(request.method),
is_browser_side_navigation(!!navigation_ui_data),
initiator(request.request_initiator),
type(static_cast<content::ResourceType>(request.resource_type)),
extra_request_headers(request.headers),
logger(std::make_unique<NetworkServiceLogger>()),
resource_context(resource_context) {
if (url.SchemeIsWSOrWSS())
web_request_type = WebRequestResourceType::WEB_SOCKET;
else
web_request_type = ToWebRequestResourceType(type.value());
InitializeWebViewAndFrameData(navigation_ui_data.get());
std::vector<std::unique_ptr<UploadDataSource>> data_sources;
if (CreateUploadDataSourcesFromResourceRequest(request, &data_sources)) {
request_body_data =
CreateRequestBodyData(method, extra_request_headers, data_sources);
}
// TODO(https://crbug.com/721414): For this constructor (i.e. the Network
// Service case), we are still missing information for |is_async| and
// |is_pac_request|.
}
WebRequestInfo::~WebRequestInfo() = default;
void WebRequestInfo::AddResponseInfoFromURLRequest(
net::URLRequest* url_request) {
response_code = url_request->GetResponseCode();
response_headers = url_request->response_headers();
response_ip = url_request->GetSocketAddress().host();
response_from_cache = url_request->was_cached();
}
void WebRequestInfo::AddResponseInfoFromResourceResponse(
const network::ResourceResponseHead& response) {
response_headers = response.headers;
if (response_headers)
response_code = response_headers->response_code();
response_ip = response.socket_address.host();
// TODO(https://crbug.com/721414): We have no apparent source for this
// information yet in the Network Service case. Should indicate whether or not
// the response data came from cache.
response_from_cache = false;
}
void WebRequestInfo::InitializeWebViewAndFrameData(
const ExtensionNavigationUIData* navigation_ui_data) {
if (navigation_ui_data) {
is_web_view = navigation_ui_data->is_web_view();
web_view_instance_id = navigation_ui_data->web_view_instance_id();
web_view_rules_registry_id =
navigation_ui_data->web_view_rules_registry_id();
frame_data = navigation_ui_data->frame_data();
} else if (frame_id >= 0) {
// Grab any WebView-related information if relevant.
WebViewRendererState::WebViewInfo web_view_info;
if (WebViewRendererState::GetInstance()->GetInfo(
render_process_id, routing_id, &web_view_info)) {
is_web_view = true;
web_view_instance_id = web_view_info.instance_id;
web_view_rules_registry_id = web_view_info.rules_registry_id;
web_view_embedder_process_id = web_view_info.embedder_process_id;
}
// For subresource loads we attempt to resolve the FrameData immediately
// anyway using cached information.
ExtensionApiFrameIdMap::FrameData data;
bool was_cached = ExtensionApiFrameIdMap::Get()->GetCachedFrameDataOnIO(
render_process_id, frame_id, &data);
if (was_cached)
frame_data = data;
}
}
} // namespace extensions