blob: b99d90aa0c257ce7350767269d5a53d56f8167c0 [file] [log] [blame]
// Copyright 2021 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_proxying_webtransport.h"
#include <optional>
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/api/web_request/extension_web_request_event_router.h"
#include "extensions/browser/api/web_request/web_request_api.h"
#include "extensions/browser/api/web_request/web_request_info.h"
#include "extensions/browser/browser_context_keyed_api_factory.h"
#include "extensions/browser/extension_navigation_ui_data.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/public/mojom/web_transport.mojom.h"
#include "url/gurl.h"
namespace extensions {
namespace {
using network::mojom::WebTransportHandshakeClient;
using CreateCallback =
content::ContentBrowserClient::WillCreateWebTransportCallback;
net::HttpRequestHeaders GetRequestHeaders() {
// We don't attach certain headers:
// 1. We cannot store pseudo-headers to `request_headers_` and they can be
// accessed via other ways, e.g., "url" for :scheme, :authority and
// :path.
// 2. We don't attach the "origin" header, to be aligned with the usual
// loading case. Extension authors can use the "initiator" property to
// observe it.
auto headers = net::HttpRequestHeaders();
// TODO(1240935): Share the code with
// DedicatedWebTransportHttp3Client::DoSendRequest.
headers.SetHeader("sec-webtransport-http3-draft02", "1");
return headers;
}
class WebTransportHandshakeProxy : public WebRequestAPI::Proxy,
public WebTransportHandshakeClient {
public:
WebTransportHandshakeProxy(
mojo::PendingRemote<WebTransportHandshakeClient> handshake_client,
WebRequestAPI::ProxySet& proxies,
content::BrowserContext* browser_context,
WebRequestInfoInitParams params,
CreateCallback create_callback)
: handshake_client_(std::move(handshake_client)),
proxies_(proxies),
browser_context_(browser_context),
info_(std::move(params)),
create_callback_(std::move(create_callback)) {
DCHECK(handshake_client_);
DCHECK(create_callback_);
}
~WebTransportHandshakeProxy() override {
// This is important to ensure that no outstanding blocking requests
// continue to reference state owned by this object.
WebRequestEventRouter::Get(browser_context_)
->OnRequestWillBeDestroyed(browser_context_, &info_);
}
void Start() {
bool should_collapse_initiator = false;
// Since WebTransport doesn't support redirect, 'redirect_url' is ignored
// even if extensions assigned it.
const int result =
WebRequestEventRouter::Get(browser_context_)
->OnBeforeRequest(
browser_context_, &info_,
base::BindOnce(
&WebTransportHandshakeProxy::OnBeforeRequestCompleted,
base::Unretained(this)),
&redirect_url_, &should_collapse_initiator);
// It doesn't make sense to collapse WebTransport requests since they won't
// be associated with a DOM element.
CHECK(!should_collapse_initiator);
if (result == net::ERR_IO_PENDING)
return;
DCHECK(result == net::OK || result == net::ERR_BLOCKED_BY_CLIENT) << result;
OnBeforeRequestCompleted(result);
}
void OnBeforeRequestCompleted(int error_code) {
if (error_code != net::OK) {
OnError(error_code);
// `this` is deleted.
return;
}
request_headers_ = GetRequestHeaders();
const int result =
WebRequestEventRouter::Get(browser_context_)
->OnBeforeSendHeaders(
browser_context_, &info_,
base::BindOnce(
&WebTransportHandshakeProxy::OnBeforeSendHeadersCompleted,
base::Unretained(this)),
&request_headers_);
if (result == net::ERR_IO_PENDING)
return;
DCHECK(result == net::OK || result == net::ERR_BLOCKED_BY_CLIENT) << result;
// See the comments in the OnBeforeSendHeadersCompleted to see why
// we pass empty values.
OnBeforeSendHeadersCompleted({}, {}, result);
}
void OnBeforeSendHeadersCompleted(
const std::set<std::string>& removed_headers,
const std::set<std::string>& set_headers,
int error_code) {
if (error_code != net::OK) {
OnError(error_code);
// `this` is deleted.
return;
}
// We don't allow extension authors to add/remove/change request headers,
// as that may lead to a WebTransport over HTTP/3 protocol violation. We may
// change this policy once https://github.com/w3c/webtransport/issues/263 is
// resolved.
WebRequestEventRouter::Get(browser_context_)
->OnSendHeaders(browser_context_, &info_, GetRequestHeaders());
// Set up proxing.
remote_.Bind(std::move(handshake_client_));
remote_.set_disconnect_handler(
base::BindOnce(&WebTransportHandshakeProxy::OnError,
base::Unretained(this), net::ERR_ABORTED));
std::move(create_callback_)
.Run(receiver_.BindNewPipeAndPassRemote(), std::nullopt);
receiver_.set_disconnect_handler(
base::BindOnce(&WebTransportHandshakeProxy::OnError,
base::Unretained(this), net::ERR_ABORTED));
}
// WebTransportHandshakeClient implementation:
// Proxing should be finished with either of below functions.
void OnConnectionEstablished(
mojo::PendingRemote<network::mojom::WebTransport> transport,
mojo::PendingReceiver<network::mojom::WebTransportClient> client,
const scoped_refptr<net::HttpResponseHeaders>& response_headers,
network::mojom::WebTransportStatsPtr initial_stats) override {
receiver_.reset();
pending_transport_ = std::move(transport);
pending_client_ = std::move(client);
initial_stats_ = std::move(initial_stats);
response_headers_ = response_headers;
bool should_collapse_initiator = false;
// Since WebTransport doesn't support redirect, 'redirect_url' is ignored
// even if extensions assigned it.
const int result =
WebRequestEventRouter::Get(browser_context_)
->OnHeadersReceived(
browser_context_, &info_,
base::BindOnce(
&WebTransportHandshakeProxy::OnHeadersReceivedCompleted,
base::Unretained(this)),
response_headers_.get(), &override_headers_, &redirect_url_,
&should_collapse_initiator);
// It doesn't make sense to collapse WebTransport requests since they won't
// be associated with a DOM element.
CHECK(!should_collapse_initiator);
if (result == net::ERR_IO_PENDING)
return;
DCHECK(result == net::OK || result == net::ERR_BLOCKED_BY_CLIENT) << result;
OnHeadersReceivedCompleted(result);
}
void OnHeadersReceivedCompleted(int error_code) {
if (error_code != net::OK) {
OnError(error_code);
return;
}
network::mojom::URLResponseHead response;
response.headers =
override_headers_ ? override_headers_ : response_headers_;
DCHECK(response.headers);
// TODO(1250090): Assign actual server IP 'response';
response.remote_endpoint = net::IPEndPoint();
// Web transport doesn't use the http cache.
response.was_fetched_via_cache = false;
info_.AddResponseInfoFromResourceResponse(response);
WebRequestEventRouter::Get(browser_context_)
->OnResponseStarted(browser_context_, &info_, net::OK);
remote_->OnConnectionEstablished(
std::move(pending_transport_), std::move(pending_client_),
response.headers, std::move(initial_stats_));
OnCompleted();
// `this` is deleted.
}
void OnHandshakeFailed(
const std::optional<net::WebTransportError>& error) override {
remote_->OnHandshakeFailed(error);
int error_code = net::ERR_ABORTED;
if (error.has_value()) {
error_code = error->net_error;
}
OnError(error_code);
// `this` is deleted.
}
void OnError(int error_code) {
DCHECK_NE(error_code, net::OK);
if (create_callback_) {
auto webtransport_error = network::mojom::WebTransportError::New(
error_code, quic::QUIC_INTERNAL_ERROR, "Blocked by an extension",
false);
std::move(create_callback_)
.Run(std::move(handshake_client_), std::move(webtransport_error));
}
WebRequestEventRouter::Get(browser_context_)
->OnErrorOccurred(browser_context_, &info_, /*started=*/true,
error_code);
proxies_->RemoveProxy(this);
// `this` is deleted.
}
void OnCompleted() {
WebRequestEventRouter::Get(browser_context_)
->OnCompleted(browser_context_, &info_, net::OK);
// Delete `this`.
proxies_->RemoveProxy(this);
}
private:
// WebRequestAPI::Proxy:
void OnDNRExtensionUnloaded(const Extension* extension) override {
info_.EraseDNRActionsForExtension(extension->id());
}
mojo::PendingRemote<WebTransportHandshakeClient> handshake_client_;
// Weak reference to the ProxySet. This is safe as `proxies_` owns this
// object.
const raw_ref<WebRequestAPI::ProxySet> proxies_;
raw_ptr<content::BrowserContext> browser_context_;
WebRequestInfo info_;
net::HttpRequestHeaders request_headers_;
GURL redirect_url_;
mojo::Remote<WebTransportHandshakeClient> remote_;
mojo::Receiver<WebTransportHandshakeClient> receiver_{this};
scoped_refptr<net::HttpResponseHeaders> response_headers_;
scoped_refptr<net::HttpResponseHeaders> override_headers_;
mojo::PendingRemote<network::mojom::WebTransport> pending_transport_;
mojo::PendingReceiver<network::mojom::WebTransportClient> pending_client_;
network::mojom::WebTransportStatsPtr initial_stats_;
CreateCallback create_callback_;
};
} // namespace
void StartWebRequestProxyingWebTransport(
content::RenderProcessHost& render_process_host,
int frame_routing_id,
const GURL& url,
const url::Origin& initiator_origin,
mojo::PendingRemote<WebTransportHandshakeClient> handshake_client,
int64_t request_id,
WebRequestAPI::ProxySet& proxies,
content::ContentBrowserClient::WillCreateWebTransportCallback callback) {
content::BrowserContext* browser_context =
render_process_host.GetBrowserContext();
// Filling ResourceRequest fields required to create WebRequestInfoInitParams.
network::ResourceRequest request;
request.method = net::HttpRequestHeaders::kConnectMethod;
request.url = url;
request.request_initiator = initiator_origin;
const int process_id = render_process_host.GetID();
WebRequestInfoInitParams params =
WebRequestInfoInitParams(request_id, process_id, frame_routing_id,
/*navigation_ui_data=*/nullptr, request,
/*is_download=*/false,
/*is_async=*/true,
/*is_service_worker_script=*/false,
/*navigation_id=*/std::nullopt);
params.web_request_type = WebRequestResourceType::WEB_TRANSPORT;
auto proxy = std::make_unique<WebTransportHandshakeProxy>(
std::move(handshake_client), proxies, browser_context, std::move(params),
std::move(callback));
auto* raw_proxy = proxy.get();
proxies.AddProxy(std::move(proxy));
raw_proxy->Start();
}
} // namespace extensions