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

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

Powered by Google App Engine
This is Rietveld 408576698