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/proxy/proxy_script_fetcher_impl.h" 6 7 #include <string> 8 9 #include "base/compiler_specific.h" 10 #include "base/files/file_path.h" 11 #include "base/path_service.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "net/base/load_flags.h" 14 #include "net/base/net_util.h" 15 #include "net/base/test_completion_callback.h" 16 #include "net/cert/mock_cert_verifier.h" 17 #include "net/disk_cache/disk_cache.h" 18 #include "net/dns/mock_host_resolver.h" 19 #include "net/http/http_cache.h" 20 #include "net/http/http_network_session.h" 21 #include "net/http/http_server_properties_impl.h" 22 #include "net/http/transport_security_state.h" 23 #include "net/ssl/ssl_config_service_defaults.h" 24 #include "net/test/spawned_test_server/spawned_test_server.h" 25 #include "net/url_request/file_protocol_handler.h" 26 #include "net/url_request/url_request_context_storage.h" 27 #include "net/url_request/url_request_file_job.h" 28 #include "net/url_request/url_request_job_factory_impl.h" 29 #include "net/url_request/url_request_test_util.h" 30 #include "testing/gtest/include/gtest/gtest.h" 31 #include "testing/platform_test.h" 32 33 namespace net { 34 35 // TODO(eroman): 36 // - Test canceling an outstanding request. 37 // - Test deleting ProxyScriptFetcher while a request is in progress. 38 39 namespace { 40 41 const base::FilePath::CharType kDocRoot[] = 42 FILE_PATH_LITERAL("net/data/proxy_script_fetcher_unittest"); 43 44 struct FetchResult { 45 int code; 46 base::string16 text; 47 }; 48 49 // A non-mock URL request which can access http:// and file:// urls. 50 class RequestContext : public URLRequestContext { 51 public: 52 RequestContext() : storage_(this) { 53 ProxyConfig no_proxy; 54 storage_.set_host_resolver(scoped_ptr<HostResolver>(new MockHostResolver)); 55 storage_.set_cert_verifier(new MockCertVerifier); 56 storage_.set_transport_security_state(new TransportSecurityState); 57 storage_.set_proxy_service(ProxyService::CreateFixed(no_proxy)); 58 storage_.set_ssl_config_service(new SSLConfigServiceDefaults); 59 storage_.set_http_server_properties( 60 scoped_ptr<HttpServerProperties>(new HttpServerPropertiesImpl())); 61 62 HttpNetworkSession::Params params; 63 params.host_resolver = host_resolver(); 64 params.cert_verifier = cert_verifier(); 65 params.transport_security_state = transport_security_state(); 66 params.proxy_service = proxy_service(); 67 params.ssl_config_service = ssl_config_service(); 68 params.http_server_properties = http_server_properties(); 69 scoped_refptr<HttpNetworkSession> network_session( 70 new HttpNetworkSession(params)); 71 storage_.set_http_transaction_factory(new HttpCache( 72 network_session.get(), HttpCache::DefaultBackend::InMemory(0))); 73 URLRequestJobFactoryImpl* job_factory = new URLRequestJobFactoryImpl(); 74 job_factory->SetProtocolHandler( 75 "file", new FileProtocolHandler(base::MessageLoopProxy::current())); 76 storage_.set_job_factory(job_factory); 77 } 78 79 virtual ~RequestContext() { 80 } 81 82 private: 83 URLRequestContextStorage storage_; 84 }; 85 86 // Get a file:// url relative to net/data/proxy/proxy_script_fetcher_unittest. 87 GURL GetTestFileUrl(const std::string& relpath) { 88 base::FilePath path; 89 PathService::Get(base::DIR_SOURCE_ROOT, &path); 90 path = path.AppendASCII("net"); 91 path = path.AppendASCII("data"); 92 path = path.AppendASCII("proxy_script_fetcher_unittest"); 93 GURL base_url = FilePathToFileURL(path); 94 return GURL(base_url.spec() + "/" + relpath); 95 } 96 97 // Really simple NetworkDelegate so we can allow local file access on ChromeOS 98 // without introducing layering violations. Also causes a test failure if a 99 // request is seen that doesn't set a load flag to bypass revocation checking. 100 101 class BasicNetworkDelegate : public NetworkDelegate { 102 public: 103 BasicNetworkDelegate() {} 104 virtual ~BasicNetworkDelegate() {} 105 106 private: 107 virtual int OnBeforeURLRequest(URLRequest* request, 108 const CompletionCallback& callback, 109 GURL* new_url) OVERRIDE { 110 EXPECT_TRUE(request->load_flags() & LOAD_DISABLE_CERT_REVOCATION_CHECKING); 111 return OK; 112 } 113 114 virtual int OnBeforeSendHeaders(URLRequest* request, 115 const CompletionCallback& callback, 116 HttpRequestHeaders* headers) OVERRIDE { 117 return OK; 118 } 119 120 virtual void OnSendHeaders(URLRequest* request, 121 const HttpRequestHeaders& headers) OVERRIDE {} 122 123 virtual int OnHeadersReceived( 124 URLRequest* request, 125 const CompletionCallback& callback, 126 const HttpResponseHeaders* original_response_headers, 127 scoped_refptr<HttpResponseHeaders>* override_response_headers) 128 OVERRIDE { 129 return OK; 130 } 131 132 virtual void OnBeforeRedirect(URLRequest* request, 133 const GURL& new_location) OVERRIDE {} 134 135 virtual void OnResponseStarted(URLRequest* request) OVERRIDE {} 136 137 virtual void OnRawBytesRead(const URLRequest& request, 138 int bytes_read) OVERRIDE {} 139 140 virtual void OnCompleted(URLRequest* request, bool started) OVERRIDE {} 141 142 virtual void OnURLRequestDestroyed(URLRequest* request) OVERRIDE {} 143 144 virtual void OnPACScriptError(int line_number, 145 const base::string16& error) OVERRIDE {} 146 147 virtual NetworkDelegate::AuthRequiredResponse OnAuthRequired( 148 URLRequest* request, 149 const AuthChallengeInfo& auth_info, 150 const AuthCallback& callback, 151 AuthCredentials* credentials) OVERRIDE { 152 return NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION; 153 } 154 155 virtual bool OnCanGetCookies(const URLRequest& request, 156 const CookieList& cookie_list) OVERRIDE { 157 return true; 158 } 159 160 virtual bool OnCanSetCookie(const URLRequest& request, 161 const std::string& cookie_line, 162 CookieOptions* options) OVERRIDE { 163 return true; 164 } 165 166 virtual bool OnCanAccessFile(const net::URLRequest& request, 167 const base::FilePath& path) const OVERRIDE { 168 return true; 169 } 170 virtual bool OnCanThrottleRequest(const URLRequest& request) const OVERRIDE { 171 return false; 172 } 173 174 virtual int OnBeforeSocketStreamConnect( 175 SocketStream* stream, 176 const CompletionCallback& callback) OVERRIDE { 177 return OK; 178 } 179 180 virtual void OnRequestWaitStateChange(const net::URLRequest& request, 181 RequestWaitState state) OVERRIDE { 182 } 183 184 DISALLOW_COPY_AND_ASSIGN(BasicNetworkDelegate); 185 }; 186 187 } // namespace 188 189 class ProxyScriptFetcherImplTest : public PlatformTest { 190 public: 191 ProxyScriptFetcherImplTest() 192 : test_server_(SpawnedTestServer::TYPE_HTTP, 193 net::SpawnedTestServer::kLocalhost, 194 base::FilePath(kDocRoot)) { 195 context_.set_network_delegate(&network_delegate_); 196 } 197 198 protected: 199 SpawnedTestServer test_server_; 200 BasicNetworkDelegate network_delegate_; 201 RequestContext context_; 202 }; 203 204 TEST_F(ProxyScriptFetcherImplTest, FileUrl) { 205 ProxyScriptFetcherImpl pac_fetcher(&context_); 206 207 { // Fetch a non-existent file. 208 base::string16 text; 209 TestCompletionCallback callback; 210 int result = pac_fetcher.Fetch(GetTestFileUrl("does-not-exist"), 211 &text, callback.callback()); 212 EXPECT_EQ(ERR_IO_PENDING, result); 213 EXPECT_EQ(ERR_FILE_NOT_FOUND, callback.WaitForResult()); 214 EXPECT_TRUE(text.empty()); 215 } 216 { // Fetch a file that exists. 217 base::string16 text; 218 TestCompletionCallback callback; 219 int result = pac_fetcher.Fetch(GetTestFileUrl("pac.txt"), 220 &text, callback.callback()); 221 EXPECT_EQ(ERR_IO_PENDING, result); 222 EXPECT_EQ(OK, callback.WaitForResult()); 223 EXPECT_EQ(ASCIIToUTF16("-pac.txt-\n"), text); 224 } 225 } 226 227 // Note that all mime types are allowed for PAC file, to be consistent 228 // with other browsers. 229 TEST_F(ProxyScriptFetcherImplTest, HttpMimeType) { 230 ASSERT_TRUE(test_server_.Start()); 231 232 ProxyScriptFetcherImpl pac_fetcher(&context_); 233 234 { // Fetch a PAC with mime type "text/plain" 235 GURL url(test_server_.GetURL("files/pac.txt")); 236 base::string16 text; 237 TestCompletionCallback callback; 238 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 239 EXPECT_EQ(ERR_IO_PENDING, result); 240 EXPECT_EQ(OK, callback.WaitForResult()); 241 EXPECT_EQ(ASCIIToUTF16("-pac.txt-\n"), text); 242 } 243 { // Fetch a PAC with mime type "text/html" 244 GURL url(test_server_.GetURL("files/pac.html")); 245 base::string16 text; 246 TestCompletionCallback callback; 247 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 248 EXPECT_EQ(ERR_IO_PENDING, result); 249 EXPECT_EQ(OK, callback.WaitForResult()); 250 EXPECT_EQ(ASCIIToUTF16("-pac.html-\n"), text); 251 } 252 { // Fetch a PAC with mime type "application/x-ns-proxy-autoconfig" 253 GURL url(test_server_.GetURL("files/pac.nsproxy")); 254 base::string16 text; 255 TestCompletionCallback callback; 256 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 257 EXPECT_EQ(ERR_IO_PENDING, result); 258 EXPECT_EQ(OK, callback.WaitForResult()); 259 EXPECT_EQ(ASCIIToUTF16("-pac.nsproxy-\n"), text); 260 } 261 } 262 263 TEST_F(ProxyScriptFetcherImplTest, HttpStatusCode) { 264 ASSERT_TRUE(test_server_.Start()); 265 266 ProxyScriptFetcherImpl pac_fetcher(&context_); 267 268 { // Fetch a PAC which gives a 500 -- FAIL 269 GURL url(test_server_.GetURL("files/500.pac")); 270 base::string16 text; 271 TestCompletionCallback callback; 272 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 273 EXPECT_EQ(ERR_IO_PENDING, result); 274 EXPECT_EQ(ERR_PAC_STATUS_NOT_OK, callback.WaitForResult()); 275 EXPECT_TRUE(text.empty()); 276 } 277 { // Fetch a PAC which gives a 404 -- FAIL 278 GURL url(test_server_.GetURL("files/404.pac")); 279 base::string16 text; 280 TestCompletionCallback callback; 281 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 282 EXPECT_EQ(ERR_IO_PENDING, result); 283 EXPECT_EQ(ERR_PAC_STATUS_NOT_OK, callback.WaitForResult()); 284 EXPECT_TRUE(text.empty()); 285 } 286 } 287 288 TEST_F(ProxyScriptFetcherImplTest, ContentDisposition) { 289 ASSERT_TRUE(test_server_.Start()); 290 291 ProxyScriptFetcherImpl pac_fetcher(&context_); 292 293 // Fetch PAC scripts via HTTP with a Content-Disposition header -- should 294 // have no effect. 295 GURL url(test_server_.GetURL("files/downloadable.pac")); 296 base::string16 text; 297 TestCompletionCallback callback; 298 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 299 EXPECT_EQ(ERR_IO_PENDING, result); 300 EXPECT_EQ(OK, callback.WaitForResult()); 301 EXPECT_EQ(ASCIIToUTF16("-downloadable.pac-\n"), text); 302 } 303 304 // Verifies that PAC scripts are not being cached. 305 TEST_F(ProxyScriptFetcherImplTest, NoCache) { 306 ASSERT_TRUE(test_server_.Start()); 307 308 ProxyScriptFetcherImpl pac_fetcher(&context_); 309 310 // Fetch a PAC script whose HTTP headers make it cacheable for 1 hour. 311 GURL url(test_server_.GetURL("files/cacheable_1hr.pac")); 312 { 313 base::string16 text; 314 TestCompletionCallback callback; 315 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 316 EXPECT_EQ(ERR_IO_PENDING, result); 317 EXPECT_EQ(OK, callback.WaitForResult()); 318 EXPECT_EQ(ASCIIToUTF16("-cacheable_1hr.pac-\n"), text); 319 } 320 321 // Kill the HTTP server. 322 ASSERT_TRUE(test_server_.Stop()); 323 324 // Try to fetch the file again. Since the server is not running anymore, the 325 // call should fail, thus indicating that the file was not fetched from the 326 // local cache. 327 { 328 base::string16 text; 329 TestCompletionCallback callback; 330 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 331 EXPECT_EQ(ERR_IO_PENDING, result); 332 333 // Expect any error. The exact error varies by platform. 334 EXPECT_NE(OK, callback.WaitForResult()); 335 } 336 } 337 338 TEST_F(ProxyScriptFetcherImplTest, TooLarge) { 339 ASSERT_TRUE(test_server_.Start()); 340 341 ProxyScriptFetcherImpl pac_fetcher(&context_); 342 343 // Set the maximum response size to 50 bytes. 344 int prev_size = pac_fetcher.SetSizeConstraint(50); 345 346 // These two URLs are the same file, but are http:// vs file:// 347 GURL urls[] = { 348 test_server_.GetURL("files/large-pac.nsproxy"), 349 GetTestFileUrl("large-pac.nsproxy") 350 }; 351 352 // Try fetching URLs that are 101 bytes large. We should abort the request 353 // after 50 bytes have been read, and fail with a too large error. 354 for (size_t i = 0; i < arraysize(urls); ++i) { 355 const GURL& url = urls[i]; 356 base::string16 text; 357 TestCompletionCallback callback; 358 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 359 EXPECT_EQ(ERR_IO_PENDING, result); 360 EXPECT_EQ(ERR_FILE_TOO_BIG, callback.WaitForResult()); 361 EXPECT_TRUE(text.empty()); 362 } 363 364 // Restore the original size bound. 365 pac_fetcher.SetSizeConstraint(prev_size); 366 367 { // Make sure we can still fetch regular URLs. 368 GURL url(test_server_.GetURL("files/pac.nsproxy")); 369 base::string16 text; 370 TestCompletionCallback callback; 371 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 372 EXPECT_EQ(ERR_IO_PENDING, result); 373 EXPECT_EQ(OK, callback.WaitForResult()); 374 EXPECT_EQ(ASCIIToUTF16("-pac.nsproxy-\n"), text); 375 } 376 } 377 378 TEST_F(ProxyScriptFetcherImplTest, Hang) { 379 ASSERT_TRUE(test_server_.Start()); 380 381 ProxyScriptFetcherImpl pac_fetcher(&context_); 382 383 // Set the timeout period to 0.5 seconds. 384 base::TimeDelta prev_timeout = pac_fetcher.SetTimeoutConstraint( 385 base::TimeDelta::FromMilliseconds(500)); 386 387 // Try fetching a URL which takes 1.2 seconds. We should abort the request 388 // after 500 ms, and fail with a timeout error. 389 { 390 GURL url(test_server_.GetURL("slow/proxy.pac?1.2")); 391 base::string16 text; 392 TestCompletionCallback callback; 393 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 394 EXPECT_EQ(ERR_IO_PENDING, result); 395 EXPECT_EQ(ERR_TIMED_OUT, callback.WaitForResult()); 396 EXPECT_TRUE(text.empty()); 397 } 398 399 // Restore the original timeout period. 400 pac_fetcher.SetTimeoutConstraint(prev_timeout); 401 402 { // Make sure we can still fetch regular URLs. 403 GURL url(test_server_.GetURL("files/pac.nsproxy")); 404 base::string16 text; 405 TestCompletionCallback callback; 406 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 407 EXPECT_EQ(ERR_IO_PENDING, result); 408 EXPECT_EQ(OK, callback.WaitForResult()); 409 EXPECT_EQ(ASCIIToUTF16("-pac.nsproxy-\n"), text); 410 } 411 } 412 413 // The ProxyScriptFetcher should decode any content-codings 414 // (like gzip, bzip, etc.), and apply any charset conversions to yield 415 // UTF8. 416 TEST_F(ProxyScriptFetcherImplTest, Encodings) { 417 ASSERT_TRUE(test_server_.Start()); 418 419 ProxyScriptFetcherImpl pac_fetcher(&context_); 420 421 // Test a response that is gzip-encoded -- should get inflated. 422 { 423 GURL url(test_server_.GetURL("files/gzipped_pac")); 424 base::string16 text; 425 TestCompletionCallback callback; 426 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 427 EXPECT_EQ(ERR_IO_PENDING, result); 428 EXPECT_EQ(OK, callback.WaitForResult()); 429 EXPECT_EQ(ASCIIToUTF16("This data was gzipped.\n"), text); 430 } 431 432 // Test a response that was served as UTF-16 (BE). It should 433 // be converted to UTF8. 434 { 435 GURL url(test_server_.GetURL("files/utf16be_pac")); 436 base::string16 text; 437 TestCompletionCallback callback; 438 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 439 EXPECT_EQ(ERR_IO_PENDING, result); 440 EXPECT_EQ(OK, callback.WaitForResult()); 441 EXPECT_EQ(ASCIIToUTF16("This was encoded as UTF-16BE.\n"), text); 442 } 443 } 444 445 TEST_F(ProxyScriptFetcherImplTest, DataURLs) { 446 ProxyScriptFetcherImpl pac_fetcher(&context_); 447 448 const char kEncodedUrl[] = 449 "data:application/x-ns-proxy-autoconfig;base64,ZnVuY3Rpb24gRmluZFByb3h5R" 450 "m9yVVJMKHVybCwgaG9zdCkgewogIGlmIChob3N0ID09ICdmb29iYXIuY29tJykKICAgIHJl" 451 "dHVybiAnUFJPWFkgYmxhY2tob2xlOjgwJzsKICByZXR1cm4gJ0RJUkVDVCc7Cn0="; 452 const char kPacScript[] = 453 "function FindProxyForURL(url, host) {\n" 454 " if (host == 'foobar.com')\n" 455 " return 'PROXY blackhole:80';\n" 456 " return 'DIRECT';\n" 457 "}"; 458 459 // Test fetching a "data:"-url containing a base64 encoded PAC script. 460 { 461 GURL url(kEncodedUrl); 462 base::string16 text; 463 TestCompletionCallback callback; 464 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 465 EXPECT_EQ(OK, result); 466 EXPECT_EQ(ASCIIToUTF16(kPacScript), text); 467 } 468 469 const char kEncodedUrlBroken[] = 470 "data:application/x-ns-proxy-autoconfig;base64,ZnVuY3Rpb24gRmluZFByb3h5R"; 471 472 // Test a broken "data:"-url containing a base64 encoded PAC script. 473 { 474 GURL url(kEncodedUrlBroken); 475 base::string16 text; 476 TestCompletionCallback callback; 477 int result = pac_fetcher.Fetch(url, &text, callback.callback()); 478 EXPECT_EQ(ERR_FAILED, result); 479 } 480 } 481 482 } // namespace net 483