Skip to content

Commit 341b370

Browse files
authored
fix: handle redirects within registered protocols (electron#29796)
1 parent 3f38681 commit 341b370

File tree

4 files changed

+200
-39
lines changed

4 files changed

+200
-39
lines changed

shell/browser/net/electron_url_loader_factory.cc

Lines changed: 96 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,83 @@ void OnWrite(std::unique_ptr<WriteData> write_data, MojoResult result) {
170170

171171
} // namespace
172172

173+
ElectronURLLoaderFactory::RedirectedRequest::RedirectedRequest(
174+
const net::RedirectInfo& redirect_info,
175+
mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
176+
int32_t request_id,
177+
uint32_t options,
178+
const network::ResourceRequest& request,
179+
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
180+
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
181+
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory_remote)
182+
: redirect_info_(redirect_info),
183+
request_id_(request_id),
184+
options_(options),
185+
request_(request),
186+
client_(std::move(client)),
187+
traffic_annotation_(traffic_annotation) {
188+
loader_receiver_.Bind(std::move(loader_receiver));
189+
loader_receiver_.set_disconnect_handler(
190+
base::BindOnce(&ElectronURLLoaderFactory::RedirectedRequest::DeleteThis,
191+
base::Unretained(this)));
192+
target_factory_remote_.Bind(std::move(target_factory_remote));
193+
target_factory_remote_.set_disconnect_handler(base::BindOnce(
194+
&ElectronURLLoaderFactory::RedirectedRequest::OnTargetFactoryError,
195+
base::Unretained(this)));
196+
}
197+
198+
ElectronURLLoaderFactory::RedirectedRequest::~RedirectedRequest() = default;
199+
200+
void ElectronURLLoaderFactory::RedirectedRequest::FollowRedirect(
201+
const std::vector<std::string>& removed_headers,
202+
const net::HttpRequestHeaders& modified_headers,
203+
const net::HttpRequestHeaders& modified_cors_exempt_headers,
204+
const absl::optional<GURL>& new_url) {
205+
// Update |request_| with info from the redirect, so that it's accurate
206+
// The following references code in WorkerScriptLoader::FollowRedirect
207+
bool should_clear_upload = false;
208+
net::RedirectUtil::UpdateHttpRequest(
209+
request_.url, request_.method, redirect_info_, removed_headers,
210+
modified_headers, &request_.headers, &should_clear_upload);
211+
request_.cors_exempt_headers.MergeFrom(modified_cors_exempt_headers);
212+
for (const std::string& name : removed_headers)
213+
request_.cors_exempt_headers.RemoveHeader(name);
214+
215+
if (should_clear_upload)
216+
request_.request_body = nullptr;
217+
218+
request_.url = redirect_info_.new_url;
219+
request_.method = redirect_info_.new_method;
220+
request_.site_for_cookies = redirect_info_.new_site_for_cookies;
221+
request_.referrer = GURL(redirect_info_.new_referrer);
222+
request_.referrer_policy = redirect_info_.new_referrer_policy;
223+
224+
// Create a new loader to process the redirect and destroy this one
225+
target_factory_remote_->CreateLoaderAndStart(
226+
loader_receiver_.Unbind(), request_id_, options_, request_,
227+
std::move(client_), traffic_annotation_);
228+
229+
DeleteThis();
230+
}
231+
232+
void ElectronURLLoaderFactory::RedirectedRequest::OnTargetFactoryError() {
233+
// Can't create a new loader at this point, so the request can't continue
234+
mojo::Remote<network::mojom::URLLoaderClient> client_remote(
235+
std::move(client_));
236+
client_remote->OnComplete(
237+
network::URLLoaderCompletionStatus(net::ERR_FAILED));
238+
client_remote.reset();
239+
240+
DeleteThis();
241+
}
242+
243+
void ElectronURLLoaderFactory::RedirectedRequest::DeleteThis() {
244+
loader_receiver_.reset();
245+
target_factory_remote_.reset();
246+
247+
delete this;
248+
}
249+
173250
// static
174251
mojo::PendingRemote<network::mojom::URLLoaderFactory>
175252
ElectronURLLoaderFactory::Create(ProtocolType type,
@@ -202,12 +279,18 @@ void ElectronURLLoaderFactory::CreateLoaderAndStart(
202279
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
203280
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
204281
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
205-
mojo::PendingRemote<network::mojom::URLLoaderFactory> proxy_factory;
282+
283+
// |StartLoading| is used for both intercepted and registered protocols,
284+
// and on redirects it needs a factory to use to create a loader for the
285+
// new request. So in this case, this factory is the target factory.
286+
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory;
287+
this->Clone(target_factory.InitWithNewPipeAndPassReceiver());
288+
206289
handler_.Run(
207290
request,
208291
base::BindOnce(&ElectronURLLoaderFactory::StartLoading, std::move(loader),
209292
request_id, options, request, std::move(client),
210-
traffic_annotation, std::move(proxy_factory), type_));
293+
traffic_annotation, std::move(target_factory), type_));
211294
}
212295

