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..1c89e0e20674fb7aea3ad702f046c0bf64cdee51 |
--- /dev/null |
+++ b/content/public/test/test_download_request_handler.cc |
@@ -0,0 +1,671 @@ |
+// 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/sequenced_task_runner.h" |
+#include "base/strings/stringprintf.h" |
+#include "base/threading/sequenced_task_runner_handle.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 { |
+ |
+// Intercepts URLRequests on behalf of TestDownloadRequestHandler. Necessarily |
+// lives on the IO thread since that's where net::URLRequestFilter invokes the |
+// URLRequestInterceptor. |
+class TestDownloadRequestHandler::Interceptor |
+ : 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<Interceptor> Register( |
+ const GURL& url, |
+ scoped_refptr<base::SequencedTaskRunner> client_task_runner); |
+ |
+ using JobFactory = |
+ base::Callback<net::URLRequestJob*(net::URLRequest*, |
+ net::NetworkDelegate*, |
+ base::WeakPtr<Interceptor>)>; |
+ |
+ ~Interceptor() 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); |
+ |
+ // Returns the task runner that should be used for invoking any client |
+ // supplied callbacks. |
+ scoped_refptr<base::SequencedTaskRunner> GetClientTaskRunner(); |
+ |
+ private: |
+ Interceptor(const GURL& url, |
+ scoped_refptr<base::SequencedTaskRunner> client_task_runner); |
+ |
+ // net::URLRequestInterceptor |
+ net::URLRequestJob* MaybeInterceptRequest( |
+ net::URLRequest* request, |
+ net::NetworkDelegate* network_delegate) const override; |
+ |
+ TestDownloadRequestHandler::CompletedRequests completed_requests_; |
+ GURL url_; |
+ JobFactory job_factory_; |
+ scoped_refptr<base::SequencedTaskRunner> client_task_runner_; |
+ |
+ // mutable because MaybeInterceptRequest() is inexplicably const. |
+ mutable base::WeakPtrFactory<Interceptor> weak_ptr_factory_; |
+ DISALLOW_COPY_AND_ASSIGN(Interceptor); |
+}; |
+ |
+// 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 TestDownloadRequestHandler::PartialResponseJob |
+ : public net::URLRequestJob { |
+ public: |
+ static net::URLRequestJob* Factory(const Parameters& parameters, |
+ net::URLRequest* request, |
+ net::NetworkDelegate* delegate, |
+ base::WeakPtr<Interceptor> 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; |
+ int ReadRawData(net::IOBuffer* buf, int buf_size) override; |
+ |
+ private: |
+ PartialResponseJob(scoped_ptr<Parameters> parameters, |
+ base::WeakPtr<Interceptor> interceptor, |
+ net::URLRequest* url_request, |
+ net::NetworkDelegate* network_delegate); |
+ |
+ ~PartialResponseJob() override; |
+ void ReportCompletedRequest(int64_t transferred_byte_count); |
+ 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 and hence may complete synchronously, it schedules the |
+ // NotifyHeadersComplete() call asynchronously in order to avoid unexpected |
+ // re-entrancy. |
+ void NotifyHeadersCompleteAndPrepareToRead(); |
+ |
+ scoped_ptr<Parameters> parameters_; |
+ |
+ base::WeakPtr<Interceptor> 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); |
+}; |
+ |
+namespace { |
+ |
+template <class T> |
+void StoreValueAndInvokeClosure(const base::Closure& closure, |
+ T* value_receiver, |
+ T value) { |
+ *value_receiver = value; |
+ closure.Run(); |
+} |
+ |
+// Xorshift* PRNG from https://en.wikipedia.org/wiki/Xorshift |
+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; |
+} |
+ |
+} // namespace |
+ |
+// static |
+net::URLRequestJob* TestDownloadRequestHandler::PartialResponseJob::Factory( |
+ const Parameters& parameters, |
+ net::URLRequest* request, |
+ net::NetworkDelegate* delegate, |
+ base::WeakPtr<Interceptor> interceptor) { |
+ return new PartialResponseJob(make_scoped_ptr(new Parameters(parameters)), |
+ interceptor, request, delegate); |
+} |
+ |
+TestDownloadRequestHandler::PartialResponseJob::PartialResponseJob( |
+ scoped_ptr<Parameters> parameters, |
+ base::WeakPtr<Interceptor> 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); |
+} |
+ |
+TestDownloadRequestHandler::PartialResponseJob::~PartialResponseJob() {} |
+ |
+void TestDownloadRequestHandler::PartialResponseJob::Start() { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ DVLOG(1) << "Starting request for " << request()->url().spec(); |
+ |
+ if (parameters_->on_start_handler.is_null() || !interceptor_.get()) { |
+ HandleOnStartDefault(); |
+ return; |
+ } |
+ |
+ DVLOG(1) << "Invoking custom OnStart handler."; |
+ interceptor_->GetClientTaskRunner()->PostTask( |
+ FROM_HERE, |
+ base::Bind( |
+ parameters_->on_start_handler, request()->extra_request_headers(), |
+ base::Bind(&PartialResponseJob:: |
+ OnStartResponseCallbackOnPossiblyIncorrectThread, |
+ weak_factory_.GetWeakPtr()))); |
+} |
+ |
+void TestDownloadRequestHandler::PartialResponseJob::GetResponseInfo( |
+ net::HttpResponseInfo* response_info) { |
+ *response_info = response_info_; |
+} |
+ |
+int64 TestDownloadRequestHandler::PartialResponseJob::GetTotalReceivedBytes() |
+ const { |
+ return offset_of_next_read_ - requested_range_begin_; |
+} |
+ |
+bool TestDownloadRequestHandler::PartialResponseJob::GetMimeType( |
+ std::string* mime_type) const { |
+ *mime_type = parameters_->content_type; |
+ return !parameters_->content_type.empty(); |
+} |
+ |
+int TestDownloadRequestHandler::PartialResponseJob::GetResponseCode() const { |
+ return response_info_.headers.get() ? response_info_.headers->response_code() |
+ : 0; |
+} |
+ |
+int TestDownloadRequestHandler::PartialResponseJob::ReadRawData( |
+ net::IOBuffer* buf, |
+ int buf_size) { |
+ 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 0; |
+ } |
+ |
+ 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 error; |
+ } |
+ |
+ 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; |
+ return bytes_to_copy; |
+} |
+ |
+void TestDownloadRequestHandler::PartialResponseJob::ReportCompletedRequest( |
+ int64_t transferred_byte_count) { |
+ if (interceptor_.get()) { |
+ TestDownloadRequestHandler::CompletedRequest completed_request; |
+ completed_request.transferred_byte_count = transferred_byte_count; |
+ completed_request.request_headers = request()->extra_request_headers(); |
+ interceptor_->AddCompletedRequest(completed_request); |
+ } |
+} |
+ |
+// static |
+void TestDownloadRequestHandler::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 TestDownloadRequestHandler::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 TestDownloadRequestHandler::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 TestDownloadRequestHandler::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 TestDownloadRequestHandler::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 TestDownloadRequestHandler::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<TestDownloadRequestHandler::Interceptor> |
+TestDownloadRequestHandler::Interceptor::Register( |
+ const GURL& url, |
+ scoped_refptr<base::SequencedTaskRunner> client_task_runner) { |
+ DCHECK(url.is_valid()); |
+ scoped_ptr<Interceptor> interceptor(new Interceptor(url, client_task_runner)); |
+ base::WeakPtr<Interceptor> weak_reference = |
+ interceptor->weak_ptr_factory_.GetWeakPtr(); |
+ net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); |
+ filter->AddUrlInterceptor(url, interceptor.Pass()); |
+ return weak_reference; |
+} |
+ |
+void TestDownloadRequestHandler::Interceptor::Unregister() { |
+ net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); |
+ filter->RemoveUrlHandler(url_); |
+ // We are deleted now since the filter owned |this|. |
+} |
+ |
+void TestDownloadRequestHandler::Interceptor::SetJobFactory( |
+ const JobFactory& job_factory) { |
+ job_factory_ = job_factory; |
+} |
+ |
+void TestDownloadRequestHandler::Interceptor::GetAndResetCompletedRequests( |
+ TestDownloadRequestHandler::CompletedRequests* requests) { |
+ requests->clear(); |
+ completed_requests_.swap(*requests); |
+} |
+ |
+void TestDownloadRequestHandler::Interceptor::AddCompletedRequest( |
+ const TestDownloadRequestHandler::CompletedRequest& request) { |
+ completed_requests_.push_back(request); |
+} |
+ |
+scoped_refptr<base::SequencedTaskRunner> |
+TestDownloadRequestHandler::Interceptor::GetClientTaskRunner() { |
+ return client_task_runner_; |
+} |
+ |
+TestDownloadRequestHandler::Interceptor::Interceptor( |
+ const GURL& url, |
+ scoped_refptr<base::SequencedTaskRunner> client_task_runner) |
+ : url_(url), |
+ client_task_runner_(client_task_runner), |
+ weak_ptr_factory_(this) {} |
+ |
+TestDownloadRequestHandler::Interceptor::~Interceptor() {} |
+ |
+net::URLRequestJob* |
+TestDownloadRequestHandler::Interceptor::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()); |
+} |
+ |
+TestDownloadRequestHandler::InjectedError::InjectedError(int64_t offset, |
+ net::Error error) |
+ : offset(offset), error(error) {} |
+ |
+// static |
+TestDownloadRequestHandler::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) {} |
+ |
+// Copy and move constructors / assignment operators are all defaults. |
+TestDownloadRequestHandler::Parameters::Parameters(const Parameters&) = default; |
+TestDownloadRequestHandler::Parameters& TestDownloadRequestHandler::Parameters:: |
+operator=(const Parameters&) = default; |
+ |
+TestDownloadRequestHandler::Parameters::Parameters(Parameters&& that) |
+ : etag(std::move(that.etag)), |
+ last_modified(std::move(that.last_modified)), |
+ content_type(std::move(that.content_type)), |
+ size(that.size), |
+ pattern_generator_seed(that.pattern_generator_seed), |
+ on_start_handler(that.on_start_handler), |
+ injected_errors(std::move(that.injected_errors)) {} |
+ |
+TestDownloadRequestHandler::Parameters& TestDownloadRequestHandler::Parameters:: |
+operator=(Parameters&& that) { |
+ etag = std::move(that.etag); |
+ last_modified = std::move(that.etag); |
+ content_type = std::move(that.content_type); |
+ size = that.size; |
+ pattern_generator_seed = that.pattern_generator_seed; |
+ on_start_handler = that.on_start_handler; |
+ injected_errors.swap(that.injected_errors); |
+ return *this; |
+} |
+ |
+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) { |
+ DCHECK(base::SequencedTaskRunnerHandle::IsSet()); |
+ base::RunLoop run_loop; |
+ BrowserThread::PostTaskAndReplyWithResult( |
+ BrowserThread::IO, FROM_HERE, |
+ base::Bind(&Interceptor::Register, url_, |
+ base::SequencedTaskRunnerHandle::Get()), |
+ base::Bind(&StoreValueAndInvokeClosure<base::WeakPtr<Interceptor>>, |
+ run_loop.QuitClosure(), &interceptor_)); |
+ run_loop.Run(); |
+} |
+ |
+void TestDownloadRequestHandler::StartServing(const Parameters& parameters) { |
+ DCHECK(CalledOnValidThread()); |
+ Interceptor::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(&Interceptor::SetJobFactory, interceptor_, job_factory)); |
+} |
+ |
+void TestDownloadRequestHandler::StartServingStaticResponse( |
+ const base::StringPiece& headers) { |
+ DCHECK(CalledOnValidThread()); |
+ 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() { |
+ DCHECK(CalledOnValidThread()); |
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
+ base::Bind(&Interceptor::Unregister, interceptor_)); |
+} |
+ |
+void TestDownloadRequestHandler::GetCompletedRequestInfo( |
+ TestDownloadRequestHandler::CompletedRequests* requests) { |
+ DCHECK(CalledOnValidThread()); |
+ base::RunLoop run_loop; |
+ BrowserThread::PostTaskAndReply( |
+ BrowserThread::IO, FROM_HERE, |
+ base::Bind(&Interceptor::GetAndResetCompletedRequests, interceptor_, |
+ requests), |
+ run_loop.QuitClosure()); |
+ run_loop.Run(); |
+} |
+ |
+} // namespace content |