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..7a7eb0516d97c5ec054cf1565cf08850c8d61c2d |
--- /dev/null |
+++ b/content/public/test/test_download_request_handler.cc |
@@ -0,0 +1,493 @@ |
+// 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 "base/logging.h" |
+#include "base/memory/weak_ptr.h" |
+#include "base/run_loop.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; |
+ |
+class Interceptor : public net::URLRequestInterceptor { |
+ public: |
+ static base::WeakPtr<Interceptor> Register(const GURL& url); |
+ |
+ using JobFactory = |
+ base::Callback<net::URLRequestJob*(net::URLRequest*, |
+ net::NetworkDelegate*, |
+ base::WeakPtr<Interceptor>)>; |
+ |
+ ~Interceptor() override; |
+ void Unregister(); |
+ void SetJobFactory(const JobFactory& factory); |
+ void GetAndResetCompletedRequests( |
+ TestDownloadRequestHandler::CompletedRequests* requests); |
+ void AddCompletedRequest( |
+ const TestDownloadRequestHandler::CompletedRequest& request_info); |
+ |
+ private: |
+ Interceptor(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 base::WeakPtrFactory<Interceptor> weak_ptr_factory_; |
+ DISALLOW_COPY_AND_ASSIGN(Interceptor); |
+}; |
+ |
+class 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; |
+ bool ReadRawData(net::IOBuffer* buf, int buf_size, int* bytes_read) 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_content_length); |
+ |
+ scoped_ptr<Parameters> parameters_; |
+ |
+ base::WeakPtr<Interceptor> interceptor_; |
+ net::HttpResponseInfo response_info_; |
+ int64_t offset_ = -1; |
+ int64_t offset_begin_ = -1; |
+ int64_t offset_end_ = -1; |
+ base::WeakPtrFactory<PartialResponseJob> weak_factory_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(PartialResponseJob); |
+}; |
+ |
+class StaticResponseJob : public net::URLRequestJob { |
+ public: |
+ static net::URLRequestJob* Factory(const std::string& headers, |
+ net::URLRequest* request, |
+ net::NetworkDelegate* delegate, |
+ base::WeakPtr<Interceptor> interceptor); |
+ // URLRequestJob |
+ void Start() override; |
+ void GetResponseInfo(net::HttpResponseInfo* response_info) override; |
+ |
+ private: |
+ StaticResponseJob(const std::string& headers, |
+ net::URLRequest* url_request, |
+ net::NetworkDelegate* network_delegate); |
+ ~StaticResponseJob() override; |
+ |
+ net::HttpResponseInfo response_info_; |
+ base::WeakPtrFactory<StaticResponseJob> weak_factory_; |
+ DISALLOW_COPY_AND_ASSIGN(StaticResponseJob); |
+}; |
+ |
+// static |
+net::URLRequestJob* 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); |
+} |
+ |
+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); |
+} |
+ |
+PartialResponseJob::~PartialResponseJob() {} |
+ |
+void PartialResponseJob::Start() { |
+ DVLOG(1) << "Starting request for " << request()->url().spec(); |
+ SetStatus(net::URLRequestStatus()); |
+ std::stringstream response_headers; |
+ |
+ const net::HttpRequestHeaders extra_headers = |
+ request()->extra_request_headers(); |
+ |
+ DCHECK(request()->method() == "GET") << "PartialResponseJob only " |
+ "knows how to respond to GET " |
+ "requests"; |
+ offset_begin_ = 0; |
+ offset_end_ = parameters_->size - 1; |
+ std::string value; |
+ std::string range_header; |
+ std::vector<net::HttpByteRange> byte_ranges; |
+ |
+ if (parameters_->support_byte_ranges && |
+ extra_headers.GetHeader(net::HttpRequestHeaders::kIfRange, &value) && |
+ !value.empty() && value == parameters_->etag && |
+ extra_headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header) && |
+ net::HttpUtil::ParseRangeHeader(range_header, &byte_ranges) && |
+ byte_ranges.size() > 0) { |
+ if (byte_ranges[0].ComputeBounds(parameters_->size)) { |
+ offset_begin_ = byte_ranges[0].first_byte_position(); |
+ offset_end_ = byte_ranges[0].last_byte_position(); |
+ response_headers << "HTTP/1.1 206 Partial content\r\n"; |
+ response_headers << "Content-Range: bytes " << offset_begin_ << "-" |
+ << offset_end_ << "/" << parameters_->size << "\r\n"; |
+ response_headers << "Content-Length: " << offset_end_ - offset_begin_ + 1 |
+ << "\r\n"; |
+ } else { |
+ response_headers << "HTTP/1.1 416 Range not satisfiable\r\n"; |
+ response_headers << "Content-Range: bytes */" << parameters_->size |
+ << "\r\n"; |
+ } |
+ } else { |
+ response_headers << "HTTP/1.1 200 Success\r\n"; |
+ response_headers << "Content-Length: " << parameters_->size << "\r\n"; |
+ } |
+ if (!parameters_->content_type.empty()) |
+ response_headers << "Content-Type: " << parameters_->content_type << "\r\n"; |
+ |
+ if (parameters_->support_byte_ranges) |
+ response_headers << "Accept-Ranges: bytes\r\n"; |
+ |
+ if (!parameters_->etag.empty()) |
+ response_headers << "ETag: " << parameters_->etag << "\r\n"; |
+ |
+ if (!parameters_->last_modified.empty()) |
+ response_headers << "Last-Modified: " << parameters_->last_modified |
+ << "\r\n"; |
+ |
+ std::string raw_headers = response_headers.str(); |
+ response_info_.headers = |
+ new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders( |
+ raw_headers.c_str(), raw_headers.size())); |
+ offset_ = offset_begin_; |
+ |
+ while (!parameters_->injected_errors.empty() && |
+ parameters_->injected_errors.front().offset <= offset_) |
+ parameters_->injected_errors.pop(); |
+ |
+ DVLOG(1) << "Notifying response headers complete with headers:\n" |
+ << raw_headers; |
+ base::MessageLoop::current()->PostTask( |
+ FROM_HERE, base::Bind(&PartialResponseJob::NotifyHeadersComplete, |
+ weak_factory_.GetWeakPtr())); |
+} |
+ |
+void PartialResponseJob::GetResponseInfo(net::HttpResponseInfo* response_info) { |
+ *response_info = response_info_; |
+} |
+ |
+int64 PartialResponseJob::GetTotalReceivedBytes() const { |
+ return offset_ - offset_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) << "Reading " << buf_size << " bytes"; |
+ if (offset_ > offset_end_) { |
+ ReportCompletedRequest(offset_end_ - offset_begin_ + 1); |
+ DVLOG(1) << "Done reading."; |
+ return true; |
+ } |
+ |
+ int64_t range_end = std::min(offset_end_, offset_ + buf_size - 1); |
+ if (!parameters_->injected_errors.empty()) { |
+ const InjectedError& injected_error = parameters_->injected_errors.front(); |
+ if (offset_ == 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 - offset_begin_); |
+ parameters_->injected_errors.pop(); |
+ return false; |
+ } |
+ |
+ if (offset_ < injected_error.offset && injected_error.offset <= range_end) |
+ range_end = injected_error.offset - 1; |
+ } |
+ int bytes_to_copy = (range_end - offset_) + 1; |
+ |
+ TestDownloadRequestHandler::GetPatternBytes( |
+ parameters_->pattern_generator_seed, offset_, bytes_to_copy, buf->data()); |
+ DVLOG(1) << "Read " << bytes_to_copy << " bytes at offset " << offset_; |
+ offset_ += bytes_to_copy; |
+ *bytes_read = bytes_to_copy; |
+ return true; |
+} |
+ |
+void PartialResponseJob::ReportCompletedRequest( |
+ int64_t transferred_content_length) { |
+ if (interceptor_.get()) { |
+ TestDownloadRequestHandler::CompletedRequest request_info; |
+ request_info.transferred_content_length = transferred_content_length; |
+ request_info.request_headers = request()->extra_request_headers(); |
+ interceptor_->AddCompletedRequest(request_info); |
+ } |
+} |
+ |
+// static |
+net::URLRequestJob* StaticResponseJob::Factory( |
+ const std::string& headers, |
+ net::URLRequest* request, |
+ net::NetworkDelegate* delegate, |
+ base::WeakPtr<Interceptor> interceptor) { |
+ return new StaticResponseJob(headers, request, delegate); |
+} |
+ |
+StaticResponseJob::StaticResponseJob(const std::string& headers, |
+ net::URLRequest* url_request, |
+ net::NetworkDelegate* network_delegate) |
+ : URLRequestJob(url_request, network_delegate), weak_factory_(this) { |
+ response_info_.headers = new net::HttpResponseHeaders( |
+ net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.size())); |
+} |
+ |
+StaticResponseJob::~StaticResponseJob() {} |
+ |
+void StaticResponseJob::Start() { |
+ base::MessageLoop::current()->PostTask( |
+ FROM_HERE, base::Bind(&StaticResponseJob::NotifyHeadersComplete, |
+ weak_factory_.GetWeakPtr())); |
+ return; |
+} |
+ |
+void StaticResponseJob::GetResponseInfo(net::HttpResponseInfo* response_info) { |
+ *response_info = response_info_; |
+} |
+ |
+template <class T> |
+void StoreValueAndInvokeClosure(const base::Closure& quit_closure, |
+ T* value_receiver, |
+ T value) { |
+ *value_receiver = value; |
+ quit_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; |
+} |
+ |
+// static |
+base::WeakPtr<Interceptor> Interceptor::Register(const GURL& url) { |
+ DCHECK(url.is_valid()); |
+ scoped_ptr<Interceptor> interceptor(new Interceptor(url)); |
+ 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 Interceptor::Unregister() { |
+ net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); |
+ filter->RemoveUrlHandler(url_); |
+} |
+ |
+void Interceptor::SetJobFactory(const JobFactory& job_factory) { |
+ job_factory_ = job_factory; |
+} |
+ |
+void Interceptor::GetAndResetCompletedRequests( |
+ TestDownloadRequestHandler::CompletedRequests* requests) { |
+ requests->clear(); |
+ completed_requests_.swap(*requests); |
+} |
+ |
+void Interceptor::AddCompletedRequest( |
+ const TestDownloadRequestHandler::CompletedRequest& request_info) { |
+ completed_requests_.push_back(request_info); |
+} |
+ |
+Interceptor::Interceptor(const GURL& url) |
+ : url_(url), weak_ptr_factory_(this) {} |
+ |
+Interceptor::~Interceptor() {} |
+ |
+net::URLRequestJob* 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()); |
+} |
+ |
+} // namespace |
+ |
+struct TestDownloadRequestHandler::InterceptorProxy { |
+ base::WeakPtr<Interceptor> 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(const Parameters& other) |
+ : etag(other.etag), |
+ last_modified(other.last_modified), |
+ content_type(other.content_type), |
+ size(other.size), |
+ pattern_generator_seed(other.pattern_generator_seed), |
+ support_byte_ranges(other.support_byte_ranges), |
+ injected_errors(other.injected_errors) {} |
+ |
+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( |
+ const std::string& etag, |
+ const std::string& last_modified, |
+ const std::string& content_type, |
+ int64_t size, |
+ int pattern_generator_seed, |
+ bool support_byte_ranges) |
+ : etag(etag), |
+ last_modified(last_modified), |
+ content_type(content_type), |
+ size(size), |
+ pattern_generator_seed(pattern_generator_seed), |
+ support_byte_ranges(support_byte_ranges) {} |
+ |
+TestDownloadRequestHandler::Parameters::~Parameters() {} |
+ |
+void TestDownloadRequestHandler::Parameters::ClearInjectedErrors() { |
+ std::queue<InjectedError> empty_error_list; |
+ injected_errors.swap(empty_error_list); |
+} |
+ |
+TestDownloadRequestHandler::TestDownloadRequestHandler() |
+ : TestDownloadRequestHandler(GURL("http://example.com/download")) {} |
+ |
+TestDownloadRequestHandler::TestDownloadRequestHandler(const GURL& url) |
+ : url_(url) { |
+ interceptor_proxy_.reset(new InterceptorProxy); |
+ base::RunLoop run_loop; |
+ BrowserThread::PostTaskAndReplyWithResult( |
+ BrowserThread::IO, FROM_HERE, base::Bind(&Interceptor::Register, url_), |
+ base::Bind(&StoreValueAndInvokeClosure<base::WeakPtr<Interceptor>>, |
+ run_loop.QuitClosure(), &interceptor_proxy_->interceptor_)); |
+ run_loop.Run(); |
+} |
+ |
+void TestDownloadRequestHandler::StartServing(const Parameters& parameters) { |
+ Interceptor::JobFactory job_factory = |
+ base::Bind(&PartialResponseJob::Factory, parameters); |
+ BrowserThread::PostTask( |
+ BrowserThread::IO, FROM_HERE, |
+ base::Bind(&Interceptor::SetJobFactory, interceptor_proxy_->interceptor_, |
+ job_factory)); |
+} |
+ |
+void TestDownloadRequestHandler::StartServingStaticResponse( |
+ const base::StringPiece& headers) { |
+ Interceptor::JobFactory job_factory = |
+ base::Bind(&StaticResponseJob::Factory, headers.as_string()); |
+ BrowserThread::PostTask( |
+ BrowserThread::IO, FROM_HERE, |
+ base::Bind(&Interceptor::SetJobFactory, interceptor_proxy_->interceptor_, |
+ job_factory)); |
+} |
+ |
+// 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(&Interceptor::Unregister, interceptor_proxy_->interceptor_)); |
+} |
+ |
+void TestDownloadRequestHandler::GetCompletedRequestInfo( |
+ TestDownloadRequestHandler::CompletedRequests* requests) { |
+ base::RunLoop run_loop; |
+ BrowserThread::PostTaskAndReply( |
+ BrowserThread::IO, FROM_HERE, |
+ base::Bind(&Interceptor::GetAndResetCompletedRequests, |
+ interceptor_proxy_->interceptor_, requests), |
+ run_loop.QuitClosure()); |
+ run_loop.Run(); |
+} |
+ |
+} // namespace content |