213296
// static
@@ -230,7 +313,7 @@ void ElectronURLLoaderFactory::StartLoading(
230313
const network::ResourceRequest& request,
231314
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
232315
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
233-
mojo::PendingRemote<network::mojom::URLLoaderFactory> proxy_factory,
316+
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
234317
ProtocolType type,
235318
gin::Arguments* args) {
236319
// Send network error when there is no argument passed.
@@ -280,47 +363,22 @@ void ElectronURLLoaderFactory::StartLoading(
280363
request.url.Resolve(location),
281364
net::RedirectUtil::GetReferrerPolicyHeader(head->headers.get()), false);
282365

283-
network::ResourceRequest new_request = request;
284-
new_request.method = redirect_info.new_method;
285-
new_request.url = redirect_info.new_url;
286-
new_request.site_for_cookies = redirect_info.new_site_for_cookies;
287-
new_request.referrer = GURL(redirect_info.new_referrer);
288-
new_request.referrer_policy = redirect_info.new_referrer_policy;
289-
290366
DCHECK(client.is_valid());
291367

292368
mojo::Remote<network::mojom::URLLoaderClient> client_remote(
293369
std::move(client));
294370

295371
client_remote->OnReceiveRedirect(redirect_info, std::move(head));
296372

297-
// Unbound client, so it an be passed to sub-methods
298-
client = client_remote.Unbind();
299-
// When the redirection comes from an intercepted scheme (which has
300-
// |proxy_factory| passed), we ask the proxy factory to create a loader
301-
// for new URL, otherwise we call |StartLoadingHttp|, which creates
302-
// loader with default factory.
303-
//
304-
// Note that when handling requests for intercepted scheme, creating loader
305-
// with default factory (i.e. calling StartLoadingHttp) would bypass the
306-
// ProxyingURLLoaderFactory, we have to explicitly use the proxy factory to
307-
// create loader so it is possible to have handlers of intercepted scheme
308-
// getting called recursively, which is a behavior expected in protocol
309-
// module.
310-
//
311-
// I'm not sure whether this is an intended behavior in Chromium.
312-
if (proxy_factory.is_valid()) {
313-
mojo::Remote<network::mojom::URLLoaderFactory> proxy_factory_remote(
314-
std::move(proxy_factory));
315-
316-
proxy_factory_remote->CreateLoaderAndStart(
317-
std::move(loader), request_id, options, new_request,
318-
std::move(client), traffic_annotation);
319-
} else {
320-
StartLoadingHttp(std::move(loader), new_request, std::move(client),
321-
traffic_annotation,
322-
gin::Dictionary::CreateEmpty(args->isolate()));
323-
}
373+
// Bind the URLLoader receiver and wait for a FollowRedirect request, or for
374+
// the remote to disconnect, which will happen if the request is aborted.
375+
// That may happen when the redirect is to a different scheme, which will
376+
// cause the URL loader to be destroyed and a new one created using the
377+
// factory for that scheme.
378+
new RedirectedRequest(redirect_info, std::move(loader), request_id, options,
379+
request, client_remote.Unbind(), traffic_annotation,
380+
std::move(target_factory));
381+
324382
return;
325383
}
326384

@@ -360,7 +418,7 @@ void ElectronURLLoaderFactory::StartLoading(
360418
}
361419
StartLoading(std::move(loader), request_id, options, request,
362420
std::move(client), traffic_annotation,
363-
std::move(proxy_factory), type, args);
421+
std::move(target_factory), type, args);
364422
break;
365423
}
366424
}

shell/browser/net/electron_url_loader_factory.h

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@
88
#include <map>
99
#include <string>
1010
#include <utility>
11+
#include <vector>
1112

1213
#include "mojo/public/cpp/bindings/pending_receiver.h"
1314
#include "mojo/public/cpp/bindings/pending_remote.h"
1415
#include "mojo/public/cpp/bindings/receiver_set.h"
1516
#include "mojo/public/cpp/bindings/remote.h"
1617
#include "net/url_request/url_request_job_factory.h"
1718
#include "services/network/public/cpp/self_deleting_url_loader_factory.h"
19+
#include "services/network/public/mojom/url_loader.mojom.h"
1820
#include "services/network/public/mojom/url_loader_factory.mojom.h"
1921
#include "services/network/public/mojom/url_response_head.mojom.h"
2022
#include "shell/common/gin_helper/dictionary.h"
23+
#include "third_party/abseil-cpp/absl/types/optional.h"
2124

