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

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

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

Powered by Google App Engine
This is Rietveld 408576698