Chromium Code Reviews| Index: content/public/test/test_download_request_handler.cc |
| diff --git a/content/public/test/test_download_request_handler.cc b/content/public/test/test_download_request_handler.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..12450aede6fa083b809efb1fcc560a74b46a240f |
| --- /dev/null |
| +++ b/content/public/test/test_download_request_handler.cc |
| @@ -0,0 +1,623 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/public/test/test_download_request_handler.h" |
| + |
| +#include <inttypes.h> |
| + |
| +#include "base/logging.h" |
| +#include "base/memory/weak_ptr.h" |
| +#include "base/run_loop.h" |
| +#include "base/strings/stringprintf.h" |
| +#include "content/public/browser/browser_thread.h" |
| +#include "net/base/io_buffer.h" |
| +#include "net/http/http_request_headers.h" |
| +#include "net/http/http_response_headers.h" |
| +#include "net/url_request/url_request_filter.h" |
| +#include "net/url_request/url_request_interceptor.h" |
| + |
| +namespace content { |
| + |
| +namespace { |
| + |
| +using Parameters = TestDownloadRequestHandler::Parameters; |
| +using InjectedError = TestDownloadRequestHandler::InjectedError; |
| + |
| +// Intercepts URLRequests on behalf of TestDownloadRequestHandler. Necessarily |
| +// lives on the IO thread since that's where net::URLRequestFilter invokes the |
| +// URLRequestInterceptor. |
| +class TestDownloadRequestInterceptor : public net::URLRequestInterceptor { |
| + public: |
| + // Invoked on the IO thread to register a URLRequestInterceptor for |url|. |
| + // Returns an IO-thread bound weak pointer for interacting with the |
| + // interceptor. |
| + static base::WeakPtr<TestDownloadRequestInterceptor> Register( |
| + const GURL& url); |
| + |
| + using JobFactory = base::Callback<net::URLRequestJob*( |
| + net::URLRequest*, |
| + net::NetworkDelegate*, |
| + base::WeakPtr<TestDownloadRequestInterceptor>)>; |
| + |
| + ~TestDownloadRequestInterceptor() override; |
| + |
| + // Unregisters the URLRequestInterceptor. In reality it unregisters whatever |
| + // was registered to intercept |url_|. Since |this| is owned by |
| + // net::URLRequestFilter, unregistering the interceptor deletes |this| as a |
| + // side-effect. |
| + void Unregister(); |
| + |
| + // Change the URLRequestJob factory. Can be called multiple times. |
| + void SetJobFactory(const JobFactory& factory); |
| + |
| + // Sets |requests| to the vector of completed requests and clears the internal |
| + // list. The returned requests are stored in the order in which they were |
| + // reported as being complete (not necessarily the order in which they were |
| + // received). |
| + void GetAndResetCompletedRequests( |
| + TestDownloadRequestHandler::CompletedRequests* requests); |
| + |
| + // Can be called by a URLRequestJob to notify this interceptor of a completed |
| + // request. |
| + void AddCompletedRequest( |
| + const TestDownloadRequestHandler::CompletedRequest& request); |
| + |
| + private: |
| + TestDownloadRequestInterceptor(const GURL& url); |
| + |
| + // net::URLRequestInterceptor |
| + net::URLRequestJob* MaybeInterceptRequest( |
| + net::URLRequest* request, |
| + net::NetworkDelegate* network_delegate) const override; |
| + |
| + TestDownloadRequestHandler::CompletedRequests completed_requests_; |
| + GURL url_; |
| + JobFactory job_factory_; |
| + // mutable because MaybeInterceptRequest() is inexplicably const. |
| + mutable base::WeakPtrFactory<TestDownloadRequestInterceptor> |
| + weak_ptr_factory_; |
| + DISALLOW_COPY_AND_ASSIGN(TestDownloadRequestInterceptor); |
| +}; |
| + |
| +// A URLRequestJob that constructs a response to a URLRequest based on the |
| +// contents of a Parameters object. It can handle partial (i.e. byte range |
| +// requests). Created on and lives on the IO thread. |
| +class PartialResponseJob : public net::URLRequestJob { |
| + public: |
| + static net::URLRequestJob* Factory( |
| + const Parameters& parameters, |
| + net::URLRequest* request, |
| + net::NetworkDelegate* delegate, |
| + base::WeakPtr<TestDownloadRequestInterceptor> interceptor); |
| + |
| + // URLRequestJob |
| + void Start() override; |
| + void GetResponseInfo(net::HttpResponseInfo* response_info) override; |
| + int64 GetTotalReceivedBytes() const override; |
| + bool GetMimeType(std::string* mime_type) const override; |
| + int GetResponseCode() const override; |
| + bool ReadRawData(net::IOBuffer* buf, int buf_size, int* bytes_read) override; |
| + |
| + private: |
| + PartialResponseJob(scoped_ptr<Parameters> parameters, |
| + base::WeakPtr<TestDownloadRequestInterceptor> interceptor, |
| + net::URLRequest* url_request, |
| + net::NetworkDelegate* network_delegate); |
| + |
| + ~PartialResponseJob() override; |
| + void ReportCompletedRequest(int64_t transferred_content_length); |
| + static void OnStartResponseCallbackOnPossiblyIncorrectThread( |
| + base::WeakPtr<PartialResponseJob> job, |
| + const std::string& headers, |
| + net::Error error); |
| + void OnStartResponseCallback(const std::string& headers, net::Error error); |
| + |
| + // In general, the Parameters object can specify an explicit OnStart handler. |
| + // In its absence or if the explicit OnStart handler requests the default |
| + // behavior, this method can be invoked to respond to the request based on the |
| + // remaining Parameters fields (as if there was no OnStart handler). |
| + void HandleOnStartDefault(); |
| + |
| + // Respond Start() assuming that any If-Match or If-Range headers have been |
| + // successfully validated. This handler assumes that there *must* be a Range |
| + // header even though the spec doesn't strictly require it for If-Match. |
| + bool HandleRangeAssumingValidatorMatch(); |
| + |
| + // Adds headers that describe the entity (Content-Type, ETag, Last-Modified). |
| + // It also adds an 'Accept-Ranges' header if appropriate. |
| + void AddCommonEntityHeaders(); |
| + |
| + // Schedules NotifyHeadersComplete() to be called and sets |
| + // offset_of_next_read_ to begin reading. Since this interceptor is avoiding |
| + // network requests, it schedules the NotifyHeadersComplete() call |
| + // 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.
|
| + void NotifyHeadersCompleteAndPrepareToRead(); |
| + |
| + scoped_ptr<Parameters> parameters_; |
| + |
| + base::WeakPtr<TestDownloadRequestInterceptor> interceptor_; |
| + net::HttpResponseInfo response_info_; |
| + int64_t offset_of_next_read_ = -1; |
| + int64_t requested_range_begin_ = -1; |
| + int64_t requested_range_end_ = -1; |
| + base::WeakPtrFactory<PartialResponseJob> weak_factory_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(PartialResponseJob); |
| +}; |
| + |
| +template <class T> |
| +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'.
|
| + T* value_receiver, |
| + T value) { |
| + *value_receiver = value; |
| + quit_closure.Run(); |
| +} |
| + |
| +// 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
|
| +uint64_t XorShift64StarWithIndex(uint64_t seed, uint64_t index) { |
| + const uint64_t kMultiplier = UINT64_C(2685821657736338717); |
| + uint64_t x = seed * kMultiplier + index; |
| + x ^= x >> 12; |
| + x ^= x << 25; |
| + x ^= x >> 27; |
| + return x * kMultiplier; |
| +} |
| + |
| +void RespondToOnStartedCallbackWithStaticHeaders( |
| + const std::string& headers, |
| + const net::HttpRequestHeaders&, |
| + const TestDownloadRequestHandler::OnStartResponseCallback& callback) { |
| + callback.Run(headers, net::OK); |
| +} |
| + |
| +GURL GetNextURLForDownloadInterceptor() { |
| + static int index = 0; |
| + std::string url_string = |
| + base::StringPrintf("https://%d.default.example.com/download/", ++index); |
| + return GURL(url_string); |
| +} |
| + |
| +scoped_refptr<net::HttpResponseHeaders> HeadersFromString( |
| + const std::string& headers_string) { |
| + scoped_refptr<net::HttpResponseHeaders> headers = |
| + new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders( |
| + headers_string.c_str(), headers_string.size())); |
| + return headers; |
| +} |
| + |
| +// static |
| +net::URLRequestJob* PartialResponseJob::Factory( |
| + const Parameters& parameters, |
| + net::URLRequest* request, |
| + net::NetworkDelegate* delegate, |
| + base::WeakPtr<TestDownloadRequestInterceptor> interceptor) { |
| + return new PartialResponseJob(make_scoped_ptr(new Parameters(parameters)), |
| + interceptor, request, delegate); |
| +} |
| + |
| +PartialResponseJob::PartialResponseJob( |
| + scoped_ptr<Parameters> parameters, |
| + base::WeakPtr<TestDownloadRequestInterceptor> interceptor, |
| + net::URLRequest* request, |
| + net::NetworkDelegate* network_delegate) |
| + : net::URLRequestJob(request, network_delegate), |
| + parameters_(parameters.Pass()), |
| + interceptor_(interceptor), |
| + weak_factory_(this) { |
| + DCHECK(parameters_.get()); |
| + DCHECK_LT(0, parameters_->size); |
| + DCHECK_NE(-1, parameters_->pattern_generator_seed); |
| +} |
| + |
| +PartialResponseJob::~PartialResponseJob() {} |
| + |
| +void PartialResponseJob::Start() { |
| + DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| + DVLOG(1) << "Starting request for " << request()->url().spec(); |
| + |
| + if (parameters_->on_start_handler.is_null()) { |
| + HandleOnStartDefault(); |
| + return; |
| + } |
| + |
| + DVLOG(1) << "Invoking custom OnStart handler."; |
| + BrowserThread::PostTask( |
| + BrowserThread::UI, FROM_HERE, |
| + base::Bind( |
| + parameters_->on_start_handler, request()->extra_request_headers(), |
| + base::Bind(&PartialResponseJob:: |
| + OnStartResponseCallbackOnPossiblyIncorrectThread, |
| + weak_factory_.GetWeakPtr()))); |
| +} |
| + |
| +void PartialResponseJob::GetResponseInfo(net::HttpResponseInfo* response_info) { |
| + *response_info = response_info_; |
| +} |
| + |
| +int64 PartialResponseJob::GetTotalReceivedBytes() const { |
| + return offset_of_next_read_ - requested_range_begin_; |
| +} |
| + |
| +bool PartialResponseJob::GetMimeType(std::string* mime_type) const { |
| + *mime_type = parameters_->content_type; |
| + return !parameters_->content_type.empty(); |
| +} |
| + |
| +int PartialResponseJob::GetResponseCode() const { |
| + return response_info_.headers.get() ? response_info_.headers->response_code() |
| + : 0; |
| +} |
| + |
| +bool PartialResponseJob::ReadRawData(net::IOBuffer* buf, |
| + int buf_size, |
| + int* bytes_read) { |
| + *bytes_read = 0; |
| + DVLOG(1) << "Preparing to read " << buf_size << " bytes"; |
| + |
| + // requested_range_begin_ == -1 implies that the body was empty. |
| + if (offset_of_next_read_ > requested_range_end_ || |
| + requested_range_begin_ == -1) { |
| + ReportCompletedRequest(requested_range_end_ - requested_range_begin_ + 1); |
| + DVLOG(1) << "Done reading."; |
| + return true; |
| + } |
| + |
| + int64_t range_end = |
| + std::min(requested_range_end_, offset_of_next_read_ + buf_size - 1); |
| + |
| + if (!parameters_->injected_errors.empty()) { |
| + const InjectedError& injected_error = parameters_->injected_errors.front(); |
| + |
| + if (offset_of_next_read_ == injected_error.offset) { |
| + int error = injected_error.error; |
| + SetStatus(net::URLRequestStatus(net::URLRequestStatus::FAILED, error)); |
| + DVLOG(1) << "Returning error " << net::ErrorToString(error); |
| + ReportCompletedRequest(injected_error.offset - requested_range_begin_); |
| + parameters_->injected_errors.pop(); |
| + return false; |
| + } |
| + |
| + if (offset_of_next_read_ < injected_error.offset && |
| + injected_error.offset <= range_end) |
| + range_end = injected_error.offset - 1; |
| + } |
| + int bytes_to_copy = (range_end - offset_of_next_read_) + 1; |
| + |
| + TestDownloadRequestHandler::GetPatternBytes( |
| + parameters_->pattern_generator_seed, offset_of_next_read_, bytes_to_copy, |
| + buf->data()); |
| + DVLOG(1) << "Read " << bytes_to_copy << " bytes at offset " |
| + << offset_of_next_read_; |
| + offset_of_next_read_ += bytes_to_copy; |
| + *bytes_read = bytes_to_copy; |
| + return true; |
| +} |
| + |
| +void PartialResponseJob::ReportCompletedRequest( |
| + int64_t transferred_content_length) { |
| + if (interceptor_.get()) { |
| + TestDownloadRequestHandler::CompletedRequest completed_request; |
| + completed_request.transferred_content_length = transferred_content_length; |
| + completed_request.request_headers = request()->extra_request_headers(); |
| + interceptor_->AddCompletedRequest(completed_request); |
| + } |
| +} |
| + |
| +// static |
| +void PartialResponseJob::OnStartResponseCallbackOnPossiblyIncorrectThread( |
| + base::WeakPtr<PartialResponseJob> job, |
| + const std::string& headers, |
| + net::Error error) { |
| + BrowserThread::PostTask( |
| + BrowserThread::IO, FROM_HERE, |
| + base::Bind(&PartialResponseJob::OnStartResponseCallback, job, headers, |
| + error)); |
| +} |
| + |
| +void PartialResponseJob::OnStartResponseCallback(const std::string& headers, |
| + net::Error error) { |
| + DVLOG(1) << "OnStartResponse invoked with error:" << error |
| + << " and headers:" << std::endl |
| + << headers; |
| + DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| + if (headers.empty() && error == net::OK) { |
| + HandleOnStartDefault(); |
| + return; |
| + } |
| + |
| + if (error != net::OK) { |
| + NotifyStartError(net::URLRequestStatus::FromError(error)); |
| + return; |
| + } |
| + |
| + response_info_.headers = new net::HttpResponseHeaders( |
| + net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.size())); |
| + NotifyHeadersCompleteAndPrepareToRead(); |
| +} |
| + |
| +void PartialResponseJob::HandleOnStartDefault() { |
| + DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| + SetStatus(net::URLRequestStatus()); |
| + |
| + const net::HttpRequestHeaders& extra_headers = |
| + request()->extra_request_headers(); |
| + |
| + DCHECK(request()->method() == "GET") << "PartialResponseJob only " |
| + "knows how to respond to GET " |
| + "requests"; |
| + std::string value; |
| + |
| + // If the request contains an 'If-Range' header and the value matches our |
| + // ETag, then try to handle the range request. |
| + if (parameters_->support_byte_ranges && |
| + extra_headers.GetHeader(net::HttpRequestHeaders::kIfRange, &value) && |
| + value == parameters_->etag && HandleRangeAssumingValidatorMatch()) |
| + return; |
| + |
| + if (parameters_->support_byte_ranges && |
| + extra_headers.GetHeader("If-Match", &value)) { |
| + if (value == parameters_->etag && HandleRangeAssumingValidatorMatch()) |
| + return; |
| + |
| + // Unlike If-Range, If-Match returns an error if the validators don't match. |
| + response_info_.headers = HeadersFromString( |
| + "HTTP/1.1 412 Precondition failed\r\n" |
| + "Content-Length: 0\r\n"); |
| + requested_range_begin_ = requested_range_end_ = -1; |
| + NotifyHeadersCompleteAndPrepareToRead(); |
| + return; |
| + } |
| + |
| + requested_range_begin_ = 0; |
| + requested_range_end_ = parameters_->size - 1; |
| + response_info_.headers = |
| + HeadersFromString(base::StringPrintf("HTTP/1.1 200 Success\r\n" |
| + "Content-Length: %" PRId64 "\r\n", |
| + parameters_->size)); |
| + AddCommonEntityHeaders(); |
| + NotifyHeadersCompleteAndPrepareToRead(); |
| + return; |
| +} |
| + |
| +bool PartialResponseJob::HandleRangeAssumingValidatorMatch() { |
| + const net::HttpRequestHeaders& extra_headers = |
| + request()->extra_request_headers(); |
| + |
| + std::string range_header; |
| + std::vector<net::HttpByteRange> byte_ranges; |
| + |
| + // There needs to be a 'Range' header and it should have exactly one range. |
| + // This server is not going to deal with multiple ranges. |
| + if (!extra_headers.GetHeader(net::HttpRequestHeaders::kRange, |
| + &range_header) || |
| + !net::HttpUtil::ParseRangeHeader(range_header, &byte_ranges) || |
| + byte_ranges.size() != 1) |
| + return false; |
| + |
| + // The request may have specified a range that's out of bounds. |
| + if (!byte_ranges[0].ComputeBounds(parameters_->size)) { |
| + response_info_.headers = HeadersFromString( |
| + base::StringPrintf("HTTP/1.1 416 Range not satisfiable\r\n" |
| + "Content-Range: bytes */%" PRId64 "\r\n" |
| + "Content-Length: 0\r\n", |
| + parameters_->size)); |
| + requested_range_begin_ = requested_range_end_ = -1; |
| + NotifyHeadersCompleteAndPrepareToRead(); |
| + return true; |
| + } |
| + |
| + requested_range_begin_ = byte_ranges[0].first_byte_position(); |
| + requested_range_end_ = byte_ranges[0].last_byte_position(); |
| + response_info_.headers = |
| + HeadersFromString(base::StringPrintf( |
| + "HTTP/1.1 206 Partial content\r\n" |
| + "Content-Range: bytes %" PRId64 "-%" PRId64 "/%" PRId64 "\r\n" |
| + "Content-Length: %" PRId64 "\r\n", |
| + requested_range_begin_, requested_range_end_, parameters_->size, |
| + (requested_range_end_ - requested_range_begin_) + 1)); |
| + AddCommonEntityHeaders(); |
| + NotifyHeadersCompleteAndPrepareToRead(); |
| + return true; |
| +} |
| + |
| +void PartialResponseJob::AddCommonEntityHeaders() { |
| + if (parameters_->support_byte_ranges) |
| + response_info_.headers->AddHeader("Accept-Ranges: bytes"); |
| + |
| + if (!parameters_->content_type.empty()) |
| + response_info_.headers->AddHeader(base::StringPrintf( |
| + "Content-Type: %s", parameters_->content_type.c_str())); |
| + |
| + if (!parameters_->etag.empty()) |
| + response_info_.headers->AddHeader( |
| + base::StringPrintf("ETag: %s", parameters_->etag.c_str())); |
| + |
| + if (!parameters_->last_modified.empty()) |
| + response_info_.headers->AddHeader(base::StringPrintf( |
| + "Last-Modified: %s", parameters_->last_modified.c_str())); |
| +} |
| + |
| +void PartialResponseJob::NotifyHeadersCompleteAndPrepareToRead() { |
| + std::string normalized_headers; |
| + response_info_.headers->GetNormalizedHeaders(&normalized_headers); |
| + DVLOG(1) << "Notify ready with headers:\n" << normalized_headers; |
| + |
| + offset_of_next_read_ = requested_range_begin_; |
| + |
| + // Flush out injected_errors that no longer apply. We are going to skip over |
| + // ones where the |offset| == |requested_range_begin_| as well. While it |
| + // prevents injecting an error at offset 0, it makes it much easier to set up |
| + // parameter sets for download resumption. It means that when a request is |
| + // interrupted at offset O, a subsequent request for the range O-<end> won't |
| + // immediately interrupt as well. If we don't exclude interruptions at |
| + // relative offset 0, then test writers would need to reset the parameter |
| + // prior to each resumption rather than once at the beginning of the test. |
| + while (!parameters_->injected_errors.empty() && |
| + parameters_->injected_errors.front().offset <= requested_range_begin_) |
| + parameters_->injected_errors.pop(); |
| + |
| + base::MessageLoop::current()->PostTask( |
| + FROM_HERE, base::Bind(&PartialResponseJob::NotifyHeadersComplete, |
| + weak_factory_.GetWeakPtr())); |
| +} |
| + |
| +// static |
| +base::WeakPtr<TestDownloadRequestInterceptor> |
| +TestDownloadRequestInterceptor::Register(const GURL& url) { |
| + DCHECK(url.is_valid()); |
| + scoped_ptr<TestDownloadRequestInterceptor> interceptor( |
| + new TestDownloadRequestInterceptor(url)); |
| + base::WeakPtr<TestDownloadRequestInterceptor> weak_reference = |
| + interceptor->weak_ptr_factory_.GetWeakPtr(); |
| + net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); |
| + filter->AddUrlInterceptor(url, interceptor.Pass()); |
| + return weak_reference; |
| +} |
| + |
| +void TestDownloadRequestInterceptor::Unregister() { |
| + net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); |
| + filter->RemoveUrlHandler(url_); |
| + // We are deleted now since the filter owned |this|. |
| +} |
| + |
| +void TestDownloadRequestInterceptor::SetJobFactory( |
| + const JobFactory& job_factory) { |
| + job_factory_ = job_factory; |
| +} |
| + |
| +void TestDownloadRequestInterceptor::GetAndResetCompletedRequests( |
| + TestDownloadRequestHandler::CompletedRequests* requests) { |
| + requests->clear(); |
| + completed_requests_.swap(*requests); |
| +} |
| + |
| +void TestDownloadRequestInterceptor::AddCompletedRequest( |
| + const TestDownloadRequestHandler::CompletedRequest& request) { |
| + completed_requests_.push_back(request); |
| +} |
| + |
| +TestDownloadRequestInterceptor::TestDownloadRequestInterceptor(const GURL& url) |
| + : url_(url), weak_ptr_factory_(this) {} |
| + |
| +TestDownloadRequestInterceptor::~TestDownloadRequestInterceptor() {} |
| + |
| +net::URLRequestJob* TestDownloadRequestInterceptor::MaybeInterceptRequest( |
| + net::URLRequest* request, |
| + net::NetworkDelegate* network_delegate) const { |
| + DVLOG(1) << "Intercepting request for " << request->url() |
| + << " with headers:\n" << request->extra_request_headers().ToString(); |
| + if (job_factory_.is_null()) |
| + return nullptr; |
| + return job_factory_.Run(request, network_delegate, |
| + weak_ptr_factory_.GetWeakPtr()); |
| +} |
| + |
| +} // namespace |
| + |
| +struct TestDownloadRequestHandler::InterceptorProxy { |
| + base::WeakPtr<TestDownloadRequestInterceptor> interceptor_; |
| +}; |
| + |
| +TestDownloadRequestHandler::InjectedError::InjectedError(int64_t offset, |
| + net::Error error) |
| + : offset(offset), error(error) {} |
| + |
| +// static |
| +Parameters TestDownloadRequestHandler::Parameters::WithSingleInterruption() { |
| + Parameters parameters; |
| + parameters.injected_errors.push( |
| + InjectedError(parameters.size / 2, net::ERR_CONNECTION_RESET)); |
| + return parameters; |
| +} |
| + |
| +TestDownloadRequestHandler::Parameters::Parameters() |
| + : etag("abcd"), |
| + last_modified("Tue, 15 Nov 1994 12:45:26 GMT"), |
| + content_type("application/octet-stream"), |
| + size(102400), |
| + pattern_generator_seed(1), |
| + support_byte_ranges(true) {} |
| + |
| +TestDownloadRequestHandler::Parameters::~Parameters() {} |
| + |
| +void TestDownloadRequestHandler::Parameters::ClearInjectedErrors() { |
| + std::queue<InjectedError> empty_error_list; |
| + injected_errors.swap(empty_error_list); |
| +} |
| + |
| +TestDownloadRequestHandler::TestDownloadRequestHandler() |
| + : TestDownloadRequestHandler(GetNextURLForDownloadInterceptor()) {} |
| + |
| +TestDownloadRequestHandler::TestDownloadRequestHandler(const GURL& url) |
| + : url_(url) { |
| + interceptor_proxy_.reset(new InterceptorProxy); |
| + base::RunLoop run_loop; |
| + BrowserThread::PostTaskAndReplyWithResult( |
| + BrowserThread::IO, FROM_HERE, |
| + base::Bind(&TestDownloadRequestInterceptor::Register, url_), |
| + base::Bind(&StoreValueAndInvokeClosure< |
| + base::WeakPtr<TestDownloadRequestInterceptor>>, |
| + run_loop.QuitClosure(), &interceptor_proxy_->interceptor_)); |
| + run_loop.Run(); |
| +} |
| + |
| +void TestDownloadRequestHandler::StartServing(const Parameters& parameters) { |
| + TestDownloadRequestInterceptor::JobFactory job_factory = |
| + base::Bind(&PartialResponseJob::Factory, parameters); |
| + // Interceptor, if valid, is already registered and serving requests. We just |
| + // need to set the correct job factory for it to start serving using the new |
| + // parameters. |
| + BrowserThread::PostTask( |
| + BrowserThread::IO, FROM_HERE, |
| + base::Bind(&TestDownloadRequestInterceptor::SetJobFactory, |
| + interceptor_proxy_->interceptor_, job_factory)); |
| +} |
| + |
| +void TestDownloadRequestHandler::StartServingStaticResponse( |
| + const base::StringPiece& headers) { |
| + Parameters parameters; |
| + parameters.on_start_handler = base::Bind( |
| + &RespondToOnStartedCallbackWithStaticHeaders, headers.as_string()); |
| + StartServing(parameters); |
| +} |
| + |
| +// static |
| +void TestDownloadRequestHandler::GetPatternBytes(int seed, |
| + int64_t starting_offset, |
| + int length, |
| + char* buffer) { |
| + int64_t seed_offset = starting_offset / sizeof(int64_t); |
| + int64_t first_byte_position = starting_offset % sizeof(int64_t); |
| + while (length > 0) { |
| + uint64_t data = XorShift64StarWithIndex(seed, seed_offset); |
| + int length_to_copy = |
| + std::min(length, static_cast<int>(sizeof(data) - first_byte_position)); |
| + memcpy(buffer, reinterpret_cast<char*>(&data) + first_byte_position, |
| + length_to_copy); |
| + buffer += length_to_copy; |
| + length -= length_to_copy; |
| + ++seed_offset; |
| + first_byte_position = 0; |
| + } |
| +} |
| + |
| +TestDownloadRequestHandler::~TestDownloadRequestHandler() { |
| + BrowserThread::PostTask( |
| + BrowserThread::IO, FROM_HERE, |
| + base::Bind(&TestDownloadRequestInterceptor::Unregister, |
| + interceptor_proxy_->interceptor_)); |
| +} |
| + |
| +void TestDownloadRequestHandler::GetCompletedRequestInfo( |
| + TestDownloadRequestHandler::CompletedRequests* requests) { |
| + base::RunLoop run_loop; |
| + BrowserThread::PostTaskAndReply( |
| + BrowserThread::IO, FROM_HERE, |
| + base::Bind(&TestDownloadRequestInterceptor::GetAndResetCompletedRequests, |
| + interceptor_proxy_->interceptor_, requests), |
| + run_loop.QuitClosure()); |
| + run_loop.Run(); |
| +} |
| + |
| +} // namespace content |