1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
|
// 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 "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(crbug.com/40194454): 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 OnBeforeConnect(const net::IPEndPoint& server_address) override {}
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(crbug.com/40791652): 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.GetDeprecatedID();
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
|