Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(136)

Side by Side Diff: content/browser/devtools/devtools_url_interceptor_request_job.cc

Issue 2739323003: DevTools protocol interception, blocking & modification of requests (Closed)
Patch Set: Add a comment Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 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
5 #include "content/browser/devtools/devtools_url_interceptor_request_job.h"
6
7 #include "base/memory/ptr_util.h"
8 #include "base/strings/stringprintf.h"
9 #include "content/browser/devtools/protocol/network_handler.h"
10 #include "net/base/elements_upload_data_stream.h"
11 #include "net/base/io_buffer.h"
12 #include "net/base/upload_bytes_element_reader.h"
13 #include "net/base/upload_element_reader.h"
14 #include "net/cert/cert_status_flags.h"
15 #include "net/http/http_response_headers.h"
16 #include "net/http/http_util.h"
17 #include "net/url_request/url_request_context.h"
18
19 namespace content {
20
21 namespace {
22 void SendRequestInterceptedEventOnUiThread(
23 base::WeakPtr<protocol::NetworkHandler> network_handler,
24 std::string interception_id,
25 std::unique_ptr<protocol::Network::Request> network_request) {
26 DCHECK_CURRENTLY_ON(BrowserThread::UI);
27 if (!network_handler)
28 return;
29 network_handler->frontend()->RequestIntercepted(interception_id,
30 std::move(network_request));
31 }
32
33 void SendRedirectInterceptedEventOnUiThread(
34 base::WeakPtr<protocol::NetworkHandler> network_handler,
35 std::string interception_id,
36 std::unique_ptr<protocol::Network::Request> network_request,
37 std::unique_ptr<protocol::Object> headers_object,
38 int http_status_code,
39 std::string redirect_url) {
40 DCHECK_CURRENTLY_ON(BrowserThread::UI);
41 if (!network_handler)
42 return;
43 return network_handler->frontend()->RequestIntercepted(
44 interception_id, std::move(network_request), std::move(headers_object),
45 http_status_code, redirect_url);
46 }
47
48 class ProxyUploadElementReader : public net::UploadElementReader {
49 public:
50 explicit ProxyUploadElementReader(net::UploadElementReader* reader)
51 : reader_(reader) {}
52
53 ~ProxyUploadElementReader() override {}
54
55 // net::UploadElementReader overrides:
56 int Init(const net::CompletionCallback& callback) override {
57 return reader_->Init(callback);
58 }
59
60 uint64_t GetContentLength() const override {
61 return reader_->GetContentLength();
62 }
63
64 uint64_t BytesRemaining() const override { return reader_->BytesRemaining(); }
65
66 bool IsInMemory() const override { return reader_->IsInMemory(); }
67
68 int Read(net::IOBuffer* buf,
69 int buf_length,
70 const net::CompletionCallback& callback) override {
71 return reader_->Read(buf, buf_length, callback);
72 }
73
74 private:
75 net::UploadElementReader* reader_; // NOT OWNED
76
77 DISALLOW_COPY_AND_ASSIGN(ProxyUploadElementReader);
78 };
79
80 std::unique_ptr<net::UploadDataStream> GetUploadData(net::URLRequest* request) {
81 if (!request->has_upload())
82 return nullptr;
83
84 const net::UploadDataStream* stream = request->get_upload();
85 auto* readers = stream->GetElementReaders();
86 if (!readers || readers->empty())
87 return nullptr;
88
89 std::vector<std::unique_ptr<net::UploadElementReader>> proxy_readers;
90 proxy_readers.reserve(readers->size());
91 for (auto& reader : *readers) {
92 proxy_readers.push_back(
93 base::MakeUnique<ProxyUploadElementReader>(reader.get()));
94 }
95
96 return base::MakeUnique<net::ElementsUploadDataStream>(
97 std::move(proxy_readers), 0);
98 }
99 } // namespace
100
101 DevToolsURLInterceptorRequestJob::DevToolsURLInterceptorRequestJob(
102 scoped_refptr<DevToolsURLRequestInterceptor::State>
103 devtools_url_request_interceptor_state,
104 const std::string& interception_id,
105 net::URLRequest* original_request,
106 net::NetworkDelegate* original_network_delegate,
107 WebContents* web_contents,
108 base::WeakPtr<protocol::NetworkHandler> network_handler,
109 bool is_redirect)
110 : net::URLRequestJob(original_request, original_network_delegate),
111 devtools_url_request_interceptor_state_(
112 devtools_url_request_interceptor_state),
113 request_details_(original_request->url(),
114 original_request->method(),
115 GetUploadData(original_request),
116 original_request->extra_request_headers(),
117 original_request->priority(),
118 original_request->context()),
119 waiting_for_user_response_(false),
120 intercepting_requests_(true),
121 killed_(false),
122 interception_id_(interception_id),
123 web_contents_(web_contents),
124 network_handler_(network_handler),
125 is_redirect_(is_redirect),
126 weak_ptr_factory_(this) {
127 DCHECK_CURRENTLY_ON(BrowserThread::IO);
128 }
129
130 DevToolsURLInterceptorRequestJob::~DevToolsURLInterceptorRequestJob() {
131 DCHECK_CURRENTLY_ON(BrowserThread::IO);
132 devtools_url_request_interceptor_state_->JobFinished(interception_id_);
133 }
134
135 // net::URLRequestJob implementation:
136 void DevToolsURLInterceptorRequestJob::SetExtraRequestHeaders(
137 const net::HttpRequestHeaders& headers) {
138 request_details_.extra_request_headers = headers;
139 }
140
141 void DevToolsURLInterceptorRequestJob::Start() {
142 DCHECK_CURRENTLY_ON(BrowserThread::IO);
143 if (is_redirect_ || !intercepting_requests_) {
144 // If this is a fetch in response to a redirect, we have already sent the
145 // Network.requestIntercepted event and the user opted to allow it so
146 // there's no need to send another. We can just start the SubRequest.
147 // If we're not intercepting results we can also just start the SubRequest.
148 sub_request_.reset(new SubRequest(request_details_, this,
149 devtools_url_request_interceptor_state_));
150 } else {
151 waiting_for_user_response_ = true;
152 std::unique_ptr<protocol::Network::Request> network_request =
153 protocol::NetworkHandler::CreateRequestFromURLRequest(request());
154 BrowserThread::PostTask(
155 BrowserThread::UI, FROM_HERE,
156 base::Bind(SendRequestInterceptedEventOnUiThread, network_handler_,
157 interception_id_, base::Passed(&network_request)));
158 }
159 }
160
161 void DevToolsURLInterceptorRequestJob::Kill() {
162 killed_ = true;
163 if (sub_request_)
164 sub_request_->Cancel();
165
166 URLRequestJob::Kill();
167 }
168
169 int DevToolsURLInterceptorRequestJob::ReadRawData(net::IOBuffer* buf,
170 int buf_size) {
171 if (sub_request_) {
172 int size = sub_request_->request()->Read(buf, buf_size);
173 return size;
174 } else {
175 CHECK(mock_response_details_);
176 return mock_response_details_->ReadRawData(buf, buf_size);
177 }
178 }
179
180 int DevToolsURLInterceptorRequestJob::GetResponseCode() const {
181 if (sub_request_) {
182 return sub_request_->request()->GetResponseCode();
183 } else {
184 CHECK(mock_response_details_);
185 return mock_response_details_->response_headers()->response_code();
186 }
187 }
188
189 void DevToolsURLInterceptorRequestJob::GetResponseInfo(
190 net::HttpResponseInfo* info) {
191 // NOTE this can get called during URLRequestJob::NotifyStartError in which
192 // case we might not have either a sub request or a mock response.
193 if (sub_request_) {
194 *info = sub_request_->request()->response_info();
195 } else if (mock_response_details_) {
196 info->headers = mock_response_details_->response_headers();
197 }
198 }
199
200 const net::HttpResponseHeaders*
201 DevToolsURLInterceptorRequestJob::GetHttpResponseHeaders() const {
202 if (sub_request_) {
203 net::URLRequest* request = sub_request_->request();
204 return request->response_info().headers.get();
205 }
206 CHECK(mock_response_details_);
207 return mock_response_details_->response_headers().get();
208 }
209
210 bool DevToolsURLInterceptorRequestJob::GetMimeType(
211 std::string* mime_type) const {
212 const net::HttpResponseHeaders* response_headers = GetHttpResponseHeaders();
213 if (!response_headers)
214 return false;
215 return response_headers->GetMimeType(mime_type);
216 }
217
218 bool DevToolsURLInterceptorRequestJob::GetCharset(std::string* charset) {
219 const net::HttpResponseHeaders* response_headers = GetHttpResponseHeaders();
220 if (!response_headers)
221 return false;
222 return response_headers->GetCharset(charset);
223 }
224
225 void DevToolsURLInterceptorRequestJob::GetLoadTimingInfo(
226 net::LoadTimingInfo* load_timing_info) const {
227 if (sub_request_) {
228 sub_request_->request()->GetLoadTimingInfo(load_timing_info);
229 } else {
230 CHECK(mock_response_details_);
231 // Since this request is mocked most of the fields are irrelevant.
232 load_timing_info->receive_headers_end =
233 mock_response_details_->response_time();
234 }
235 }
236
237 bool DevToolsURLInterceptorRequestJob::NeedsAuth() {
238 return !!auth_info_;
239 }
240
241 void DevToolsURLInterceptorRequestJob::GetAuthChallengeInfo(
242 scoped_refptr<net::AuthChallengeInfo>* auth_info) {
243 *auth_info = auth_info_.get();
244 }
245
246 void DevToolsURLInterceptorRequestJob::SetAuth(
247 const net::AuthCredentials& credentials) {
248 sub_request_->request()->SetAuth(credentials);
249 auth_info_ = nullptr;
250 }
251
252 void DevToolsURLInterceptorRequestJob::CancelAuth() {
253 sub_request_->request()->CancelAuth();
254 auth_info_ = nullptr;
255 }
256
257 void DevToolsURLInterceptorRequestJob::OnAuthRequired(
258 net::URLRequest* request,
259 net::AuthChallengeInfo* auth_info) {
260 DCHECK(sub_request_);
261 DCHECK_EQ(request, sub_request_->request());
262 auth_info_ = auth_info;
263
264 // This notification came from the sub requests URLRequest::Delegate and we
265 // must proxy it the origional URLRequest::Delegate. We can't do that
266 // directly but by implementing NeedsAuth and calling NotifyHeadersComplete
267 // triggers it. To close the loop we also need to implement
268 // GetAuthChallengeInfo, SetAuth and CancelAuth.
269 NotifyHeadersComplete();
270 }
271
272 void DevToolsURLInterceptorRequestJob::OnCertificateRequested(
273 net::URLRequest* request,
274 net::SSLCertRequestInfo* cert_request_info) {
275 DCHECK(sub_request_);
276 DCHECK_EQ(request, sub_request_->request());
277 NotifyCertificateRequested(cert_request_info);
278 }
279
280 void DevToolsURLInterceptorRequestJob::OnSSLCertificateError(
281 net::URLRequest* request,
282 const net::SSLInfo& ssl_info,
283 bool fatal) {
284 DCHECK(sub_request_);
285 DCHECK_EQ(request, sub_request_->request());
286 NotifySSLCertificateError(ssl_info, fatal);
287 }
288
289 void DevToolsURLInterceptorRequestJob::OnResponseStarted(
290 net::URLRequest* request,
291 int net_error) {
292 DCHECK_CURRENTLY_ON(BrowserThread::IO);
293 DCHECK(sub_request_);
294 DCHECK_EQ(request, sub_request_->request());
295 DCHECK_NE(net::ERR_IO_PENDING, net_error);
296
297 if (net_error != net::OK) {
298 sub_request_->Cancel();
299
300 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
301 static_cast<net::Error>(net_error)));
302 return;
303 }
304
305 NotifyHeadersComplete();
306 }
307
308 void DevToolsURLInterceptorRequestJob::OnReadCompleted(net::URLRequest* request,
309 int num_bytes) {
310 DCHECK_CURRENTLY_ON(BrowserThread::IO);
311 DCHECK(sub_request_);
312 DCHECK_EQ(request, sub_request_->request());
313
314 // OnReadCompleted may get called while canceling the subrequest, in that
315 // event theres no need to call ReadRawDataComplete.
316 if (!killed_)
317 ReadRawDataComplete(num_bytes);
318 }
319
320 void DevToolsURLInterceptorRequestJob::OnReceivedRedirect(
321 net::URLRequest* request,
322 const net::RedirectInfo& redirectinfo,
323 bool* defer_redirect) {
324 DCHECK_CURRENTLY_ON(BrowserThread::IO);
325 DCHECK(sub_request_);
326 DCHECK_EQ(request, sub_request_->request());
327 // If we're not intercepting results then just exit.
328 if (!intercepting_requests_) {
329 *defer_redirect = false;
330 return;
331 }
332
333 // Otherwise we will need to ask what to do via DevTools protocol.
334 *defer_redirect = true;
335
336 size_t iter = 0;
337 std::string header_name;
338 std::string header_value;
339 std::unique_ptr<protocol::DictionaryValue> headers_dict(
340 protocol::DictionaryValue::create());
341 while (request->response_headers()->EnumerateHeaderLines(&iter, &header_name,
342 &header_value)) {
343 headers_dict->setString(header_name, header_value);
344 }
345
346 redirect_.reset(new net::RedirectInfo(redirectinfo));
347 sub_request_->Cancel();
348 sub_request_.reset();
349
350 waiting_for_user_response_ = true;
351
352 std::unique_ptr<protocol::Network::Request> network_request =
353 protocol::NetworkHandler::CreateRequestFromURLRequest(this->request());
354 std::unique_ptr<protocol::Object> headers_object =
355 protocol::Object::fromValue(headers_dict.get(), nullptr);
356 BrowserThread::PostTask(
357 BrowserThread::UI, FROM_HERE,
358 base::Bind(SendRedirectInterceptedEventOnUiThread, network_handler_,
359 interception_id_, base::Passed(&network_request),
360 base::Passed(&headers_object), redirectinfo.status_code,
361 redirectinfo.new_url.spec()));
362 }
363
364 void DevToolsURLInterceptorRequestJob::StopIntercepting() {
365 DCHECK_CURRENTLY_ON(BrowserThread::IO);
366 intercepting_requests_ = false;
367
368 // Allow the request to continue if we're waiting for user input.
369 ContinueInterceptedRequest(
370 base::MakeUnique<DevToolsURLRequestInterceptor::Modifications>(
371 base::nullopt, base::nullopt, protocol::Maybe<std::string>(),
372 protocol::Maybe<std::string>(), protocol::Maybe<std::string>(),
373 protocol::Maybe<protocol::Network::Headers>()));
374 }
375
376 bool DevToolsURLInterceptorRequestJob::ContinueInterceptedRequest(
377 std::unique_ptr<DevToolsURLRequestInterceptor::Modifications>
378 modifications) {
379 DCHECK_CURRENTLY_ON(BrowserThread::IO);
380 if (!waiting_for_user_response_)
381 return false;
382 waiting_for_user_response_ = false;
383
384 if (modifications->error_reason) {
385 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
386 *modifications->error_reason));
387 return true;
388 }
389
390 if (modifications->raw_response) {
391 mock_response_details_.reset(new MockResponseDetails(
392 std::move(*modifications->raw_response), base::TimeTicks::Now()));
393
394 std::string value;
395 if (mock_response_details_->response_headers()->IsRedirect(&value)) {
396 devtools_url_request_interceptor_state_->ExpectRequestAfterRedirect(
397 request(), interception_id_);
398 }
399 NotifyHeadersComplete();
400 return true;
401 }
402
403 if (redirect_) {
404 // NOTE we don't append the text form of the status code because
405 // net::HttpResponseHeaders doesn't need that.
406 std::string raw_headers =
407 base::StringPrintf("HTTP/1.1 %d", redirect_->status_code);
408 raw_headers.append(1, '\0');
409 raw_headers.append("Location: ");
410 raw_headers.append(
411 modifications->modified_url.fromMaybe(redirect_->new_url.spec()));
412 raw_headers.append(2, '\0');
413 mock_response_details_.reset(new MockResponseDetails(
414 make_scoped_refptr(new net::HttpResponseHeaders(raw_headers)), "", 0,
415 base::TimeTicks::Now()));
416 redirect_.reset();
417
418 devtools_url_request_interceptor_state_->ExpectRequestAfterRedirect(
419 request(), interception_id_);
420 NotifyHeadersComplete();
421 } else {
422 // Note this redirect is not visible to the caller by design. If they want a
423 // visible redirect they can mock a response with a 302.
424 if (modifications->modified_url.isJust())
425 request_details_.url = GURL(modifications->modified_url.fromJust());
426
427 if (modifications->modified_method.isJust())
428 request_details_.method = modifications->modified_method.fromJust();
429
430 if (modifications->modified_post_data.isJust()) {
431 const std::string& post_data =
432 modifications->modified_post_data.fromJust();
433 std::vector<char> data(post_data.begin(), post_data.end());
434 request_details_.post_data =
435 net::ElementsUploadDataStream::CreateWithReader(
436 base::MakeUnique<net::UploadOwnedBytesElementReader>(&data), 0);
437 }
438
439 if (modifications->modified_headers.isJust()) {
440 request_details_.extra_request_headers.Clear();
441 std::unique_ptr<protocol::DictionaryValue> headers =
442 modifications->modified_headers.fromJust()->toValue();
443 for (size_t i = 0; i < headers->size(); i++) {
444 std::string value;
445 if (headers->at(i).second->asString(&value)) {
446 request_details_.extra_request_headers.SetHeader(headers->at(i).first,
447 value);
448 }
449 }
450 }
451
452 // The reason we start a sub request is because we are in full control of it
453 // and can choose to ignore it if, for example, the fetch encounters a
454 // redirect that the user chooses to replace with a mock response.
455 sub_request_.reset(new SubRequest(request_details_, this,
456 devtools_url_request_interceptor_state_));
457 }
458 return true;
459 }
460
461 DevToolsURLInterceptorRequestJob::RequestDetails::RequestDetails(
462 const GURL& url,
463 const std::string& method,
464 std::unique_ptr<net::UploadDataStream> post_data,
465 const net::HttpRequestHeaders& extra_request_headers,
466 const net::RequestPriority& priority,
467 const net::URLRequestContext* url_request_context)
468 : url(url),
469 method(method),
470 post_data(std::move(post_data)),
471 extra_request_headers(extra_request_headers),
472 priority(priority),
473 url_request_context(url_request_context) {}
474
475 DevToolsURLInterceptorRequestJob::RequestDetails::~RequestDetails() {}
476
477 DevToolsURLInterceptorRequestJob::SubRequest::SubRequest(
478 DevToolsURLInterceptorRequestJob::RequestDetails& request_details,
479 DevToolsURLInterceptorRequestJob* devtools_interceptor_request_job,
480 scoped_refptr<DevToolsURLRequestInterceptor::State>
481 devtools_url_request_interceptor_state)
482 : devtools_interceptor_request_job_(devtools_interceptor_request_job),
483 devtools_url_request_interceptor_state_(
484 devtools_url_request_interceptor_state),
485 fetch_in_progress_(true) {
486 DCHECK_CURRENTLY_ON(BrowserThread::IO);
487 request_ = request_details.url_request_context->CreateRequest(
488 request_details.url, request_details.priority,
489 devtools_interceptor_request_job_),
490 request_->set_method(request_details.method);
491 request_->SetExtraRequestHeaders(request_details.extra_request_headers);
492
493 if (request_details.post_data)
494 request_->set_upload(std::move(request_details.post_data));
495
496 devtools_url_request_interceptor_state_->RegisterSubRequest(request_.get());
497 request_->Start();
498 }
499
500 DevToolsURLInterceptorRequestJob::SubRequest::~SubRequest() {
501 DCHECK_CURRENTLY_ON(BrowserThread::IO);
502 devtools_url_request_interceptor_state_->UnregisterSubRequest(request_.get());
503 }
504
505 void DevToolsURLInterceptorRequestJob::SubRequest::Cancel() {
506 DCHECK_CURRENTLY_ON(BrowserThread::IO);
507 if (!fetch_in_progress_)
508 return;
509
510 fetch_in_progress_ = false;
511 request_->Cancel();
512 }
513
514 DevToolsURLInterceptorRequestJob::MockResponseDetails::MockResponseDetails(
515 std::string response_bytes,
516 base::TimeTicks response_time)
517 : response_bytes_(std::move(response_bytes)),
518 read_offset_(0),
519 response_time_(response_time) {
520 int header_size = net::HttpUtil::LocateEndOfHeaders(response_bytes_.c_str(),
521 response_bytes_.size());
522 if (header_size == -1) {
523 LOG(WARNING) << "Can't find headers in result";
524 response_headers_ = new net::HttpResponseHeaders("");
525 } else {
526 response_headers_ =
527 new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
528 response_bytes_.c_str(), header_size));
529 read_offset_ = header_size;
530 }
531
532 CHECK_LE(read_offset_, response_bytes_.size());
533 }
534
535 DevToolsURLInterceptorRequestJob::MockResponseDetails::MockResponseDetails(
536 const scoped_refptr<net::HttpResponseHeaders>& response_headers,
537 std::string response_bytes,
538 size_t read_offset,
539 base::TimeTicks response_time)
540 : response_headers_(response_headers),
541 response_bytes_(std::move(response_bytes)),
542 read_offset_(read_offset),
543 response_time_(response_time) {}
544
545 DevToolsURLInterceptorRequestJob::MockResponseDetails::~MockResponseDetails() {}
546
547 int DevToolsURLInterceptorRequestJob::MockResponseDetails::ReadRawData(
548 net::IOBuffer* buf,
549 int buf_size) {
550 size_t bytes_available = response_bytes_.size() - read_offset_;
551 size_t bytes_to_copy =
552 std::min(static_cast<size_t>(buf_size), bytes_available);
553 if (bytes_to_copy > 0) {
554 std::memcpy(buf->data(), &response_bytes_.data()[read_offset_],
555 bytes_to_copy);
556 read_offset_ += bytes_to_copy;
557 }
558 return bytes_to_copy;
559 }
560
561 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698