// Copyright (c) 2012 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 "net/url_request/url_request_test_job.h" #include #include #include "base/bind.h" #include "base/compiler_specific.h" #include "base/lazy_instance.h" #include "base/location.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_util.h" #include "base/thread_task_runner_handle.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/http/http_response_headers.h" namespace net { namespace { typedef std::list URLRequestJobList; base::LazyInstance::Leaky g_pending_jobs = LAZY_INSTANCE_INITIALIZER; class TestJobProtocolHandler : public URLRequestJobFactory::ProtocolHandler { public: // URLRequestJobFactory::ProtocolHandler implementation: URLRequestJob* MaybeCreateJob( URLRequest* request, NetworkDelegate* network_delegate) const override { return new URLRequestTestJob(request, network_delegate); } }; } // namespace // static getters for known URLs GURL URLRequestTestJob::test_url_1() { return GURL("test:url1"); } GURL URLRequestTestJob::test_url_2() { return GURL("test:url2"); } GURL URLRequestTestJob::test_url_3() { return GURL("test:url3"); } GURL URLRequestTestJob::test_url_4() { return GURL("test:url4"); } GURL URLRequestTestJob::test_url_error() { return GURL("test:error"); } GURL URLRequestTestJob::test_url_redirect_to_url_2() { return GURL("test:redirect_to_2"); } // static getters for known URL responses std::string URLRequestTestJob::test_data_1() { return std::string("Test One"); } std::string URLRequestTestJob::test_data_2() { return std::string("Test Two Two"); } std::string URLRequestTestJob::test_data_3() { return std::string("Test Three Three Three"); } std::string URLRequestTestJob::test_data_4() { return std::string("Test Four Four Four Four"); } // static getter for simple response headers std::string URLRequestTestJob::test_headers() { static const char kHeaders[] = "HTTP/1.1 200 OK\0" "Content-type: text/html\0" "\0"; return std::string(kHeaders, arraysize(kHeaders)); } // static getter for redirect response headers std::string URLRequestTestJob::test_redirect_headers() { static const char kHeaders[] = "HTTP/1.1 302 MOVED\0" "Location: somewhere\0" "\0"; return std::string(kHeaders, arraysize(kHeaders)); } // static getter for redirect response headers std::string URLRequestTestJob::test_redirect_to_url_2_headers() { std::string headers = "HTTP/1.1 302 MOVED"; headers.push_back('\0'); headers += "Location: "; headers += test_url_2().spec(); headers.push_back('\0'); headers.push_back('\0'); return headers; } // static getter for error response headers std::string URLRequestTestJob::test_error_headers() { static const char kHeaders[] = "HTTP/1.1 500 BOO HOO\0" "\0"; return std::string(kHeaders, arraysize(kHeaders)); } // static scoped_ptr URLRequestTestJob::CreateProtocolHandler() { return make_scoped_ptr(new TestJobProtocolHandler()); } URLRequestTestJob::URLRequestTestJob(URLRequest* request, NetworkDelegate* network_delegate) : URLRequestJob(request, network_delegate), auto_advance_(false), stage_(WAITING), priority_(DEFAULT_PRIORITY), offset_(0), async_buf_(NULL), async_buf_size_(0), weak_factory_(this) { } URLRequestTestJob::URLRequestTestJob(URLRequest* request, NetworkDelegate* network_delegate, bool auto_advance) : URLRequestJob(request, network_delegate), auto_advance_(auto_advance), stage_(WAITING), priority_(DEFAULT_PRIORITY), offset_(0), async_buf_(NULL), async_buf_size_(0), weak_factory_(this) { } URLRequestTestJob::URLRequestTestJob(URLRequest* request, NetworkDelegate* network_delegate, const std::string& response_headers, const std::string& response_data, bool auto_advance) : URLRequestJob(request, network_delegate), auto_advance_(auto_advance), stage_(WAITING), priority_(DEFAULT_PRIORITY), response_headers_(new HttpResponseHeaders(response_headers)), response_data_(response_data), offset_(0), async_buf_(NULL), async_buf_size_(0), weak_factory_(this) { } URLRequestTestJob::~URLRequestTestJob() { g_pending_jobs.Get().erase( std::remove( g_pending_jobs.Get().begin(), g_pending_jobs.Get().end(), this), g_pending_jobs.Get().end()); } bool URLRequestTestJob::GetMimeType(std::string* mime_type) const { DCHECK(mime_type); if (!response_headers_.get()) return false; return response_headers_->GetMimeType(mime_type); } void URLRequestTestJob::SetPriority(RequestPriority priority) { priority_ = priority; } void URLRequestTestJob::Start() { // Start reading asynchronously so that all error reporting and data // callbacks happen as they would for network requests. base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&URLRequestTestJob::StartAsync, weak_factory_.GetWeakPtr())); } void URLRequestTestJob::StartAsync() { if (!response_headers_.get()) { response_headers_ = new HttpResponseHeaders(test_headers()); if (request_->url().spec() == test_url_1().spec()) { response_data_ = test_data_1(); stage_ = DATA_AVAILABLE; // Simulate a synchronous response for this one. } else if (request_->url().spec() == test_url_2().spec()) { response_data_ = test_data_2(); } else if (request_->url().spec() == test_url_3().spec()) { response_data_ = test_data_3(); } else if (request_->url().spec() == test_url_4().spec()) { response_data_ = test_data_4(); } else if (request_->url().spec() == test_url_redirect_to_url_2().spec()) { response_headers_ = new HttpResponseHeaders(test_redirect_to_url_2_headers()); } else { AdvanceJob(); // unexpected url, return error // FIXME(brettw) we may want to use WININET errors or have some more types // of errors NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, ERR_INVALID_URL)); // FIXME(brettw): this should emulate a network error, and not just fail // initiating a connection return; } } AdvanceJob(); this->NotifyHeadersComplete(); } bool URLRequestTestJob::ReadRawData(IOBuffer* buf, int buf_size, int *bytes_read) { if (stage_ == WAITING) { async_buf_ = buf; async_buf_size_ = buf_size; SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); return false; } DCHECK(bytes_read); *bytes_read = 0; if (offset_ >= static_cast(response_data_.length())) { return true; // done reading } int to_read = buf_size; if (to_read + offset_ > static_cast(response_data_.length())) to_read = static_cast(response_data_.length()) - offset_; memcpy(buf->data(), &response_data_.c_str()[offset_], to_read); offset_ += to_read; *bytes_read = to_read; return true; } void URLRequestTestJob::GetResponseInfo(HttpResponseInfo* info) { if (response_headers_.get()) info->headers = response_headers_; } void URLRequestTestJob::GetLoadTimingInfo( LoadTimingInfo* load_timing_info) const { // Preserve the times the URLRequest is responsible for, but overwrite all // the others. base::TimeTicks request_start = load_timing_info->request_start; base::Time request_start_time = load_timing_info->request_start_time; *load_timing_info = load_timing_info_; load_timing_info->request_start = request_start; load_timing_info->request_start_time = request_start_time; } int URLRequestTestJob::GetResponseCode() const { if (response_headers_.get()) return response_headers_->response_code(); return -1; } bool URLRequestTestJob::IsRedirectResponse(GURL* location, int* http_status_code) { if (!response_headers_.get()) return false; std::string value; if (!response_headers_->IsRedirect(&value)) return false; *location = request_->url().Resolve(value); *http_status_code = response_headers_->response_code(); return true; } void URLRequestTestJob::Kill() { stage_ = DONE; URLRequestJob::Kill(); weak_factory_.InvalidateWeakPtrs(); g_pending_jobs.Get().erase( std::remove( g_pending_jobs.Get().begin(), g_pending_jobs.Get().end(), this), g_pending_jobs.Get().end()); } void URLRequestTestJob::ProcessNextOperation() { switch (stage_) { case WAITING: // Must call AdvanceJob() prior to NotifyReadComplete() since that may // delete |this|. AdvanceJob(); stage_ = DATA_AVAILABLE; // OK if ReadRawData wasn't called yet. if (async_buf_) { int bytes_read; if (!ReadRawData(async_buf_, async_buf_size_, &bytes_read)) NOTREACHED() << "This should not return false in DATA_AVAILABLE."; SetStatus(URLRequestStatus()); // clear the io pending flag if (NextReadAsync()) { // Make all future reads return io pending until the next // ProcessNextOperation(). stage_ = WAITING; } NotifyReadComplete(bytes_read); } break; case DATA_AVAILABLE: AdvanceJob(); stage_ = ALL_DATA; // done sending data break; case ALL_DATA: stage_ = DONE; return; case DONE: return; default: NOTREACHED() << "Invalid stage"; return; } } bool URLRequestTestJob::NextReadAsync() { return false; } void URLRequestTestJob::AdvanceJob() { if (auto_advance_) { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&URLRequestTestJob::ProcessNextOperation, weak_factory_.GetWeakPtr())); return; } g_pending_jobs.Get().push_back(this); } // static bool URLRequestTestJob::ProcessOnePendingMessage() { if (g_pending_jobs.Get().empty()) return false; URLRequestTestJob* next_job(g_pending_jobs.Get().front()); g_pending_jobs.Get().pop_front(); DCHECK(!next_job->auto_advance()); // auto_advance jobs should be in this q next_job->ProcessNextOperation(); return true; } } // namespace net