2225
namespace electron {
2326

@@ -43,6 +46,52 @@ using HandlersMap =
4346
// Implementation of URLLoaderFactory.
4447
class ElectronURLLoaderFactory : public network::SelfDeletingURLLoaderFactory {
4548
public:
49+
// This class binds a URLLoader receiver in the case of a redirect, waiting
50+
// for |FollowRedirect| to be called at which point the new request will be
51+
// started, and the receiver will be unbound letting a new URLLoader bind it
52+
class RedirectedRequest : public network::mojom::URLLoader {
53+
public:
54+
RedirectedRequest(
55+
const net::RedirectInfo& redirect_info,
56+
mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
57+
int32_t request_id,
58+
uint32_t options,
59+
const network::ResourceRequest& request,
60+
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
61+
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
62+
mojo::PendingRemote<network::mojom::URLLoaderFactory>
63+
target_factory_remote);
64+
~RedirectedRequest() override;
65+
66+
// network::mojom::URLLoader:
67+
void FollowRedirect(
68+
const std::vector<std::string>& removed_headers,
69+
const net::HttpRequestHeaders& modified_headers,
70+
const net::HttpRequestHeaders& modified_cors_exempt_headers,
71+
const absl::optional<GURL>& new_url) override;
72+
void SetPriority(net::RequestPriority priority,
73+
int32_t intra_priority_value) override {}
74+
void PauseReadingBodyFromNet() override {}
75+
void ResumeReadingBodyFromNet() override {}
76+
77+
void OnTargetFactoryError();
78+
void DeleteThis();
79+
80+
private:
81+
net::RedirectInfo redirect_info_;
82+
83+
mojo::Receiver<network::mojom::URLLoader> loader_receiver_{this};
84+
int32_t request_id_;
85+
uint32_t options_;
86+
network::ResourceRequest request_;
87+
mojo::PendingRemote<network::mojom::URLLoaderClient> client_;
88+
net::MutableNetworkTrafficAnnotationTag traffic_annotation_;
89+
90+
mojo::Remote<network::mojom::URLLoaderFactory> target_factory_remote_;
91+
92+
DISALLOW_COPY_AND_ASSIGN(RedirectedRequest);
93+
};
94+
4695
static mojo::PendingRemote<network::mojom::URLLoaderFactory> Create(
4796
ProtocolType type,
4897
const ProtocolHandler& handler);
@@ -64,7 +113,7 @@ class ElectronURLLoaderFactory : public network::SelfDeletingURLLoaderFactory {
64113
const network::ResourceRequest& request,
65114
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
66115
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
67-
mojo::PendingRemote<network::mojom::URLLoaderFactory> proxy_factory,
116+
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
68117
ProtocolType type,
69118
gin::Arguments* args);
70119

spec-main/api-protocol-spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,24 @@ describe('protocol module', () => {
120120
const r = await ajax(protocolName + '://fake-host');
121121
expect(r.data).to.equal(text);
122122
});
123+
124+
it('can redirect to the same scheme', async () => {
125+
registerStringProtocol(protocolName, (request, callback) => {
126+
if (request.url === `${protocolName}://fake-host/redirect`) {
127+
callback({
128+
statusCode: 302,
129+
headers: {
130+
Location: `${protocolName}://fake-host`
131+
}
132+
});
133+
} else {
134+
expect(request.url).to.equal(`${protocolName}://fake-host`);
135+
callback('redirected');
136+
}
137+
});
138+
const r = await ajax(`${protocolName}://fake-host/redirect`);
139+
expect(r.data).to.equal('redirected');
140+
});
123141
});
124142

125143
describe('protocol.unregisterProtocol', () => {

spec-main/api-web-request-spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,42 @@ describe('webRequest module', () => {
181181
expect(data).to.equal('/header/received');
182182
});
183183

184+
it('can change the request headers on a custom protocol redirect', async () => {
185+
protocol.registerStringProtocol('custom-scheme', (req, callback) => {
186+
if (req.url === 'custom-scheme://fake-host/redirect') {
187+
callback({
188+
statusCode: 302,
189+
headers: {
190+
Location: 'custom-scheme://fake-host'
191+
}
192+
});
193+
} else {
194+
let content = '';
195+
if (req.headers.Accept === '*/*;test/header') {
196+
content = 'header-received';
197+
}
198+
callback(content);
199+
}
200+
});
201+
202+
// Note that we need to do navigation every time after a protocol is
203+
// registered or unregistered, otherwise the new protocol won't be
204+
// recognized by current page when NetworkService is used.
205+
await contents.loadFile(path.join(__dirname, 'fixtures', 'pages', 'jquery.html'));
206+
207+
try {
208+
ses.webRequest.onBeforeSendHeaders((details, callback) => {
209+
const requestHeaders = details.requestHeaders;
210+
requestHeaders.Accept = '*/*;test/header';
211+
callback({ requestHeaders: requestHeaders });
212+
});
213+
const { data } = await ajax('custom-scheme://fake-host/redirect');
214+
expect(data).to.equal('header-received');
215+
} finally {
216+
protocol.unregisterProtocol('custom-scheme');
217+
}
218+
});
219+
184220
it('can change request origin', async () => {
185221
ses.webRequest.onBeforeSendHeaders((details, callback) => {
186222
const requestHeaders = details.requestHeaders;

0 commit comments

Comments
 (0)