OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 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/public/test/test_download_request_handler.h" | |
6 | |
7 #include <inttypes.h> | |
8 | |
9 #include "base/logging.h" | |
10 #include "base/memory/weak_ptr.h" | |
11 #include "base/run_loop.h" | |
12 #include "base/strings/stringprintf.h" | |
13 #include "content/public/browser/browser_thread.h" | |
14 #include "net/base/io_buffer.h" | |
15 #include "net/http/http_request_headers.h" | |
16 #include "net/http/http_response_headers.h" | |
17 #include "net/url_request/url_request_filter.h" | |
18 #include "net/url_request/url_request_interceptor.h" | |
19 | |
20 namespace content { | |
21 | |
22 namespace { | |
23 | |
24 using Parameters = TestDownloadRequestHandler::Parameters; | |
25 using InjectedError = TestDownloadRequestHandler::InjectedError; | |
26 | |
27 // Intercepts URLRequests on behalf of TestDownloadRequestHandler. Necessarily | |
28 // lives on the IO thread since that's where net::URLRequestFilter invokes the | |
29 // URLRequestInterceptor. | |
30 class TestDownloadRequestInterceptor : public net::URLRequestInterceptor { | |
31 public: | |
32 // Invoked on the IO thread to register a URLRequestInterceptor for |url|. | |
33 // Returns an IO-thread bound weak pointer for interacting with the | |
34 // interceptor. | |
35 static base::WeakPtr<TestDownloadRequestInterceptor> Register( | |
36 const GURL& url); | |
37 | |
38 using JobFactory = base::Callback<net::URLRequestJob*( | |
39 net::URLRequest*, | |
40 net::NetworkDelegate*, | |
41 base::WeakPtr<TestDownloadRequestInterceptor>)>; | |
42 | |
43 ~TestDownloadRequestInterceptor() override; | |
44 | |
45 // Unregisters the URLRequestInterceptor. In reality it unregisters whatever | |
46 // was registered to intercept |url_|. Since |this| is owned by | |
47 // net::URLRequestFilter, unregistering the interceptor deletes |this| as a | |
48 // side-effect. | |
49 void Unregister(); | |
50 | |
51 // Change the URLRequestJob factory. Can be called multiple times. | |
52 void SetJobFactory(const JobFactory& factory); | |
53 | |
54 // Sets |requests| to the vector of completed requests and clears the internal | |
55 // list. The returned requests are stored in the order in which they were | |
56 // reported as being complete (not necessarily the order in which they were | |
57 // received). | |
58 void GetAndResetCompletedRequests( | |
59 TestDownloadRequestHandler::CompletedRequests* requests); | |
60 | |
61 // Can be called by a URLRequestJob to notify this interceptor of a completed | |
62 // request. | |
63 void AddCompletedRequest( | |
64 const TestDownloadRequestHandler::CompletedRequest& request); | |
65 | |
66 private: | |
67 TestDownloadRequestInterceptor(const GURL& url); | |
68 | |
69 // net::URLRequestInterceptor | |
70 net::URLRequestJob* MaybeInterceptRequest( | |
71 net::URLRequest* request, | |
72 net::NetworkDelegate* network_delegate) const override; | |
73 | |
74 TestDownloadRequestHandler::CompletedRequests completed_requests_; | |
75 GURL url_; | |
76 JobFactory job_factory_; | |
77 // mutable because MaybeInterceptRequest() is inexplicably const. | |
78 mutable base::WeakPtrFactory<TestDownloadRequestInterceptor> | |
79 weak_ptr_factory_; | |
80 DISALLOW_COPY_AND_ASSIGN(TestDownloadRequestInterceptor); | |
81 }; | |
82 | |
83 // A URLRequestJob that constructs a response to a URLRequest based on the | |
84 // contents of a Parameters object. It can handle partial (i.e. byte range | |
85 // requests). Created on and lives on the IO thread. | |
86 class PartialResponseJob : public net::URLRequestJob { | |
87 public: | |
88 static net::URLRequestJob* Factory( | |
89 const Parameters& parameters, | |
90 net::URLRequest* request, | |
91 net::NetworkDelegate* delegate, | |
92 base::WeakPtr<TestDownloadRequestInterceptor> interceptor); | |
93 | |
94 // URLRequestJob | |
95 void Start() override; | |
96 void GetResponseInfo(net::HttpResponseInfo* response_info) override; | |
97 int64 GetTotalReceivedBytes() const override; | |
98 bool GetMimeType(std::string* mime_type) const override; | |
99 int GetResponseCode() const override; | |
100 bool ReadRawData(net::IOBuffer* buf, int buf_size, int* bytes_read) override; | |
101 | |
102 private: | |
103 PartialResponseJob(scoped_ptr<Parameters> parameters, | |
104 base::WeakPtr<TestDownloadRequestInterceptor> interceptor, | |
105 net::URLRequest* url_request, | |
106 net::NetworkDelegate* network_delegate); | |
107 | |
108 ~PartialResponseJob() override; | |
109 void ReportCompletedRequest(int64_t transferred_content_length); | |
110 static void OnStartResponseCallbackOnPossiblyIncorrectThread( | |
111 base::WeakPtr<PartialResponseJob> job, | |
112 const std::string& headers, | |
113 net::Error error); | |
114 void OnStartResponseCallback(const std::string& headers, net::Error error); | |
115 | |
116 // In general, the Parameters object can specify an explicit OnStart handler. | |
117 // In its absence or if the explicit OnStart handler requests the default | |
118 // behavior, this method can be invoked to respond to the request based on the | |
119 // remaining Parameters fields (as if there was no OnStart handler). | |
120 void HandleOnStartDefault(); | |
121 | |
122 // Respond Start() assuming that any If-Match or If-Range headers have been | |
123 // successfully validated. This handler assumes that there *must* be a Range | |
124 // header even though the spec doesn't strictly require it for If-Match. | |
125 bool HandleRangeAssumingValidatorMatch(); | |
126 | |
127 // Adds headers that describe the entity (Content-Type, ETag, Last-Modified). | |
128 // It also adds an 'Accept-Ranges' header if appropriate. | |
129 void AddCommonEntityHeaders(); | |
130 | |
131 // Schedules NotifyHeadersComplete() to be called and sets | |
132 // offset_of_next_read_ to begin reading. Since this interceptor is avoiding | |
133 // network requests, it schedules the NotifyHeadersComplete() call | |
134 // asynchronously in order to avoid unexpected re-entrancy. | |
Randy Smith (Not in Mondays)
2015/11/17 22:21:42
I don't understand this sentence; what does avoidi
asanka
2015/11/23 22:04:59
Had we hit the network, we are likely to block and
Randy Smith (Not in Mondays)
2015/11/28 00:38:19
Gotcha. Suggestion, nit: add ".. is avoiding netw
asanka
2015/11/30 18:00:10
Done.
| |
135 void NotifyHeadersCompleteAndPrepareToRead(); | |
136 | |
137 scoped_ptr<Parameters> parameters_; | |
138 | |
139 base::WeakPtr<TestDownloadRequestInterceptor> interceptor_; | |
140 net::HttpResponseInfo response_info_; | |
141 int64_t offset_of_next_read_ = -1; | |
142 int64_t requested_range_begin_ = -1; | |
143 int64_t requested_range_end_ = -1; | |
144 base::WeakPtrFactory<PartialResponseJob> weak_factory_; | |
145 | |
146 DISALLOW_COPY_AND_ASSIGN(PartialResponseJob); | |
147 }; | |
148 | |
149 template <class T> | |
150 void StoreValueAndInvokeClosure(const base::Closure& quit_closure, | |
Randy Smith (Not in Mondays)
2015/11/17 22:21:42
nitty nit: The name "quit_closure" presumes how th
asanka
2015/11/23 22:04:59
Renamed to 'closure'.
| |
151 T* value_receiver, | |
152 T value) { | |
153 *value_receiver = value; | |
154 quit_closure.Run(); | |
155 } | |
156 | |
157 // Xorshift* PRNG from https://en.wikipedia.org/wiki/Xorshift | |
Randy Smith (Not in Mondays)
2015/11/17 22:21:42
Did not review except to validate that it looked a
asanka
2015/11/23 22:04:59
:-P
| |
158 uint64_t XorShift64StarWithIndex(uint64_t seed, uint64_t index) { | |
159 const uint64_t kMultiplier = UINT64_C(2685821657736338717); | |
160 uint64_t x = seed * kMultiplier + index; | |
161 x ^= x >> 12; | |
162 x ^= x << 25; | |
163 x ^= x >> 27; | |
164 return x * kMultiplier; | |
165 } | |
166 | |
167 void RespondToOnStartedCallbackWithStaticHeaders( | |
168 const std::string& headers, | |
169 const net::HttpRequestHeaders&, | |
170 const TestDownloadRequestHandler::OnStartResponseCallback& callback) { | |
171 callback.Run(headers, net::OK); | |
172 } | |
173 | |
174 GURL GetNextURLForDownloadInterceptor() { | |
175 static int index = 0; | |
176 std::string url_string = | |
177 base::StringPrintf("https://%d.default.example.com/download/", ++index); | |
178 return GURL(url_string); | |
179 } | |
180 | |
181 scoped_refptr<net::HttpResponseHeaders> HeadersFromString( | |
182 const std::string& headers_string) { | |
183 scoped_refptr<net::HttpResponseHeaders> headers = | |
184 new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders( | |
185 headers_string.c_str(), headers_string.size())); | |
186 return headers; | |
187 } | |
188 | |
189 // static | |
190 net::URLRequestJob* PartialResponseJob::Factory( | |
191 const Parameters& parameters, | |
192 net::URLRequest* request, | |
193 net::NetworkDelegate* delegate, | |
194 base::WeakPtr<TestDownloadRequestInterceptor> interceptor) { | |
195 return new PartialResponseJob(make_scoped_ptr(new Parameters(parameters)), | |
196 interceptor, request, delegate); | |
197 } | |
198 | |
199 PartialResponseJob::PartialResponseJob( | |
200 scoped_ptr<Parameters> parameters, | |
201 base::WeakPtr<TestDownloadRequestInterceptor> interceptor, | |
202 net::URLRequest* request, | |
203 net::NetworkDelegate* network_delegate) | |
204 : net::URLRequestJob(request, network_delegate), | |
205 parameters_(parameters.Pass()), | |
206 interceptor_(interceptor), | |
207 weak_factory_(this) { | |
208 DCHECK(parameters_.get()); | |
209 DCHECK_LT(0, parameters_->size); | |
210 DCHECK_NE(-1, parameters_->pattern_generator_seed); | |
211 } | |
212 | |
213 PartialResponseJob::~PartialResponseJob() {} | |
214 | |
215 void PartialResponseJob::Start() { | |
216 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
217 DVLOG(1) << "Starting request for " << request()->url().spec(); | |
218 | |
219 if (parameters_->on_start_handler.is_null()) { | |
220 HandleOnStartDefault(); | |
221 return; | |
222 } | |
223 | |
224 DVLOG(1) << "Invoking custom OnStart handler."; | |
225 BrowserThread::PostTask( | |
226 BrowserThread::UI, FROM_HERE, | |
227 base::Bind( | |
228 parameters_->on_start_handler, request()->extra_request_headers(), | |
229 base::Bind(&PartialResponseJob:: | |
230 OnStartResponseCallbackOnPossiblyIncorrectThread, | |
231 weak_factory_.GetWeakPtr()))); | |
232 } | |
233 | |
234 void PartialResponseJob::GetResponseInfo(net::HttpResponseInfo* response_info) { | |
235 *response_info = response_info_; | |
236 } | |
237 | |
238 int64 PartialResponseJob::GetTotalReceivedBytes() const { | |
239 return offset_of_next_read_ - requested_range_begin_; | |
240 } | |
241 | |
242 bool PartialResponseJob::GetMimeType(std::string* mime_type) const { | |
243 *mime_type = parameters_->content_type; | |
244 return !parameters_->content_type.empty(); | |
245 } | |
246 | |
247 int PartialResponseJob::GetResponseCode() const { | |
248 return response_info_.headers.get() ? response_info_.headers->response_code() | |
249 : 0; | |
250 } | |
251 | |
252 bool PartialResponseJob::ReadRawData(net::IOBuffer* buf, | |
253 int buf_size, | |
254 int* bytes_read) { | |
255 *bytes_read = 0; | |
256 DVLOG(1) << "Preparing to read " << buf_size << " bytes"; | |
257 | |
258 // requested_range_begin_ == -1 implies that the body was empty. | |
259 if (offset_of_next_read_ > requested_range_end_ || | |
260 requested_range_begin_ == -1) { | |
261 ReportCompletedRequest(requested_range_end_ - requested_range_begin_ + 1); | |
262 DVLOG(1) << "Done reading."; | |
263 return true; | |
264 } | |
265 | |
266 int64_t range_end = | |
267 std::min(requested_range_end_, offset_of_next_read_ + buf_size - 1); | |
268 | |
269 if (!parameters_->injected_errors.empty()) { | |
270 const InjectedError& injected_error = parameters_->injected_errors.front(); | |
271 | |
272 if (offset_of_next_read_ == injected_error.offset) { | |
273 int error = injected_error.error; | |
274 SetStatus(net::URLRequestStatus(net::URLRequestStatus::FAILED, error)); | |
275 DVLOG(1) << "Returning error " << net::ErrorToString(error); | |
276 ReportCompletedRequest(injected_error.offset - requested_range_begin_); | |
277 parameters_->injected_errors.pop(); | |
278 return false; | |
279 } | |
280 | |
281 if (offset_of_next_read_ < injected_error.offset && | |
282 injected_error.offset <= range_end) | |
283 range_end = injected_error.offset - 1; | |
284 } | |
285 int bytes_to_copy = (range_end - offset_of_next_read_) + 1; | |
286 | |
287 TestDownloadRequestHandler::GetPatternBytes( | |
288 parameters_->pattern_generator_seed, offset_of_next_read_, bytes_to_copy, | |
289 buf->data()); | |
290 DVLOG(1) << "Read " << bytes_to_copy << " bytes at offset " | |
291 << offset_of_next_read_; | |
292 offset_of_next_read_ += bytes_to_copy; | |
293 *bytes_read = bytes_to_copy; | |
294 return true; | |
295 } | |
296 | |
297 void PartialResponseJob::ReportCompletedRequest( | |
298 int64_t transferred_content_length) { | |
299 if (interceptor_.get()) { | |
300 TestDownloadRequestHandler::CompletedRequest completed_request; | |
301 completed_request.transferred_content_length = transferred_content_length; | |
302 completed_request.request_headers = request()->extra_request_headers(); | |
303 interceptor_->AddCompletedRequest(completed_request); | |
304 } | |
305 } | |
306 | |
307 // static | |
308 void PartialResponseJob::OnStartResponseCallbackOnPossiblyIncorrectThread( | |
309 base::WeakPtr<PartialResponseJob> job, | |
310 const std::string& headers, | |
311 net::Error error) { | |
312 BrowserThread::PostTask( | |
313 BrowserThread::IO, FROM_HERE, | |
314 base::Bind(&PartialResponseJob::OnStartResponseCallback, job, headers, | |
315 error)); | |
316 } | |
317 | |
318 void PartialResponseJob::OnStartResponseCallback(const std::string& headers, | |
319 net::Error error) { | |
320 DVLOG(1) << "OnStartResponse invoked with error:" << error | |
321 << " and headers:" << std::endl | |
322 << headers; | |
323 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
324 if (headers.empty() && error == net::OK) { | |
325 HandleOnStartDefault(); | |
326 return; | |
327 } | |
328 | |
329 if (error != net::OK) { | |
330 NotifyStartError(net::URLRequestStatus::FromError(error)); | |
331 return; | |
332 } | |
333 | |
334 response_info_.headers = new net::HttpResponseHeaders( | |
335 net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.size())); | |
336 NotifyHeadersCompleteAndPrepareToRead(); | |
337 } | |
338 | |
339 void PartialResponseJob::HandleOnStartDefault() { | |
340 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
341 SetStatus(net::URLRequestStatus()); | |
342 | |
343 const net::HttpRequestHeaders& extra_headers = | |
344 request()->extra_request_headers(); | |
345 | |
346 DCHECK(request()->method() == "GET") << "PartialResponseJob only " | |
347 "knows how to respond to GET " | |
348 "requests"; | |
349 std::string value; | |
350 | |
351 // If the request contains an 'If-Range' header and the value matches our | |
352 // ETag, then try to handle the range request. | |
353 if (parameters_->support_byte_ranges && | |
354 extra_headers.GetHeader(net::HttpRequestHeaders::kIfRange, &value) && | |
355 value == parameters_->etag && HandleRangeAssumingValidatorMatch()) | |
356 return; | |
357 | |
358 if (parameters_->support_byte_ranges && | |
359 extra_headers.GetHeader("If-Match", &value)) { | |
360 if (value == parameters_->etag && HandleRangeAssumingValidatorMatch()) | |
361 return; | |
362 | |
363 // Unlike If-Range, If-Match returns an error if the validators don't match. | |
364 response_info_.headers = HeadersFromString( | |
365 "HTTP/1.1 412 Precondition failed\r\n" | |
366 "Content-Length: 0\r\n"); | |
367 requested_range_begin_ = requested_range_end_ = -1; | |
368 NotifyHeadersCompleteAndPrepareToRead(); | |
369 return; | |
370 } | |
371 | |
372 requested_range_begin_ = 0; | |
373 requested_range_end_ = parameters_->size - 1; | |
374 response_info_.headers = | |
375 HeadersFromString(base::StringPrintf("HTTP/1.1 200 Success\r\n" | |
376 "Content-Length: %" PRId64 "\r\n", | |
377 parameters_->size)); | |
378 AddCommonEntityHeaders(); | |
379 NotifyHeadersCompleteAndPrepareToRead(); | |
380 return; | |
381 } | |
382 | |
383 bool PartialResponseJob::HandleRangeAssumingValidatorMatch() { | |
384 const net::HttpRequestHeaders& extra_headers = | |
385 request()->extra_request_headers(); | |
386 | |
387 std::string range_header; | |
388 std::vector<net::HttpByteRange> byte_ranges; | |
389 | |
390 // There needs to be a 'Range' header and it should have exactly one range. | |
391 // This server is not going to deal with multiple ranges. | |
392 if (!extra_headers.GetHeader(net::HttpRequestHeaders::kRange, | |
393 &range_header) || | |
394 !net::HttpUtil::ParseRangeHeader(range_header, &byte_ranges) || | |
395 byte_ranges.size() != 1) | |
396 return false; | |
397 | |
398 // The request may have specified a range that's out of bounds. | |
399 if (!byte_ranges[0].ComputeBounds(parameters_->size)) { | |
400 response_info_.headers = HeadersFromString( | |
401 base::StringPrintf("HTTP/1.1 416 Range not satisfiable\r\n" | |
402 "Content-Range: bytes */%" PRId64 "\r\n" | |
403 "Content-Length: 0\r\n", | |
404 parameters_->size)); | |
405 requested_range_begin_ = requested_range_end_ = -1; | |
406 NotifyHeadersCompleteAndPrepareToRead(); | |
407 return true; | |
408 } | |
409 | |
410 requested_range_begin_ = byte_ranges[0].first_byte_position(); | |
411 requested_range_end_ = byte_ranges[0].last_byte_position(); | |
412 response_info_.headers = | |
413 HeadersFromString(base::StringPrintf( | |
414 "HTTP/1.1 206 Partial content\r\n" | |
415 "Content-Range: bytes %" PRId64 "-%" PRId64 "/%" PRId64 "\r\n" | |
416 "Content-Length: %" PRId64 "\r\n", | |
417 requested_range_begin_, requested_range_end_, parameters_->size, | |
418 (requested_range_end_ - requested_range_begin_) + 1)); | |
419 AddCommonEntityHeaders(); | |
420 NotifyHeadersCompleteAndPrepareToRead(); | |
421 return true; | |
422 } | |
423 | |
424 void PartialResponseJob::AddCommonEntityHeaders() { | |
425 if (parameters_->support_byte_ranges) | |
426 response_info_.headers->AddHeader("Accept-Ranges: bytes"); | |
427 | |
428 if (!parameters_->content_type.empty()) | |
429 response_info_.headers->AddHeader(base::StringPrintf( | |
430 "Content-Type: %s", parameters_->content_type.c_str())); | |
431 | |
432 if (!parameters_->etag.empty()) | |
433 response_info_.headers->AddHeader( | |
434 base::StringPrintf("ETag: %s", parameters_->etag.c_str())); | |
435 | |
436 if (!parameters_->last_modified.empty()) | |
437 response_info_.headers->AddHeader(base::StringPrintf( | |
438 "Last-Modified: %s", parameters_->last_modified.c_str())); | |
439 } | |
440 | |
441 void PartialResponseJob::NotifyHeadersCompleteAndPrepareToRead() { | |
442 std::string normalized_headers; | |
443 response_info_.headers->GetNormalizedHeaders(&normalized_headers); | |
444 DVLOG(1) << "Notify ready with headers:\n" << normalized_headers; | |
445 | |
446 offset_of_next_read_ = requested_range_begin_; | |
447 | |
448 // Flush out injected_errors that no longer apply. We are going to skip over | |
449 // ones where the |offset| == |requested_range_begin_| as well. While it | |
450 // prevents injecting an error at offset 0, it makes it much easier to set up | |
451 // parameter sets for download resumption. It means that when a request is | |
452 // interrupted at offset O, a subsequent request for the range O-<end> won't | |
453 // immediately interrupt as well. If we don't exclude interruptions at | |
454 // relative offset 0, then test writers would need to reset the parameter | |
455 // prior to each resumption rather than once at the beginning of the test. | |
456 while (!parameters_->injected_errors.empty() && | |
457 parameters_->injected_errors.front().offset <= requested_range_begin_) | |
458 parameters_->injected_errors.pop(); | |
459 | |
460 base::MessageLoop::current()->PostTask( | |
461 FROM_HERE, base::Bind(&PartialResponseJob::NotifyHeadersComplete, | |
462 weak_factory_.GetWeakPtr())); | |
463 } | |
464 | |
465 // static | |
466 base::WeakPtr<TestDownloadRequestInterceptor> | |
467 TestDownloadRequestInterceptor::Register(const GURL& url) { | |
468 DCHECK(url.is_valid()); | |
469 scoped_ptr<TestDownloadRequestInterceptor> interceptor( | |
470 new TestDownloadRequestInterceptor(url)); | |
471 base::WeakPtr<TestDownloadRequestInterceptor> weak_reference = | |
472 interceptor->weak_ptr_factory_.GetWeakPtr(); | |
473 net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); | |
474 filter->AddUrlInterceptor(url, interceptor.Pass()); | |
475 return weak_reference; | |
476 } | |
477 | |
478 void TestDownloadRequestInterceptor::Unregister() { | |
479 net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); | |
480 filter->RemoveUrlHandler(url_); | |
481 // We are deleted now since the filter owned |this|. | |
482 } | |
483 | |
484 void TestDownloadRequestInterceptor::SetJobFactory( | |
485 const JobFactory& job_factory) { | |
486 job_factory_ = job_factory; | |
487 } | |
488 | |
489 void TestDownloadRequestInterceptor::GetAndResetCompletedRequests( | |
490 TestDownloadRequestHandler::CompletedRequests* requests) { | |
491 requests->clear(); | |
492 completed_requests_.swap(*requests); | |
493 } | |
494 | |
495 void TestDownloadRequestInterceptor::AddCompletedRequest( | |
496 const TestDownloadRequestHandler::CompletedRequest& request) { | |
497 completed_requests_.push_back(request); | |
498 } | |
499 | |
500 TestDownloadRequestInterceptor::TestDownloadRequestInterceptor(const GURL& url) | |
501 : url_(url), weak_ptr_factory_(this) {} | |
502 | |
503 TestDownloadRequestInterceptor::~TestDownloadRequestInterceptor() {} | |
504 | |
505 net::URLRequestJob* TestDownloadRequestInterceptor::MaybeInterceptRequest( | |
506 net::URLRequest* request, | |
507 net::NetworkDelegate* network_delegate) const { | |
508 DVLOG(1) << "Intercepting request for " << request->url() | |
509 << " with headers:\n" << request->extra_request_headers().ToString(); | |
510 if (job_factory_.is_null()) | |
511 return nullptr; | |
512 return job_factory_.Run(request, network_delegate, | |
513 weak_ptr_factory_.GetWeakPtr()); | |
514 } | |
515 | |
516 } // namespace | |
517 | |
518 struct TestDownloadRequestHandler::InterceptorProxy { | |
519 base::WeakPtr<TestDownloadRequestInterceptor> interceptor_; | |
520 }; | |
521 | |
522 TestDownloadRequestHandler::InjectedError::InjectedError(int64_t offset, | |
523 net::Error error) | |
524 : offset(offset), error(error) {} | |
525 | |
526 // static | |
527 Parameters TestDownloadRequestHandler::Parameters::WithSingleInterruption() { | |
528 Parameters parameters; | |
529 parameters.injected_errors.push( | |
530 InjectedError(parameters.size / 2, net::ERR_CONNECTION_RESET)); | |
531 return parameters; | |
532 } | |
533 | |
534 TestDownloadRequestHandler::Parameters::Parameters() | |
535 : etag("abcd"), | |
536 last_modified("Tue, 15 Nov 1994 12:45:26 GMT"), | |
537 content_type("application/octet-stream"), | |
538 size(102400), | |
539 pattern_generator_seed(1), | |
540 support_byte_ranges(true) {} | |
541 | |
542 TestDownloadRequestHandler::Parameters::~Parameters() {} | |
543 | |
544 void TestDownloadRequestHandler::Parameters::ClearInjectedErrors() { | |
545 std::queue<InjectedError> empty_error_list; | |
546 injected_errors.swap(empty_error_list); | |
547 } | |
548 | |
549 TestDownloadRequestHandler::TestDownloadRequestHandler() | |
550 : TestDownloadRequestHandler(GetNextURLForDownloadInterceptor()) {} | |
551 | |
552 TestDownloadRequestHandler::TestDownloadRequestHandler(const GURL& url) | |
553 : url_(url) { | |
554 interceptor_proxy_.reset(new InterceptorProxy); | |
555 base::RunLoop run_loop; | |
556 BrowserThread::PostTaskAndReplyWithResult( | |
557 BrowserThread::IO, FROM_HERE, | |
558 base::Bind(&TestDownloadRequestInterceptor::Register, url_), | |
559 base::Bind(&StoreValueAndInvokeClosure< | |
560 base::WeakPtr<TestDownloadRequestInterceptor>>, | |
561 run_loop.QuitClosure(), &interceptor_proxy_->interceptor_)); | |
562 run_loop.Run(); | |
563 } | |
564 | |
565 void TestDownloadRequestHandler::StartServing(const Parameters& parameters) { | |
566 TestDownloadRequestInterceptor::JobFactory job_factory = | |
567 base::Bind(&PartialResponseJob::Factory, parameters); | |
568 // Interceptor, if valid, is already registered and serving requests. We just | |
569 // need to set the correct job factory for it to start serving using the new | |
570 // parameters. | |
571 BrowserThread::PostTask( | |
572 BrowserThread::IO, FROM_HERE, | |
573 base::Bind(&TestDownloadRequestInterceptor::SetJobFactory, | |
574 interceptor_proxy_->interceptor_, job_factory)); | |
575 } | |
576 | |
577 void TestDownloadRequestHandler::StartServingStaticResponse( | |
578 const base::StringPiece& headers) { | |
579 Parameters parameters; | |
580 parameters.on_start_handler = base::Bind( | |
581 &RespondToOnStartedCallbackWithStaticHeaders, headers.as_string()); | |
582 StartServing(parameters); | |
583 } | |
584 | |
585 // static | |
586 void TestDownloadRequestHandler::GetPatternBytes(int seed, | |
587 int64_t starting_offset, | |
588 int length, | |
589 char* buffer) { | |
590 int64_t seed_offset = starting_offset / sizeof(int64_t); | |
591 int64_t first_byte_position = starting_offset % sizeof(int64_t); | |
592 while (length > 0) { | |
593 uint64_t data = XorShift64StarWithIndex(seed, seed_offset); | |
594 int length_to_copy = | |
595 std::min(length, static_cast<int>(sizeof(data) - first_byte_position)); | |
596 memcpy(buffer, reinterpret_cast<char*>(&data) + first_byte_position, | |
597 length_to_copy); | |
598 buffer += length_to_copy; | |
599 length -= length_to_copy; | |
600 ++seed_offset; | |
601 first_byte_position = 0; | |
602 } | |
603 } | |
604 | |
605 TestDownloadRequestHandler::~TestDownloadRequestHandler() { | |
606 BrowserThread::PostTask( | |
607 BrowserThread::IO, FROM_HERE, | |
608 base::Bind(&TestDownloadRequestInterceptor::Unregister, | |
609 interceptor_proxy_->interceptor_)); | |
610 } | |
611 | |
612 void TestDownloadRequestHandler::GetCompletedRequestInfo( | |
613 TestDownloadRequestHandler::CompletedRequests* requests) { | |
614 base::RunLoop run_loop; | |
615 BrowserThread::PostTaskAndReply( | |
616 BrowserThread::IO, FROM_HERE, | |
617 base::Bind(&TestDownloadRequestInterceptor::GetAndResetCompletedRequests, | |
618 interceptor_proxy_->interceptor_, requests), | |
619 run_loop.QuitClosure()); | |
620 run_loop.Run(); | |
621 } | |
622 | |
623 } // namespace content | |
OLD | NEW |