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