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

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: Dont assume there's only one element reader 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 interception_id_(interception_id),
122 web_contents_(web_contents),
123 network_handler_(network_handler),
124 is_redirect_(is_redirect),
125 weak_ptr_factory_(this) {
126 DCHECK_CURRENTLY_ON(BrowserThread::IO);
127 }
128
129 DevToolsURLInterceptorRequestJob::~DevToolsURLInterceptorRequestJob() {
130 DCHECK_CURRENTLY_ON(BrowserThread::IO);
131 devtools_url_request_interceptor_state_->JobFinished(interception_id_);
132 }
133
134 // net::URLRequestJob implementation:
135 void DevToolsURLInterceptorRequestJob::SetExtraRequestHeaders(
136 const net::HttpRequestHeaders& headers) {
137 request_details_.extra_request_headers = headers;
138 }
139
140 void DevToolsURLInterceptorRequestJob::Start() {
141 DCHECK_CURRENTLY_ON(BrowserThread::IO);
142 if (is_redirect_ || !intercepting_requests_) {
143 // If this is a fetch in response to a redirect, we have already sent the
144 // Network.requestIntercepted event and the user opted to allow it so
145 // there's no need to send another. We can just start the SubRequest.
146 // If we're not intercepting results we can also just start the SubRequest.
147 sub_request_.reset(new SubRequest(request_details_, this,
148 devtools_url_request_interceptor_state_));
149 } else {
150 waiting_for_user_response_ = true;
151 std::unique_ptr<protocol::Network::Request> network_request =
152 protocol::NetworkHandler::CreateRequestFromURLRequest(request());
153 BrowserThread::PostTask(
154 BrowserThread::UI, FROM_HERE,
155 base::Bind(SendRequestInterceptedEventOnUiThread, network_handler_,
156 interception_id_, base::Passed(&network_request)));
157 }
158 }
159
160 void DevToolsURLInterceptorRequestJob::Kill() {
161 if (sub_request_)
162 sub_request_->Cancel();
163
164 URLRequestJob::Kill();
165 }
166
167 int DevToolsURLInterceptorRequestJob::ReadRawData(net::IOBuffer* buf,
168 int buf_size) {
169 if (sub_request_) {
170 int size = sub_request_->request()->Read(buf, buf_size);
171 return size;
172 } else {
173 CHECK(mock_response_details_);
174 return mock_response_details_->ReadRawData(buf, buf_size);
175 }
176 }
177
178 int DevToolsURLInterceptorRequestJob::GetResponseCode() const {
179 if (sub_request_) {
180 return sub_request_->request()->GetResponseCode();
181 } else {
182 CHECK(mock_response_details_);
183 return mock_response_details_->response_headers()->response_code();
184 }
185 }
186
187 void DevToolsURLInterceptorRequestJob::GetResponseInfo(
188 net::HttpResponseInfo* info) {
189 // NOTE this can get called during URLRequestJob::NotifyStartError in which
190 // case we might not have either a sub request or a mock response.
191 if (sub_request_) {
192 *info = sub_request_->request()->response_info();
193 } else if (mock_response_details_) {
194 info->headers = mock_response_details_->response_headers();
195 }
196 }
197
198 const net::HttpResponseHeaders*
199 DevToolsURLInterceptorRequestJob::GetHttpResponseHeaders() const {
200 if (sub_request_) {
201 net::URLRequest* request = sub_request_->request();
202 return request->response_info().headers.get();
203 }
204 CHECK(mock_response_details_);
205 return mock_response_details_->response_headers().get();
206 }
207
208 bool DevToolsURLInterceptorRequestJob::GetMimeType(
209 std::string* mime_type) const {
210 const net::HttpResponseHeaders* response_headers = GetHttpResponseHeaders();
211 if (!response_headers)
212 return false;
213 return response_headers->GetMimeType(mime_type);
214 }
215
216 bool DevToolsURLInterceptorRequestJob::GetCharset(std::string* charset) {
217 const net::HttpResponseHeaders* response_headers = GetHttpResponseHeaders();
218 if (!response_headers)
219 return false;
220 return response_headers->GetCharset(charset);
221 }
222
223 void DevToolsURLInterceptorRequestJob::GetLoadTimingInfo(
224 net::LoadTimingInfo* load_timing_info) const {
225 if (sub_request_) {
226 sub_request_->request()->GetLoadTimingInfo(load_timing_info);
227 } else {
228 CHECK(mock_response_details_);
229 // Since this request is mocked most of the fields are irrelevant.
230 load_timing_info->receive_headers_end =
231 mock_response_details_->response_time();
232 }
233 }
234
235 void DevToolsURLInterceptorRequestJob::OnAuthRequired(
236 net::URLRequest* request,
237 net::AuthChallengeInfo* auth_info) {
238 // This shouldn't get called because the actual network fetch is done by the
239 // SubRequest.
240 NOTREACHED();
241 }
242
243 void DevToolsURLInterceptorRequestJob::OnCertificateRequested(
244 net::URLRequest* request,
245 net::SSLCertRequestInfo* cert_request_info) {
246 // This shouldn't get called because the actual network fetch is done by the
247 // SubRequest.
248 NOTREACHED();
249 }
250
251 void DevToolsURLInterceptorRequestJob::OnSSLCertificateError(
252 net::URLRequest* request,
253 const net::SSLInfo& ssl_info,
254 bool fatal) {
255 // This shouldn't get called because the actual network fetch is done by the
256 // SubRequest.
257 NOTREACHED();
258 }
259
260 void DevToolsURLInterceptorRequestJob::OnResponseStarted(
261 net::URLRequest* request,
262 int net_error) {
263 DCHECK_CURRENTLY_ON(BrowserThread::IO);
264 DCHECK(sub_request_);
265 DCHECK_EQ(request, sub_request_->request());
266 DCHECK_NE(net::ERR_IO_PENDING, net_error);
267
268 if (net_error != net::OK) {
269 sub_request_->Cancel();
270
271 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
272 static_cast<net::Error>(net_error)));
273 return;
274 }
275
276 NotifyHeadersComplete();
277 }
278
279 void DevToolsURLInterceptorRequestJob::OnReadCompleted(net::URLRequest* request,
280 int num_bytes) {
281 DCHECK_CURRENTLY_ON(BrowserThread::IO);
282 DCHECK_EQ(request, sub_request_->request());
283
284 ReadRawDataComplete(num_bytes);
285 }
286
287 void DevToolsURLInterceptorRequestJob::OnReceivedRedirect(
288 net::URLRequest* request,
289 const net::RedirectInfo& redirectinfo,
290 bool* defer_redirect) {
291 DCHECK_CURRENTLY_ON(BrowserThread::IO);
292 // If we're not intercepting results then just exit.
293 if (!intercepting_requests_) {
294 *defer_redirect = false;
295 return;
296 }
297
298 // Otherwise we will need to ask what to do via DevTools protocol.
299 *defer_redirect = true;
300
301 size_t iter = 0;
302 std::string header_name;
303 std::string header_value;
304 std::unique_ptr<protocol::DictionaryValue> headers_dict(
305 protocol::DictionaryValue::create());
306 while (request->response_headers()->EnumerateHeaderLines(&iter, &header_name,
307 &header_value)) {
308 headers_dict->setString(header_name, header_value);
309 }
310
311 redirect_.reset(new net::RedirectInfo(redirectinfo));
312 sub_request_->Cancel();
313 sub_request_.reset();
314
315 waiting_for_user_response_ = true;
316
317 std::unique_ptr<protocol::Network::Request> network_request =
318 protocol::NetworkHandler::CreateRequestFromURLRequest(
319 DevToolsURLInterceptorRequestJob::request());
320 std::unique_ptr<protocol::Object> headers_object =
321 protocol::Object::fromValue(headers_dict.get(), nullptr);
322 BrowserThread::PostTask(
323 BrowserThread::UI, FROM_HERE,
324 base::Bind(SendRedirectInterceptedEventOnUiThread, network_handler_,
325 interception_id_, base::Passed(&network_request),
326 base::Passed(&headers_object), redirectinfo.status_code,
327 redirectinfo.new_url.spec()));
328 }
329
330 void DevToolsURLInterceptorRequestJob::StopIntercepting() {
331 DCHECK_CURRENTLY_ON(BrowserThread::IO);
332 if (!waiting_for_user_response_)
333 return;
334
335 waiting_for_user_response_ = false;
336 intercepting_requests_ = false;
337 sub_request_.reset(new SubRequest(request_details_, this,
338 devtools_url_request_interceptor_state_));
339 }
340
341 bool DevToolsURLInterceptorRequestJob::ContinueInterceptedRequest(
342 std::unique_ptr<DevToolsURLRequestInterceptor::Modifications>
343 modifications) {
344 DCHECK_CURRENTLY_ON(BrowserThread::IO);
345 if (!waiting_for_user_response_)
346 return false;
347 waiting_for_user_response_ = false;
348
349 if (modifications->error_reason) {
350 NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED,
351 *modifications->error_reason));
352 return true;
353 }
354
355 if (modifications->raw_response) {
356 mock_response_details_.reset(new MockResponseDetails(
357 std::move(*modifications->raw_response), base::TimeTicks::Now()));
358
359 std::string value;
360 if (mock_response_details_->response_headers()->IsRedirect(&value)) {
361 devtools_url_request_interceptor_state_->ExpectRequestAfterRedirect(
362 request(), interception_id_);
363 }
364 NotifyHeadersComplete();
365 return true;
366 }
367
368 if (redirect_) {
369 // NOTE we don't append the text form of the status code because
370 // net::HttpResponseHeaders doesn't need that.
371 std::string raw_headers =
372 base::StringPrintf("HTTP/1.1 %d", redirect_->status_code);
373 raw_headers.append(1, '\0');
374 raw_headers.append("Location: ");
375 raw_headers.append(
376 modifications->modified_url.fromMaybe(redirect_->new_url.spec()));
377 raw_headers.append(2, '\0');
378 mock_response_details_.reset(new MockResponseDetails(
379 make_scoped_refptr(new net::HttpResponseHeaders(raw_headers)), "", 0,
380 base::TimeTicks::Now()));
381 redirect_.reset();
382
383 devtools_url_request_interceptor_state_->ExpectRequestAfterRedirect(
384 request(), interception_id_);
385 NotifyHeadersComplete();
386 } else {
387 // Note this redirect is not visible to the caller by design. If they want a
388 // visible redirect they can mock a response with a 302.
389 if (modifications->modified_url.isJust())
390 request_details_.url = GURL(modifications->modified_url.fromJust());
391
392 if (modifications->modified_method.isJust())
393 request_details_.method = modifications->modified_method.fromJust();
394
395 if (modifications->modified_post_data.isJust()) {
396 const std::string& post_data =
397 modifications->modified_post_data.fromJust();
398 std::vector<char> data(post_data.begin(), post_data.end());
399 request_details_.post_data =
400 net::ElementsUploadDataStream::CreateWithReader(
401 base::MakeUnique<net::UploadOwnedBytesElementReader>(&data), 0);
402 }
403
404 if (modifications->modified_headers.isJust()) {
405 request_details_.extra_request_headers.Clear();
406 std::unique_ptr<protocol::DictionaryValue> headers =
407 modifications->modified_headers.fromJust()->toValue();
408 for (size_t i = 0; i < headers->size(); i++) {
409 std::string value;
410 if (headers->at(i).second->asString(&value)) {
411 request_details_.extra_request_headers.SetHeader(headers->at(i).first,
412 value);
413 }
414 }
415 }
416
417 // The reason we start a sub request is because we are in full control of it
418 // and can choose to ignore it if, for example, the fetch encounters a
419 // redirect that the user chooses to replace with a mock response.
420 sub_request_.reset(new SubRequest(request_details_, this,
421 devtools_url_request_interceptor_state_));
422 }
423 return true;
424 }
425
426 DevToolsURLInterceptorRequestJob::RequestDetails::RequestDetails(
427 const GURL& url,
428 const std::string& method,
429 std::unique_ptr<net::UploadDataStream> post_data,
430 const net::HttpRequestHeaders& extra_request_headers,
431 const net::RequestPriority& priority,
432 const net::URLRequestContext* url_request_context)
433 : url(url),
434 method(method),
435 post_data(std::move(post_data)),
436 extra_request_headers(extra_request_headers),
437 priority(priority),
438 url_request_context(url_request_context) {}
439
440 DevToolsURLInterceptorRequestJob::RequestDetails::~RequestDetails() {}
441
442 DevToolsURLInterceptorRequestJob::SubRequest::SubRequest(
443 DevToolsURLInterceptorRequestJob::RequestDetails& request_details,
444 DevToolsURLInterceptorRequestJob* devtools_interceptor_request_job,
445 scoped_refptr<DevToolsURLRequestInterceptor::State>
446 devtools_url_request_interceptor_state)
447 : devtools_interceptor_request_job_(devtools_interceptor_request_job),
448 devtools_url_request_interceptor_state_(
449 devtools_url_request_interceptor_state),
450 fetch_in_progress_(true) {
451 DCHECK_CURRENTLY_ON(BrowserThread::IO);
452 request_ = request_details.url_request_context->CreateRequest(
453 request_details.url, request_details.priority,
454 devtools_interceptor_request_job_),
455 request_->set_method(request_details.method);
456 request_->SetExtraRequestHeaders(request_details.extra_request_headers);
457
458 if (request_details.post_data)
459 request_->set_upload(std::move(request_details.post_data));
460
461 devtools_url_request_interceptor_state_->RegisterSubRequest(request_.get());
462 request_->Start();
463 }
464
465 DevToolsURLInterceptorRequestJob::SubRequest::~SubRequest() {
466 DCHECK_CURRENTLY_ON(BrowserThread::IO);
467 devtools_url_request_interceptor_state_->UnregisterSubRequest(request_.get());
468 }
469
470 void DevToolsURLInterceptorRequestJob::SubRequest::Cancel() {
471 DCHECK_CURRENTLY_ON(BrowserThread::IO);
472 if (!fetch_in_progress_)
473 return;
474
475 fetch_in_progress_ = false;
476 request_->Cancel();
477 }
478
479 DevToolsURLInterceptorRequestJob::MockResponseDetails::MockResponseDetails(
480 std::string response_bytes,
481 base::TimeTicks response_time)
482 : response_bytes_(std::move(response_bytes)),
483 read_offset_(0),
484 response_time_(response_time) {
485 int header_size = net::HttpUtil::LocateEndOfHeaders(response_bytes_.c_str(),
486 response_bytes_.size());
487 if (header_size == -1) {
488 LOG(WARNING) << "Can't find headers in result";
489 response_headers_ = new net::HttpResponseHeaders("");
490 } else {
491 response_headers_ =
492 new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
493 response_bytes_.c_str(), header_size));
494 read_offset_ = header_size;
495 }
496
497 CHECK_LE(read_offset_, response_bytes_.size());
498 }
499
500 DevToolsURLInterceptorRequestJob::MockResponseDetails::MockResponseDetails(
501 const scoped_refptr<net::HttpResponseHeaders>& response_headers,
502 std::string response_bytes,
503 size_t read_offset,
504 base::TimeTicks response_time)
505 : response_headers_(response_headers),
506 response_bytes_(std::move(response_bytes)),
507 read_offset_(read_offset),
508 response_time_(response_time) {}
509
510 DevToolsURLInterceptorRequestJob::MockResponseDetails::~MockResponseDetails() {}
511
512 int DevToolsURLInterceptorRequestJob::MockResponseDetails::ReadRawData(
513 net::IOBuffer* buf,
514 int buf_size) {
515 size_t bytes_available = response_bytes_.size() - read_offset_;
516 size_t bytes_to_copy =
517 std::min(static_cast<size_t>(buf_size), bytes_available);
518 if (bytes_to_copy > 0) {
519 std::memcpy(buf->data(), &response_bytes_.data()[read_offset_],
520 bytes_to_copy);
521 read_offset_ += bytes_to_copy;
522 }
523 return bytes_to_copy;
524 }
525
526 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698