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