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/callback_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<protocol::Binary> 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<protocol::Binary> 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