diff options
Diffstat (limited to 'net/proxy/proxy_script_fetcher_impl.cc')
-rw-r--r-- | net/proxy/proxy_script_fetcher_impl.cc | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/net/proxy/proxy_script_fetcher_impl.cc b/net/proxy/proxy_script_fetcher_impl.cc new file mode 100644 index 0000000..035622f --- /dev/null +++ b/net/proxy/proxy_script_fetcher_impl.cc @@ -0,0 +1,286 @@ +// Copyright (c) 2010 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/proxy/proxy_script_fetcher_impl.h" + +#include "base/compiler_specific.h" +#include "base/i18n/icu_string_conversions.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "net/base/io_buffer.h" +#include "net/base/load_flags.h" +#include "net/base/net_errors.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request_context.h" + +// TODO(eroman): +// - Support auth-prompts. + +// TODO(eroman): Use a state machine rather than recursion. Recursion could +// lead to lots of frames. +namespace net { + +namespace { + +// The maximum size (in bytes) allowed for a PAC script. Responses exceeding +// this will fail with ERR_FILE_TOO_BIG. +const int kDefaultMaxResponseBytes = 1048576; // 1 megabyte + +// The maximum duration (in milliseconds) allowed for fetching the PAC script. +// Responses exceeding this will fail with ERR_TIMED_OUT. +const int kDefaultMaxDurationMs = 300000; // 5 minutes + +// Returns true if |mime_type| is one of the known PAC mime type. +bool IsPacMimeType(const std::string& mime_type) { + static const char * const kSupportedPacMimeTypes[] = { + "application/x-ns-proxy-autoconfig", + "application/x-javascript-config", + }; + for (size_t i = 0; i < arraysize(kSupportedPacMimeTypes); ++i) { + if (LowerCaseEqualsASCII(mime_type, kSupportedPacMimeTypes[i])) + return true; + } + return false; +} + +// Converts |bytes| (which is encoded by |charset|) to UTF16, saving the resul +// to |*utf16|. +// If |charset| is empty, then we don't know what it was and guess. +void ConvertResponseToUTF16(const std::string& charset, + const std::string& bytes, + string16* utf16) { + const char* codepage; + + if (charset.empty()) { + // Assume ISO-8859-1 if no charset was specified. + codepage = base::kCodepageLatin1; + } else { + // Otherwise trust the charset that was provided. + codepage = charset.c_str(); + } + + // We will be generous in the conversion -- if any characters lie + // outside of |charset| (i.e. invalid), then substitute them with + // U+FFFD rather than failing. + base::CodepageToUTF16(bytes, codepage, + base::OnStringConversionError::SUBSTITUTE, + utf16); +} + +} // namespace + +ProxyScriptFetcherImpl::ProxyScriptFetcherImpl( + URLRequestContext* url_request_context) + : ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)), + url_request_context_(url_request_context), + buf_(new net::IOBuffer(kBufSize)), + next_id_(0), + cur_request_(NULL), + cur_request_id_(0), + callback_(NULL), + result_code_(OK), + result_text_(NULL), + max_response_bytes_(kDefaultMaxResponseBytes), + max_duration_(base::TimeDelta::FromMilliseconds(kDefaultMaxDurationMs)) { + DCHECK(url_request_context); +} + +ProxyScriptFetcherImpl::~ProxyScriptFetcherImpl() { + // The URLRequest's destructor will cancel the outstanding request, and + // ensure that the delegate (this) is not called again. +} + +int ProxyScriptFetcherImpl::Fetch(const GURL& url, + string16* text, + CompletionCallback* callback) { + // It is invalid to call Fetch() while a request is already in progress. + DCHECK(!cur_request_.get()); + + DCHECK(callback); + DCHECK(text); + + cur_request_.reset(new URLRequest(url, this)); + cur_request_->set_context(url_request_context_); + cur_request_->set_method("GET"); + + // Make sure that the PAC script is downloaded using a direct connection, + // to avoid circular dependencies (fetching is a part of proxy resolution). + // Also disable the use of the disk cache. The cache is disabled so that if + // the user switches networks we don't potentially use the cached response + // from old network when we should in fact be re-fetching on the new network. + cur_request_->set_load_flags(LOAD_BYPASS_PROXY | LOAD_DISABLE_CACHE); + + // Save the caller's info for notification on completion. + callback_ = callback; + result_text_ = text; + + bytes_read_so_far_.clear(); + + // Post a task to timeout this request if it takes too long. + cur_request_id_ = ++next_id_; + MessageLoop::current()->PostDelayedTask(FROM_HERE, + task_factory_.NewRunnableMethod(&ProxyScriptFetcherImpl::OnTimeout, + cur_request_id_), + static_cast<int>(max_duration_.InMilliseconds())); + + // Start the request. + cur_request_->Start(); + return ERR_IO_PENDING; +} + +void ProxyScriptFetcherImpl::Cancel() { + // ResetCurRequestState will free the URLRequest, which will cause + // cancellation. + ResetCurRequestState(); +} + +URLRequestContext* ProxyScriptFetcherImpl::GetRequestContext() { + return url_request_context_; +} + +void ProxyScriptFetcherImpl::OnAuthRequired(URLRequest* request, + AuthChallengeInfo* auth_info) { + DCHECK(request == cur_request_.get()); + // TODO(eroman): + LOG(WARNING) << "Auth required to fetch PAC script, aborting."; + result_code_ = ERR_NOT_IMPLEMENTED; + request->CancelAuth(); +} + +void ProxyScriptFetcherImpl::OnSSLCertificateError(URLRequest* request, + int cert_error, + X509Certificate* cert) { + DCHECK(request == cur_request_.get()); + LOG(WARNING) << "SSL certificate error when fetching PAC script, aborting."; + // Certificate errors are in same space as net errors. + result_code_ = cert_error; + request->Cancel(); +} + +void ProxyScriptFetcherImpl::OnResponseStarted(URLRequest* request) { + DCHECK(request == cur_request_.get()); + + if (!request->status().is_success()) { + OnResponseCompleted(request); + return; + } + + // Require HTTP responses to have a success status code. + if (request->url().SchemeIs("http") || request->url().SchemeIs("https")) { + // NOTE about status codes: We are like Firefox 3 in this respect. + // {IE 7, Safari 3, Opera 9.5} do not care about the status code. + if (request->GetResponseCode() != 200) { + VLOG(1) << "Fetched PAC script had (bad) status line: " + << request->response_headers()->GetStatusLine(); + result_code_ = ERR_PAC_STATUS_NOT_OK; + request->Cancel(); + return; + } + + // NOTE about mime types: We do not enforce mime types on PAC files. + // This is for compatibility with {IE 7, Firefox 3, Opera 9.5}. We will + // however log mismatches to help with debugging. + std::string mime_type; + cur_request_->GetMimeType(&mime_type); + if (!IsPacMimeType(mime_type)) { + VLOG(1) << "Fetched PAC script does not have a proper mime type: " + << mime_type; + } + } + + ReadBody(request); +} + +void ProxyScriptFetcherImpl::OnReadCompleted(URLRequest* request, + int num_bytes) { + DCHECK(request == cur_request_.get()); + if (num_bytes > 0) { + // Enforce maximum size bound. + if (num_bytes + bytes_read_so_far_.size() > + static_cast<size_t>(max_response_bytes_)) { + result_code_ = ERR_FILE_TOO_BIG; + request->Cancel(); + return; + } + bytes_read_so_far_.append(buf_->data(), num_bytes); + ReadBody(request); + } else { // Error while reading, or EOF + OnResponseCompleted(request); + } +} + +void ProxyScriptFetcherImpl::OnResponseCompleted(URLRequest* request) { + DCHECK(request == cur_request_.get()); + + // Use |result_code_| as the request's error if we have already set it to + // something specific. + if (result_code_ == OK && !request->status().is_success()) + result_code_ = request->status().os_error(); + + FetchCompleted(); +} + +void ProxyScriptFetcherImpl::ReadBody(URLRequest* request) { + int num_bytes; + if (request->Read(buf_, kBufSize, &num_bytes)) { + OnReadCompleted(request, num_bytes); + } else if (!request->status().is_io_pending()) { + // Read failed synchronously. + OnResponseCompleted(request); + } +} + +void ProxyScriptFetcherImpl::FetchCompleted() { + if (result_code_ == OK) { + // The caller expects the response to be encoded as UTF16. + std::string charset; + cur_request_->GetCharset(&charset); + ConvertResponseToUTF16(charset, bytes_read_so_far_, result_text_); + } else { + // On error, the caller expects empty string for bytes. + result_text_->clear(); + } + + int result_code = result_code_; + CompletionCallback* callback = callback_; + + ResetCurRequestState(); + + callback->Run(result_code); +} + +void ProxyScriptFetcherImpl::ResetCurRequestState() { + cur_request_.reset(); + cur_request_id_ = 0; + callback_ = NULL; + result_code_ = OK; + result_text_ = NULL; +} + +void ProxyScriptFetcherImpl::OnTimeout(int id) { + // Timeout tasks may outlive the URLRequest they reference. Make sure it + // is still applicable. + if (cur_request_id_ != id) + return; + + DCHECK(cur_request_.get()); + result_code_ = ERR_TIMED_OUT; + cur_request_->Cancel(); +} + +base::TimeDelta ProxyScriptFetcherImpl::SetTimeoutConstraint( + base::TimeDelta timeout) { + base::TimeDelta prev = max_duration_; + max_duration_ = timeout; + return prev; +} + +size_t ProxyScriptFetcherImpl::SetSizeConstraint(size_t size_bytes) { + size_t prev = max_response_bytes_; + max_response_bytes_ = size_bytes; + return prev; +} + +} // namespace net |