1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "net/test/url_request/url_request_mock_http_job.h" 6 7#include "base/files/file_util.h" 8#include "base/message_loop/message_loop.h" 9#include "base/strings/string_util.h" 10#include "base/strings/utf_string_conversions.h" 11#include "base/task_runner_util.h" 12#include "base/threading/sequenced_worker_pool.h" 13#include "base/threading/thread_restrictions.h" 14#include "net/base/filename_util.h" 15#include "net/http/http_response_headers.h" 16#include "net/url_request/url_request_filter.h" 17#include "net/url_request/url_request_interceptor.h" 18 19const char kMockHostname[] = "mock.http"; 20const base::FilePath::CharType kMockHeaderFileSuffix[] = 21 FILE_PATH_LITERAL(".mock-http-headers"); 22 23namespace net { 24 25namespace { 26 27class MockJobInterceptor : public net::URLRequestInterceptor { 28 public: 29 // When |map_all_requests_to_base_path| is true, all request should return the 30 // contents of the file at |base_path|. When |map_all_requests_to_base_path| 31 // is false, |base_path| is the file path leading to the root of the directory 32 // to use as the root of the HTTP server. 33 MockJobInterceptor( 34 const base::FilePath& base_path, 35 bool map_all_requests_to_base_path, 36 const scoped_refptr<base::SequencedWorkerPool>& worker_pool) 37 : base_path_(base_path), 38 map_all_requests_to_base_path_(map_all_requests_to_base_path), 39 worker_pool_(worker_pool) {} 40 virtual ~MockJobInterceptor() {} 41 42 // net::URLRequestJobFactory::ProtocolHandler implementation 43 virtual net::URLRequestJob* MaybeInterceptRequest( 44 net::URLRequest* request, 45 net::NetworkDelegate* network_delegate) const OVERRIDE { 46 return new URLRequestMockHTTPJob( 47 request, 48 network_delegate, 49 map_all_requests_to_base_path_ ? base_path_ : GetOnDiskPath(request), 50 worker_pool_->GetTaskRunnerWithShutdownBehavior( 51 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)); 52 } 53 54 private: 55 base::FilePath GetOnDiskPath(net::URLRequest* request) const { 56 // Conceptually we just want to "return base_path_ + request->url().path()". 57 // But path in the request URL is in URL space (i.e. %-encoded spaces). 58 // So first we convert base FilePath to a URL, then append the URL 59 // path to that, and convert the final URL back to a FilePath. 60 GURL file_url(net::FilePathToFileURL(base_path_)); 61 std::string url = file_url.spec() + request->url().path(); 62 base::FilePath file_path; 63 net::FileURLToFilePath(GURL(url), &file_path); 64 return file_path; 65 } 66 67 const base::FilePath base_path_; 68 const bool map_all_requests_to_base_path_; 69 const scoped_refptr<base::SequencedWorkerPool> worker_pool_; 70 71 DISALLOW_COPY_AND_ASSIGN(MockJobInterceptor); 72}; 73 74std::string DoFileIO(const base::FilePath& file_path) { 75 base::FilePath header_file = 76 base::FilePath(file_path.value() + kMockHeaderFileSuffix); 77 78 if (!base::PathExists(header_file)) { 79 // If there is no mock-http-headers file, fake a 200 OK. 80 return "HTTP/1.0 200 OK\n"; 81 } 82 83 std::string raw_headers; 84 base::ReadFileToString(header_file, &raw_headers); 85 return raw_headers; 86} 87 88} // namespace 89 90// static 91void URLRequestMockHTTPJob::AddUrlHandler( 92 const base::FilePath& base_path, 93 const scoped_refptr<base::SequencedWorkerPool>& worker_pool) { 94 // Add kMockHostname to net::URLRequestFilter. 95 net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); 96 filter->AddHostnameInterceptor( 97 "http", kMockHostname, CreateInterceptor(base_path, worker_pool)); 98} 99 100// static 101void URLRequestMockHTTPJob::AddHostnameToFileHandler( 102 const std::string& hostname, 103 const base::FilePath& file, 104 const scoped_refptr<base::SequencedWorkerPool>& worker_pool) { 105 net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); 106 filter->AddHostnameInterceptor( 107 "http", hostname, CreateInterceptorForSingleFile(file, worker_pool)); 108} 109 110// static 111GURL URLRequestMockHTTPJob::GetMockUrl(const base::FilePath& path) { 112 std::string url = "http://"; 113 url.append(kMockHostname); 114 url.append("/"); 115 std::string path_str = path.MaybeAsASCII(); 116 DCHECK(!path_str.empty()); // We only expect ASCII paths in tests. 117 url.append(path_str); 118 return GURL(url); 119} 120 121// static 122scoped_ptr<net::URLRequestInterceptor> URLRequestMockHTTPJob::CreateInterceptor( 123 const base::FilePath& base_path, 124 const scoped_refptr<base::SequencedWorkerPool>& worker_pool) { 125 return scoped_ptr<net::URLRequestInterceptor>( 126 new MockJobInterceptor(base_path, false, worker_pool)); 127} 128 129// static 130scoped_ptr<net::URLRequestInterceptor> 131URLRequestMockHTTPJob::CreateInterceptorForSingleFile( 132 const base::FilePath& file, 133 const scoped_refptr<base::SequencedWorkerPool>& worker_pool) { 134 return scoped_ptr<net::URLRequestInterceptor>( 135 new MockJobInterceptor(file, true, worker_pool)); 136} 137 138URLRequestMockHTTPJob::URLRequestMockHTTPJob( 139 net::URLRequest* request, 140 net::NetworkDelegate* network_delegate, 141 const base::FilePath& file_path, 142 const scoped_refptr<base::TaskRunner>& task_runner) 143 : net::URLRequestFileJob(request, network_delegate, file_path, task_runner), 144 task_runner_(task_runner), 145 weak_ptr_factory_(this) { 146} 147 148URLRequestMockHTTPJob::~URLRequestMockHTTPJob() { 149} 150 151// Public virtual version. 152void URLRequestMockHTTPJob::GetResponseInfo(net::HttpResponseInfo* info) { 153 // Forward to private const version. 154 GetResponseInfoConst(info); 155} 156 157bool URLRequestMockHTTPJob::IsRedirectResponse(GURL* location, 158 int* http_status_code) { 159 // Override the net::URLRequestFileJob implementation to invoke the default 160 // one based on HttpResponseInfo. 161 return net::URLRequestJob::IsRedirectResponse(location, http_status_code); 162} 163 164// Public virtual version. 165void URLRequestMockHTTPJob::Start() { 166 base::PostTaskAndReplyWithResult( 167 task_runner_.get(), 168 FROM_HERE, 169 base::Bind(&DoFileIO, file_path_), 170 base::Bind(&URLRequestMockHTTPJob::GetRawHeaders, 171 weak_ptr_factory_.GetWeakPtr())); 172} 173 174void URLRequestMockHTTPJob::GetRawHeaders(std::string raw_headers) { 175 // Handle CRLF line-endings. 176 ReplaceSubstringsAfterOffset(&raw_headers, 0, "\r\n", "\n"); 177 // ParseRawHeaders expects \0 to end each header line. 178 ReplaceSubstringsAfterOffset(&raw_headers, 0, "\n", std::string("\0", 1)); 179 raw_headers_ = raw_headers; 180 URLRequestFileJob::Start(); 181} 182 183// Private const version. 184void URLRequestMockHTTPJob::GetResponseInfoConst( 185 net::HttpResponseInfo* info) const { 186 info->headers = new net::HttpResponseHeaders(raw_headers_); 187} 188 189bool URLRequestMockHTTPJob::GetMimeType(std::string* mime_type) const { 190 net::HttpResponseInfo info; 191 GetResponseInfoConst(&info); 192 return info.headers.get() && info.headers->GetMimeType(mime_type); 193} 194 195int URLRequestMockHTTPJob::GetResponseCode() const { 196 net::HttpResponseInfo info; 197 GetResponseInfoConst(&info); 198 // If we have headers, get the response code from them. 199 if (info.headers.get()) 200 return info.headers->response_code(); 201 return net::URLRequestJob::GetResponseCode(); 202} 203 204bool URLRequestMockHTTPJob::GetCharset(std::string* charset) { 205 net::HttpResponseInfo info; 206 GetResponseInfo(&info); 207 return info.headers.get() && info.headers->GetCharset(charset); 208} 209 210} // namespace net 211