1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 #include "content/browser/devtools/protocol/fetch_handler.h"
5
6 #include <memory>
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "content/browser/devtools/devtools_agent_host_impl.h"
13 #include "content/browser/devtools/devtools_io_context.h"
14 #include "content/browser/devtools/devtools_stream_pipe.h"
15 #include "content/browser/devtools/devtools_url_loader_interceptor.h"
16 #include "content/browser/devtools/protocol/network_handler.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "net/http/http_request_headers.h"
19 #include "net/http/http_response_headers.h"
20 #include "net/http/http_status_code.h"
21 #include "net/http/http_util.h"
22
23 namespace content {
24 namespace protocol {
25
26 // static
ForAgentHost(DevToolsAgentHostImpl * host)27 std::vector<FetchHandler*> FetchHandler::ForAgentHost(
28 DevToolsAgentHostImpl* host) {
29 return host->HandlersByName<FetchHandler>(Fetch::Metainfo::domainName);
30 }
31
FetchHandler(DevToolsIOContext * io_context,UpdateLoaderFactoriesCallback update_loader_factories_callback)32 FetchHandler::FetchHandler(
33 DevToolsIOContext* io_context,
34 UpdateLoaderFactoriesCallback update_loader_factories_callback)
35 : DevToolsDomainHandler(Fetch::Metainfo::domainName),
36 io_context_(io_context),
37 update_loader_factories_callback_(
38 std::move(update_loader_factories_callback)) {}
39
40 FetchHandler::~FetchHandler() = default;
41
Wire(UberDispatcher * dispatcher)42 void FetchHandler::Wire(UberDispatcher* dispatcher) {
43 frontend_.reset(new Fetch::Frontend(dispatcher->channel()));
44 Fetch::Dispatcher::wire(dispatcher, this);
45 }
46
RequestStageToInterceptorStage(const Fetch::RequestStage & stage)47 DevToolsURLLoaderInterceptor::InterceptionStage RequestStageToInterceptorStage(
48 const Fetch::RequestStage& stage) {
49 if (stage == Fetch::RequestStageEnum::Request)
50 return DevToolsURLLoaderInterceptor::REQUEST;
51 if (stage == Fetch::RequestStageEnum::Response)
52 return DevToolsURLLoaderInterceptor::RESPONSE;
53 NOTREACHED();
54 return DevToolsURLLoaderInterceptor::REQUEST;
55 }
56
ToInterceptionPatterns(const Maybe<Array<Fetch::RequestPattern>> & maybe_patterns,std::vector<DevToolsURLLoaderInterceptor::Pattern> * result)57 Response ToInterceptionPatterns(
58 const Maybe<Array<Fetch::RequestPattern>>& maybe_patterns,
59 std::vector<DevToolsURLLoaderInterceptor::Pattern>* result) {
60 result->clear();
61 if (!maybe_patterns.isJust()) {
62 result->emplace_back("*", base::flat_set<blink::mojom::ResourceType>(),
63 DevToolsURLLoaderInterceptor::REQUEST);
64 return Response::Success();
65 }
66 Array<Fetch::RequestPattern>& patterns = *maybe_patterns.fromJust();
67 for (const std::unique_ptr<Fetch::RequestPattern>& pattern : patterns) {
68 base::flat_set<blink::mojom::ResourceType> resource_types;
69 std::string resource_type = pattern->GetResourceType("");
70 if (!resource_type.empty()) {
71 if (!NetworkHandler::AddInterceptedResourceType(resource_type,
72 &resource_types)) {
73 return Response::InvalidParams(
74 base::StringPrintf("Unknown resource type in fetch filter: '%s'",
75 resource_type.c_str()));
76 }
77 }
78 result->emplace_back(
79 pattern->GetUrlPattern("*"), std::move(resource_types),
80 RequestStageToInterceptorStage(
81 pattern->GetRequestStage(Fetch::RequestStageEnum::Request)));
82 }
83 return Response::Success();
84 }
85
MaybeCreateProxyForInterception(RenderProcessHost * rph,const base::UnguessableToken & frame_token,bool is_navigation,bool is_download,network::mojom::URLLoaderFactoryOverride * intercepting_factory)86 bool FetchHandler::MaybeCreateProxyForInterception(
87 RenderProcessHost* rph,
88 const base::UnguessableToken& frame_token,
89 bool is_navigation,
90 bool is_download,
91 network::mojom::URLLoaderFactoryOverride* intercepting_factory) {
92 return interceptor_ && interceptor_->CreateProxyForInterception(
93 rph, frame_token, is_navigation, is_download,
94 intercepting_factory);
95 }
96
Enable(Maybe<Array<Fetch::RequestPattern>> patterns,Maybe<bool> handleAuth,std::unique_ptr<EnableCallback> callback)97 void FetchHandler::Enable(Maybe<Array<Fetch::RequestPattern>> patterns,
98 Maybe<bool> handleAuth,
99 std::unique_ptr<EnableCallback> callback) {
100 if (!interceptor_) {
101 interceptor_ =
102 std::make_unique<DevToolsURLLoaderInterceptor>(base::BindRepeating(
103 &FetchHandler::RequestIntercepted, weak_factory_.GetWeakPtr()));
104 }
105 std::vector<DevToolsURLLoaderInterceptor::Pattern> interception_patterns;
106 Response response = ToInterceptionPatterns(patterns, &interception_patterns);
107 if (!response.IsSuccess()) {
108 callback->sendFailure(response);
109 return;
110 }
111 if (!interception_patterns.size() && handleAuth.fromMaybe(false)) {
112 callback->sendFailure(Response::InvalidParams(
113 "Can\'t specify empty patterns with handleAuth set"));
114 return;
115 }
116 interceptor_->SetPatterns(std::move(interception_patterns),
117 handleAuth.fromMaybe(false));
118 update_loader_factories_callback_.Run(
119 base::BindOnce(&EnableCallback::sendSuccess, std::move(callback)));
120 }
121
Disable()122 Response FetchHandler::Disable() {
123 const bool was_enabled = !!interceptor_;
124 interceptor_.reset();
125 if (was_enabled)
126 update_loader_factories_callback_.Run(base::DoNothing());
127 return Response::Success();
128 }
129
130 namespace {
131 using ContinueInterceptedRequestCallback =
132 DevToolsURLLoaderInterceptor::ContinueInterceptedRequestCallback;
133
134 template <typename Callback, typename Base, typename... Args>
135 class CallbackWrapper : public Base {
136 public:
CallbackWrapper(std::unique_ptr<Callback> callback)137 explicit CallbackWrapper(std::unique_ptr<Callback> callback)
138 : callback_(std::move(callback)) {}
139
140 private:
141 friend typename std::unique_ptr<CallbackWrapper>::deleter_type;
142
sendSuccess(Args...args)143 void sendSuccess(Args... args) override {
144 callback_->sendSuccess(std::forward<Args>(args)...);
145 }
sendFailure(const DispatchResponse & response)146 void sendFailure(const DispatchResponse& response) override {
147 callback_->sendFailure(response);
148 }
fallThrough()149 void fallThrough() override { NOTREACHED(); }
~CallbackWrapper()150 ~CallbackWrapper() override {}
151
152 std::unique_ptr<Callback> callback_;
153 };
154
155 template <typename Callback>
156 std::unique_ptr<CallbackWrapper<Callback, ContinueInterceptedRequestCallback>>
WrapCallback(std::unique_ptr<Callback> cb)157 WrapCallback(std::unique_ptr<Callback> cb) {
158 return std::make_unique<
159 CallbackWrapper<Callback, ContinueInterceptedRequestCallback>>(
160 std::move(cb));
161 }
162
163 template <typename Callback>
ValidateHeaders(Fetch::HeaderEntry * entry,Callback * callback)164 bool ValidateHeaders(Fetch::HeaderEntry* entry, Callback* callback) {
165 if (!net::HttpUtil::IsValidHeaderName(entry->GetName()) ||
166 !net::HttpUtil::IsValidHeaderValue(entry->GetValue())) {
167 callback->sendFailure(Response::InvalidParams("Invalid header"));
168 return false;
169 }
170 return true;
171 }
172 } // namespace
173
FailRequest(const String & requestId,const String & errorReason,std::unique_ptr<FailRequestCallback> callback)174 void FetchHandler::FailRequest(const String& requestId,
175 const String& errorReason,
176 std::unique_ptr<FailRequestCallback> callback) {
177 if (!interceptor_) {
178 callback->sendFailure(Response::ServerError("Fetch domain is not enabled"));
179 return;
180 }
181 bool ok = false;
182 net::Error reason = NetworkHandler::NetErrorFromString(errorReason, &ok);
183 if (!ok) {
184 callback->sendFailure(Response::InvalidParams("Unknown errorReason"));
185 return;
186 }
187 auto modifications =
188 std::make_unique<DevToolsURLLoaderInterceptor::Modifications>(reason);
189 interceptor_->ContinueInterceptedRequest(requestId, std::move(modifications),
190 WrapCallback(std::move(callback)));
191 }
192
FulfillRequest(const String & requestId,int responseCode,Maybe<Array<Fetch::HeaderEntry>> responseHeaders,Maybe<Binary> binaryResponseHeaders,Maybe<Binary> body,Maybe<String> responsePhrase,std::unique_ptr<FulfillRequestCallback> callback)193 void FetchHandler::FulfillRequest(
194 const String& requestId,
195 int responseCode,
196 Maybe<Array<Fetch::HeaderEntry>> responseHeaders,
197 Maybe<Binary> binaryResponseHeaders,
198 Maybe<Binary> body,
199 Maybe<String> responsePhrase,
200 std::unique_ptr<FulfillRequestCallback> callback) {
201 if (!interceptor_) {
202 callback->sendFailure(Response::ServerError("Fetch domain is not enabled"));
203 return;
204 }
205 std::string status_phrase =
206 responsePhrase.isJust()
207 ? responsePhrase.fromJust()
208 : net::GetHttpReasonPhrase(
209 static_cast<net::HttpStatusCode>(responseCode));
210 if (status_phrase.empty()) {
211 callback->sendFailure(
212 Response::InvalidParams("Invalid http status code or phrase"));
213 return;
214 }
215 std::string headers =
216 base::StringPrintf("HTTP/1.1 %d %s", responseCode, status_phrase.c_str());
217 headers.append(1, '\0');
218 if (responseHeaders.isJust()) {
219 if (binaryResponseHeaders.isJust()) {
220 callback->sendFailure(Response::InvalidParams(
221 "Only one of responseHeaders or binaryHeaders may be present"));
222 return;
223 }
224 for (const auto& entry : *responseHeaders.fromJust()) {
225 if (!ValidateHeaders(entry.get(), callback.get()))
226 return;
227 headers.append(entry->GetName());
228 headers.append(":");
229 headers.append(entry->GetValue());
230 headers.append(1, '\0');
231 }
232 } else if (binaryResponseHeaders.isJust()) {
233 Binary response_headers = binaryResponseHeaders.fromJust();
234 headers.append(reinterpret_cast<const char*>(response_headers.data()),
235 response_headers.size());
236 if (headers.back() != '\0')
237 headers.append(1, '\0');
238 }
239 headers.append(1, '\0');
240 auto modifications =
241 std::make_unique<DevToolsURLLoaderInterceptor::Modifications>(
242 base::MakeRefCounted<net::HttpResponseHeaders>(headers),
243 body.isJust() ? body.fromJust().bytes() : nullptr);
244 interceptor_->ContinueInterceptedRequest(requestId, std::move(modifications),
245 WrapCallback(std::move(callback)));
246 }
247
ContinueRequest(const String & requestId,Maybe<String> url,Maybe<String> method,Maybe<String> postData,Maybe<Array<Fetch::HeaderEntry>> headers,std::unique_ptr<ContinueRequestCallback> callback)248 void FetchHandler::ContinueRequest(
249 const String& requestId,
250 Maybe<String> url,
251 Maybe<String> method,
252 Maybe<String> postData,
253 Maybe<Array<Fetch::HeaderEntry>> headers,
254 std::unique_ptr<ContinueRequestCallback> callback) {
255 if (!interceptor_) {
256 callback->sendFailure(Response::ServerError("Fetch domain is not enabled"));
257 return;
258 }
259 std::unique_ptr<DevToolsURLLoaderInterceptor::Modifications::HeadersVector>
260 request_headers;
261 if (headers.isJust()) {
262 request_headers = std::make_unique<
263 DevToolsURLLoaderInterceptor::Modifications::HeadersVector>();
264 for (const std::unique_ptr<Fetch::HeaderEntry>& entry :
265 *headers.fromJust()) {
266 if (!ValidateHeaders(entry.get(), callback.get()))
267 return;
268 request_headers->emplace_back(entry->GetName(), entry->GetValue());
269 }
270 }
271 auto modifications =
272 std::make_unique<DevToolsURLLoaderInterceptor::Modifications>(
273 std::move(url), std::move(method), std::move(postData),
274 std::move(request_headers));
275 interceptor_->ContinueInterceptedRequest(requestId, std::move(modifications),
276 WrapCallback(std::move(callback)));
277 }
278
ContinueWithAuth(const String & requestId,std::unique_ptr<protocol::Fetch::AuthChallengeResponse> authChallengeResponse,std::unique_ptr<ContinueWithAuthCallback> callback)279 void FetchHandler::ContinueWithAuth(
280 const String& requestId,
281 std::unique_ptr<protocol::Fetch::AuthChallengeResponse>
282 authChallengeResponse,
283 std::unique_ptr<ContinueWithAuthCallback> callback) {
284 if (!interceptor_) {
285 callback->sendFailure(Response::ServerError("Fetch domain is not enabled"));
286 return;
287 }
288 using AuthChallengeResponse =
289 DevToolsURLLoaderInterceptor::AuthChallengeResponse;
290 std::unique_ptr<AuthChallengeResponse> auth_response;
291 std::string type = authChallengeResponse->GetResponse();
292 if (type == Network::AuthChallengeResponse::ResponseEnum::Default) {
293 auth_response = std::make_unique<AuthChallengeResponse>(
294 AuthChallengeResponse::kDefault);
295 } else if (type == Network::AuthChallengeResponse::ResponseEnum::CancelAuth) {
296 auth_response = std::make_unique<AuthChallengeResponse>(
297 AuthChallengeResponse::kCancelAuth);
298 } else if (type ==
299 Network::AuthChallengeResponse::ResponseEnum::ProvideCredentials) {
300 auth_response = std::make_unique<AuthChallengeResponse>(
301 base::UTF8ToUTF16(authChallengeResponse->GetUsername("")),
302 base::UTF8ToUTF16(authChallengeResponse->GetPassword("")));
303 } else {
304 callback->sendFailure(
305 Response::InvalidParams("Unrecognized authChallengeResponse"));
306 return;
307 }
308 auto modifications =
309 std::make_unique<DevToolsURLLoaderInterceptor::Modifications>(
310 std::move(auth_response));
311 interceptor_->ContinueInterceptedRequest(requestId, std::move(modifications),
312 WrapCallback(std::move(callback)));
313 }
314
GetResponseBody(const String & requestId,std::unique_ptr<GetResponseBodyCallback> callback)315 void FetchHandler::GetResponseBody(
316 const String& requestId,
317 std::unique_ptr<GetResponseBodyCallback> callback) {
318 if (!interceptor_) {
319 callback->sendFailure(Response::ServerError("Fetch domain is not enabled"));
320 return;
321 }
322 auto weapped_callback = std::make_unique<CallbackWrapper<
323 GetResponseBodyCallback,
324 DevToolsURLLoaderInterceptor::GetResponseBodyForInterceptionCallback,
325 const std::string&, bool>>(std::move(callback));
326 interceptor_->GetResponseBody(requestId, std::move(weapped_callback));
327 }
328
TakeResponseBodyAsStream(const String & requestId,std::unique_ptr<TakeResponseBodyAsStreamCallback> callback)329 void FetchHandler::TakeResponseBodyAsStream(
330 const String& requestId,
331 std::unique_ptr<TakeResponseBodyAsStreamCallback> callback) {
332 if (!interceptor_) {
333 callback->sendFailure(Response::ServerError("Fetch domain is not enabled"));
334 return;
335 }
336 interceptor_->TakeResponseBodyPipe(
337 requestId,
338 base::BindOnce(&FetchHandler::OnResponseBodyPipeTaken,
339 weak_factory_.GetWeakPtr(), std::move(callback)));
340 }
341
OnResponseBodyPipeTaken(std::unique_ptr<TakeResponseBodyAsStreamCallback> callback,Response response,mojo::ScopedDataPipeConsumerHandle pipe,const std::string & mime_type)342 void FetchHandler::OnResponseBodyPipeTaken(
343 std::unique_ptr<TakeResponseBodyAsStreamCallback> callback,
344 Response response,
345 mojo::ScopedDataPipeConsumerHandle pipe,
346 const std::string& mime_type) {
347 DCHECK_CURRENTLY_ON(BrowserThread::UI);
348 DCHECK_EQ(response.IsSuccess(), pipe.is_valid());
349 if (!response.IsSuccess()) {
350 callback->sendFailure(std::move(response));
351 return;
352 }
353 // The pipe stream is owned only by io_context after we return.
354 bool is_binary = !DevToolsIOContext::IsTextMimeType(mime_type);
355 auto stream =
356 DevToolsStreamPipe::Create(io_context_, std::move(pipe), is_binary);
357 callback->sendSuccess(stream->handle());
358 }
359
360 namespace {
361
ToHeaderEntryArray(scoped_refptr<net::HttpResponseHeaders> headers)362 std::unique_ptr<Array<Fetch::HeaderEntry>> ToHeaderEntryArray(
363 scoped_refptr<net::HttpResponseHeaders> headers) {
364 auto result = std::make_unique<Array<Fetch::HeaderEntry>>();
365 size_t iterator = 0;
366 std::string name;
367 std::string value;
368 while (headers->EnumerateHeaderLines(&iterator, &name, &value)) {
369 result->emplace_back(
370 Fetch::HeaderEntry::Create().SetName(name).SetValue(value).Build());
371 }
372 return result;
373 }
374
375 } // namespace
376
RequestIntercepted(std::unique_ptr<InterceptedRequestInfo> info)377 void FetchHandler::RequestIntercepted(
378 std::unique_ptr<InterceptedRequestInfo> info) {
379 protocol::Maybe<protocol::Network::ErrorReason> error_reason;
380 if (info->response_error_code < 0)
381 error_reason = NetworkHandler::NetErrorToString(info->response_error_code);
382
383 Maybe<int> status_code;
384 Maybe<Array<Fetch::HeaderEntry>> response_headers;
385 if (info->response_headers) {
386 status_code = info->response_headers->response_code();
387 response_headers = ToHeaderEntryArray(info->response_headers);
388 }
389
390 if (info->auth_challenge) {
391 auto auth_challenge =
392 Fetch::AuthChallenge::Create()
393 .SetSource(info->auth_challenge->is_proxy
394 ? Network::AuthChallenge::SourceEnum::Proxy
395 : Network::AuthChallenge::SourceEnum::Server)
396 .SetOrigin(info->auth_challenge->challenger.Serialize())
397 .SetScheme(info->auth_challenge->scheme)
398 .SetRealm(info->auth_challenge->realm)
399 .Build();
400 frontend_->AuthRequired(
401 info->interception_id, std::move(info->network_request),
402 info->frame_id.ToString(),
403 NetworkHandler::ResourceTypeToString(info->resource_type),
404 std::move(auth_challenge));
405 return;
406 }
407 frontend_->RequestPaused(
408 info->interception_id, std::move(info->network_request),
409 info->frame_id.ToString(),
410 NetworkHandler::ResourceTypeToString(info->resource_type),
411 std::move(error_reason), std::move(status_code),
412 std::move(response_headers), std::move(info->renderer_request_id));
413 }
414
415 } // namespace protocol
416 } // namespace content
417