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

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: Fix bugs with auth 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 NotifyHeadersComplete();
Sami 2017/06/02 09:59:47 Let's a add a short comment explaining the flow he
alex clarke (OOO till 29th) 2017/06/02 10:16:59 Done.
264 }
265
266 void DevToolsURLInterceptorRequestJob::OnCertificateRequested(
267 net::URLRequest* request,
268 net::SSLCertRequestInfo* cert_request_info) {
269 DCHECK(sub_request_);
270 DCHECK_EQ(request, sub_request_->request());
271 NotifyCertificateRequested(cert_request_info);
272 }
273
274 void DevToolsURLInterceptorRequestJob::OnSSLCertificateError(
275 net::URLRequest* request,
276 const net::SSLInfo& ssl_info,
277 bool fatal) {
278 DCHECK(sub_request_);
279 DCHECK_EQ(request, sub_request_->request());
280 NotifySSLCertificateError(ssl_info, fatal);
281 }
282
283 void DevToolsURLInterceptorRequestJob::OnResponseStarted(
284 net::URLRequest* request,
285 int net_error) {
286 DCHECK_CURRENTLY_ON(BrowserThread::IO);
287 DCHECK(sub_request_);
288 DCHECK_EQ(request, sub_request_->request());
289 DCHECK_NE(net::ERR_IO_PENDING, net_error);
290
291 if (net_error != net::OK) {
292 sub_request_->Cancel();
293
294 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
295 static_cast<net::Error>(net_error)));
296 return;
297 }
298
299 NotifyHeadersComplete();
300 }
301
302 void DevToolsURLInterceptorRequestJob::OnReadCompleted(net::URLRequest* request,
303 int num_bytes) {
304 DCHECK_CURRENTLY_ON(BrowserThread::IO);
305 DCHECK(sub_request_);
306 DCHECK_EQ(request, sub_request_->request());
307
308 // OnReadCompleted may get called while canceling the subrequest, in that
309 // event theres no need to call ReadRawDataComplete.
310 if (!killed_)
311 ReadRawDataComplete(num_bytes);
312 }
313
314 void DevToolsURLInterceptorRequestJob::OnReceivedRedirect(
315 net::URLRequest* request,
316 const net::RedirectInfo& redirectinfo,
317 bool* defer_redirect) {
318 DCHECK_CURRENTLY_ON(BrowserThread::IO);
319 DCHECK(sub_request_);
320 DCHECK_EQ(request, sub_request_->request());
321 // If we're not intercepting results then just exit.
322 if (!intercepting_requests_) {
323 *defer_redirect = false;
324 return;
325 }
326
327 // Otherwise we will need to ask what to do via DevTools protocol.
328 *defer_redirect = true;
329
330 size_t iter = 0;
331 std::string header_name;
332 std::string header_value;
333 std::unique_ptr<protocol::DictionaryValue> headers_dict(
334 protocol::DictionaryValue::create());
335 while (request->response_headers()->EnumerateHeaderLines(&iter, &header_name,
336 &header_value)) {
337 headers_dict->setString(header_name, header_value);
338 }
339
340 redirect_.reset(new net::RedirectInfo(redirectinfo));
341 sub_request_->Cancel();
342 sub_request_.reset();
343
344 waiting_for_user_response_ = true;
345
346 std::unique_ptr<protocol::Network::Request> network_request =
347 protocol::NetworkHandler::CreateRequestFromURLRequest(this->request());
348 std::unique_ptr<protocol::Object> headers_object =
349 protocol::Object::fromValue(headers_dict.get(), nullptr);
350 BrowserThread::PostTask(
351 BrowserThread::UI, FROM_HERE,
352 base::Bind(SendRedirectInterceptedEventOnUiThread, network_handler_,
353 interception_id_, base::Passed(&network_request),
354 base::Passed(&headers_object), redirectinfo.status_code,
355 redirectinfo.new_url.spec()));
356 }
357
358 void DevToolsURLInterceptorRequestJob::StopIntercepting() {
359 DCHECK_CURRENTLY_ON(BrowserThread::IO);
360 intercepting_requests_ = false;
361
362 // Allow the request to continue if we're waiting for user input.
363 ContinueInterceptedRequest(
364 base::MakeUnique<DevToolsURLRequestInterceptor::Modifications>(
365 base::nullopt, base::nullopt, protocol::Maybe<std::string>(),
366 protocol::Maybe<std::string>(), protocol::Maybe<std::string>(),
367 protocol::Maybe<protocol::Network::Headers>()));
368 }
369
370 bool DevToolsURLInterceptorRequestJob::ContinueInterceptedRequest(
371 std::unique_ptr<DevToolsURLRequestInterceptor::Modifications>
372 modifications) {
373 DCHECK_CURRENTLY_ON(BrowserThread::IO);
374 if (!waiting_for_user_response_)
375 return false;
376 waiting_for_user_response_ = false;
377
378 if (modifications->error_reason) {
379 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
380 *modifications->error_reason));
381 return true;
382 }
383
384 if (modifications->raw_response) {
385 mock_response_details_.reset(new MockResponseDetails(
386 std::move(*modifications->raw_response), base::TimeTicks::Now()));
387
388 std::string value;
389 if (mock_response_details_->response_headers()->IsRedirect(&value)) {
390 devtools_url_request_interceptor_state_->ExpectRequestAfterRedirect(
391 request(), interception_id_);
392 }
393 NotifyHeadersComplete();
394 return true;
395 }
396
397 if (redirect_) {
398 // NOTE we don't append the text form of the status code because
399 // net::HttpResponseHeaders doesn't need that.
400 std::string raw_headers =
401 base::StringPrintf("HTTP/1.1 %d", redirect_->status_code);
402 raw_headers.append(1, '\0');
403 raw_headers.append("Location: ");
404 raw_headers.append(
405 modifications->modified_url.fromMaybe(redirect_->new_url.spec()));
406 raw_headers.append(2, '\0');
407 mock_response_details_.reset(new MockResponseDetails(
408 make_scoped_refptr(new net::HttpResponseHeaders(raw_headers)), "", 0,
409 base::TimeTicks::Now()));
410 redirect_.reset();
411
412 devtools_url_request_interceptor_state_->ExpectRequestAfterRedirect(
413 request(), interception_id_);
414 NotifyHeadersComplete();
415 } else {
416 // Note this redirect is not visible to the caller by design. If they want a
417 // visible redirect they can mock a response with a 302.
418 if (modifications->modified_url.isJust())
419 request_details_.url = GURL(modifications->modified_url.fromJust());
420
421 if (modifications->modified_method.isJust())
422 request_details_.method = modifications->modified_method.fromJust();
423
424 if (modifications->modified_post_data.isJust()) {
425 const std::string& post_data =
426 modifications->modified_post_data.fromJust();
427 std::vector<char> data(post_data.begin(), post_data.end());
428 request_details_.post_data =
429 net::ElementsUploadDataStream::CreateWithReader(
430 base::MakeUnique<net::UploadOwnedBytesElementReader>(&data), 0);
431 }
432
433 if (modifications->modified_headers.isJust()) {
434 request_details_.extra_request_headers.Clear();
435 std::unique_ptr<protocol::DictionaryValue> headers =
436 modifications->modified_headers.fromJust()->toValue();
437 for (size_t i = 0; i < headers->size(); i++) {
438 std::string value;
439 if (headers->at(i).second->asString(&value)) {
440 request_details_.extra_request_headers.SetHeader(headers->at(i).first,
441 value);
442 }
443 }
444 }
445
446 // The reason we start a sub request is because we are in full control of it
447 // and can choose to ignore it if, for example, the fetch encounters a
448 // redirect that the user chooses to replace with a mock response.
449 sub_request_.reset(new SubRequest(request_details_, this,
450 devtools_url_request_interceptor_state_));
451 }
452 return true;
453 }
454
455 DevToolsURLInterceptorRequestJob::RequestDetails::RequestDetails(
456 const GURL& url,
457 const std::string& method,
458 std::unique_ptr<net::UploadDataStream> post_data,
459 const net::HttpRequestHeaders& extra_request_headers,
460 const net::RequestPriority& priority,
461 const net::URLRequestContext* url_request_context)
462 : url(url),
463 method(method),
464 post_data(std::move(post_data)),
465 extra_request_headers(extra_request_headers),
466 priority(priority),
467 url_request_context(url_request_context) {}
468
469 DevToolsURLInterceptorRequestJob::RequestDetails::~RequestDetails() {}
470
471 DevToolsURLInterceptorRequestJob::SubRequest::SubRequest(
472 DevToolsURLInterceptorRequestJob::RequestDetails& request_details,
473 DevToolsURLInterceptorRequestJob* devtools_interceptor_request_job,
474 scoped_refptr<DevToolsURLRequestInterceptor::State>
475 devtools_url_request_interceptor_state)
476 : devtools_interceptor_request_job_(devtools_interceptor_request_job),
477 devtools_url_request_interceptor_state_(
478 devtools_url_request_interceptor_state),
479 fetch_in_progress_(true) {
480 DCHECK_CURRENTLY_ON(BrowserThread::IO);
481 request_ = request_details.url_request_context->CreateRequest(
482 request_details.url, request_details.priority,
483 devtools_interceptor_request_job_),
484 request_->set_method(request_details.method);
485 request_->SetExtraRequestHeaders(request_details.extra_request_headers);
486
487 if (request_details.post_data)
488 request_->set_upload(std::move(request_details.post_data));
489
490 devtools_url_request_interceptor_state_->RegisterSubRequest(request_.get());
491 request_->Start();
492 }
493
494 DevToolsURLInterceptorRequestJob::SubRequest::~SubRequest() {
495 DCHECK_CURRENTLY_ON(BrowserThread::IO);
496 devtools_url_request_interceptor_state_->UnregisterSubRequest(request_.get());
497 }
498
499 void DevToolsURLInterceptorRequestJob::SubRequest::Cancel() {
500 DCHECK_CURRENTLY_ON(BrowserThread::IO);
501 if (!fetch_in_progress_)
502 return;
503
504 fetch_in_progress_ = false;
505 request_->Cancel();
506 }
507
508 DevToolsURLInterceptorRequestJob::MockResponseDetails::MockResponseDetails(
509 std::string response_bytes,
510 base::TimeTicks response_time)
511 : response_bytes_(std::move(response_bytes)),
512 read_offset_(0),
513 response_time_(response_time) {
514 int header_size = net::HttpUtil::LocateEndOfHeaders(response_bytes_.c_str(),
515 response_bytes_.size());
516 if (header_size == -1) {
517 LOG(WARNING) << "Can't find headers in result";
518 response_headers_ = new net::HttpResponseHeaders("");
519 } else {
520 response_headers_ =
521 new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
522 response_bytes_.c_str(), header_size));
523 read_offset_ = header_size;
524 }
525
526 CHECK_LE(read_offset_, response_bytes_.size());
527 }
528
529 DevToolsURLInterceptorRequestJob::MockResponseDetails::MockResponseDetails(
530 const scoped_refptr<net::HttpResponseHeaders>& response_headers,
531 std::string response_bytes,
532 size_t read_offset,
533 base::TimeTicks response_time)
534 : response_headers_(response_headers),
535 response_bytes_(std::move(response_bytes)),
536 read_offset_(read_offset),
537 response_time_(response_time) {}
538
539 DevToolsURLInterceptorRequestJob::MockResponseDetails::~MockResponseDetails() {}
540
541 int DevToolsURLInterceptorRequestJob::MockResponseDetails::ReadRawData(
542 net::IOBuffer* buf,
543 int buf_size) {
544 size_t bytes_available = response_bytes_.size() - read_offset_;
545 size_t bytes_to_copy =
546 std::min(static_cast<size_t>(buf_size), bytes_available);
547 if (bytes_to_copy > 0) {
548 std::memcpy(buf->data(), &response_bytes_.data()[read_offset_],
549 bytes_to_copy);
550 read_offset_ += bytes_to_copy;
551 }
552 return bytes_to_copy;
553 }
554
555 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698