| // 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 <algorithm> | 
 | #include <list> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/compiler_specific.h" | 
 | #include "base/lazy_instance.h" | 
 | #include "base/message_loop/message_loop.h" | 
 | #include "base/strings/string_util.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<URLRequestTestJob*> URLRequestJobList; | 
 | base::LazyInstance<URLRequestJobList>::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("<html><title>Test One</title></html>"); | 
 | } | 
 | std::string URLRequestTestJob::test_data_2() { | 
 |   return std::string("<html><title>Test Two Two</title></html>"); | 
 | } | 
 | std::string URLRequestTestJob::test_data_3() { | 
 |   return std::string("<html><title>Test Three Three Three</title></html>"); | 
 | } | 
 | std::string URLRequestTestJob::test_data_4() { | 
 |   return std::string("<html><title>Test Four Four Four Four</title></html>"); | 
 | } | 
 |  | 
 | // 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 | 
 | URLRequestJobFactory::ProtocolHandler* | 
 | URLRequestTestJob::CreateProtocolHandler() { | 
 |   return 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::MessageLoop::current()->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<int>(response_data_.length())) { | 
 |     return true;  // done reading | 
 |   } | 
 |  | 
 |   int to_read = buf_size; | 
 |   if (to_read + offset_ > static_cast<int>(response_data_.length())) | 
 |     to_read = static_cast<int>(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::MessageLoop::current()->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 |