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
content / public / test / url_loader_interceptor.h [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_PUBLIC_TEST_URL_LOADER_INTERCEPTOR_H_
#define CONTENT_PUBLIC_TEST_URL_LOADER_INTERCEPTOR_H_
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include "base/files/file_path.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/synchronization/lock.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/net_errors.h"
#include "net/http/http_request_headers.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/url_loader.mojom.h"
namespace network {
class URLLoaderFactoryBuilder;
} // namespace network
namespace content {
// Helper class to intercept URLLoaderFactory calls for tests.
// This intercepts:
// -frame requests (which start from the browser)
// -subresource requests from pages, dedicated workers, and shared workers
// -by sending the renderer an intermediate URLLoaderFactory
// -subresource requests from service workers and requests of non-installed
// service worker scripts
// -at EmbeddedWorkerInstance
// -requests by the browser
// -http(s)://mock.failed.request/foo URLs internally, copying the behavior
// of net::URLRequestFailedJob
//
// Prefer not to use this class. In order of ease of use & simplicity:
// -if you need to serve static data, use net::test::EmbeddedTestServer and
// serve data from the source tree (e.g. in content/test/data).
// -if you need to control the response data at runtime, then use
// net::test_server::EmbeddedTestServer::RegisterRequestHandler.
// -if you need to delay when the server sends the response, use
// net::test_server::ControllableHttpResponse.
// -otherwise, if you need full control over the net::Error and/or want to
// inspect and/or modify the C++ structs used by URLLoader interface, then use
// this helper class.
//
// Notes:
// -the callback is called on the UI or IO threads depending on the factory
// that was hooked
// -this is done to avoid changing message order
// -intercepting resource requests for subresources changes message order by
// definition (since they would normally go directly from renderer->network
// service, but now they're routed through the browser).
class URLLoaderInterceptor {
public:
struct RequestParams {
RequestParams();
~RequestParams();
RequestParams(RequestParams&& other);
RequestParams& operator=(RequestParams&& other);
// See the comment for `url_loader_factory::TerminalParams::process_id_`.
int process_id;
// The following are the parameters to CreateLoaderAndStart.
mojo::PendingReceiver<network::mojom::URLLoader> receiver;
int32_t request_id;
uint32_t options;
network::ResourceRequest url_request;
mojo::Remote<network::mojom::URLLoaderClient> client;
net::MutableNetworkTrafficAnnotationTag traffic_annotation;
};
// Function signature for intercept method.
// Return true if the request was intercepted. Otherwise this class will
// forward the request to the original URLLoaderFactory.
using InterceptCallback =
base::RepeatingCallback<bool(RequestParams* params)>;
// Function signature for a loading completion method.
// This class will listen on loading completion responses from the network,
// invoke this callback, and delegate the response to the original client.
using URLLoaderCompletionStatusCallback = base::RepeatingCallback<void(
const GURL& request_url,
const network::URLLoaderCompletionStatus& status)>;
// Create an interceptor which calls |callback|. If |ready_callback| is not
// provided, a nested RunLoop is used to ensure the interceptor is ready
// before returning. If |ready_callback| is provided, no RunLoop is called,
// and instead |ready_callback| is called after the interceptor is installed.
// If provided, |completion_status_callback| is called when the load
// completes.
//
// In order to hook up `completion_status_callback`, the interceptor wraps all
// requests that the `intercept_callback` does not intercept, so destroying
// the URLLoaderInterceptor aborts all non-intercepted requests.
explicit URLLoaderInterceptor(
InterceptCallback intercept_callback,
const URLLoaderCompletionStatusCallback& completion_status_callback = {},
base::OnceClosure ready_callback = {});
URLLoaderInterceptor(const URLLoaderInterceptor&) = delete;
URLLoaderInterceptor& operator=(const URLLoaderInterceptor&) = delete;
~URLLoaderInterceptor();
// Serves static data, similar to net::test::EmbeddedTestServer, for
// cases where you need a static origin, such as tests with origin trials.
// Optional callback will notify callers for any accessed urls.
static std::unique_ptr<URLLoaderInterceptor> ServeFilesFromDirectoryAtOrigin(
const std::string& relative_base_path,
const GURL& origin,
base::RepeatingCallback<void(const GURL&)> callback = base::DoNothing());
// Helper methods for use when intercepting.
// Writes the given response body, header, and SSL Info to `client`.
// If `url` is present, also computes the ParsedHeaders for the response.
static void WriteResponse(std::string_view headers,
std::string_view body,
network::mojom::URLLoaderClient* client,
std::optional<net::SSLInfo> ssl_info = std::nullopt,
std::optional<GURL> url = std::nullopt);
// Reads the given path, relative to the root source directory, and writes it
// to |client|. For headers:
// 1) if |headers| is specified, it's used
// 2) otherwise if an adjoining file that ends in .mock-http-headers is
// found, its contents will be used
// 3) otherwise a simple 200 response will be used, with a Content-Type
// guessed from the file extension
// For SSL info, if |ssl_info| is specified, then it is added to the response.
// If `url` is present, also computes the ParsedHeaders for the response.
static void WriteResponse(const std::string& relative_path,
network::mojom::URLLoaderClient* client,
const std::string* headers = nullptr,
std::optional<net::SSLInfo> ssl_info = std::nullopt,
std::optional<GURL> url = std::nullopt);
// Like above, but uses an absolute file path.
static void WriteResponse(const base::FilePath& file_path,
network::mojom::URLLoaderClient* client,
const std::string* headers = nullptr,
std::optional<net::SSLInfo> ssl_info = std::nullopt,
std::optional<GURL> url = std::nullopt);
// Returns an interceptor that (as long as it says alive) will intercept
// requests to |url| and fail them using the provided |error|.
// |ready_callback| is optional and avoids the use of RunLoop, see
// the constructor for more detail.
static std::unique_ptr<URLLoaderInterceptor> SetupRequestFailForURL(
const GURL& url,
net::Error error,
base::OnceClosure ready_callback = {});
// Returns the URL of the last request processed by this interceptor.
//
// Use this function instead of creating a WebContentsObserver to observe
// request headers, if you need the last request url sent in the event of
// resends or redirects, as the NavigationHandle::GetRequestHeaders() function
// only returns the initial request's request headers.
const GURL& GetLastRequestURL();
// Returns the request headers of the last request processed by this
// interceptor.
//
// Use this function instead of creating a WebContentsObserver to observe
// request headers, if you need the last request headers sent in the event of
// resends or redirects, as the NavigationHandle::GetRequestHeaders() function
// only returns the initial request's request headers.
const net::HttpRequestHeaders& GetLastRequestHeaders();
private:
class IOState;
class Interceptor;
class Wrapper;
// Adds `this` as an interceptor when a `URLLoaderFactory` is about to be
// created. `Wrapper` plumbs related objects to `Intercept()`.
void InterceptorCallback(int process_id,
network::URLLoaderFactoryBuilder& factory_builder);
// Attempts to intercept the given request, returning true if it was
// intercepted.
bool Intercept(RequestParams* params);
// Called on IO thread at initialization and shutdown.
void InitializeOnIOThread(base::OnceClosure closure);
// Sets the request URL of the last request processed by this interceptor.
void SetLastRequestURL(const GURL& url);
// Sets the request headers of the last request processed by this interceptor.
void SetLastRequestHeaders(const net::HttpRequestHeaders& headers);
bool use_runloop_;
base::OnceClosure ready_callback_;
InterceptCallback callback_;
scoped_refptr<IOState> io_thread_;
// For intercepting non-frame requests from the browser process. There is one
// per StoragePartition. Only accessed on UI thread.
std::set<std::unique_ptr<Wrapper>> wrappers_on_ui_thread_;
base::Lock last_request_lock_;
GURL last_request_url_ GUARDED_BY(last_request_lock_);
net::HttpRequestHeaders last_request_headers_ GUARDED_BY(last_request_lock_);
};
} // namespace content
#endif // CONTENT_PUBLIC_TEST_URL_LOADER_INTERCEPTOR_H_