blob: 719c3806ebe1a18fc7eacd54cfc33283981749e7 [file] [log] [blame]
[email protected]9b9ae9552010-07-01 22:20:501// Copyright (c) 2010 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.
[email protected]677c90572008-12-10 09:03:154
5#include "net/proxy/proxy_script_fetcher.h"
6
7#include "base/compiler_specific.h"
[email protected]d6e58c6e2009-10-10 20:40:508#include "base/i18n/icu_string_conversions.h"
[email protected]13a279e2009-04-13 17:32:379#include "base/logging.h"
[email protected]677c90572008-12-10 09:03:1510#include "base/message_loop.h"
[email protected]9dea9e1f2009-01-29 00:30:4711#include "base/ref_counted.h"
[email protected]677c90572008-12-10 09:03:1512#include "base/string_util.h"
[email protected]d6e58c6e2009-10-10 20:40:5013#include "base/utf_string_conversions.h"
[email protected]9dea9e1f2009-01-29 00:30:4714#include "net/base/io_buffer.h"
[email protected]677c90572008-12-10 09:03:1515#include "net/base/load_flags.h"
[email protected]597cf6e2009-05-29 09:43:2616#include "net/base/net_errors.h"
[email protected]e0ef2c22009-06-03 23:54:4417#include "net/http/http_response_headers.h"
[email protected]677c90572008-12-10 09:03:1518#include "net/url_request/url_request.h"
19
[email protected]f0a51fb52009-03-05 12:46:3820// TODO(eroman):
[email protected]677c90572008-12-10 09:03:1521// - Support auth-prompts.
22
23namespace net {
24
25namespace {
26
27// The maximum size (in bytes) allowed for a PAC script. Responses exceeding
28// this will fail with ERR_FILE_TOO_BIG.
29int max_response_bytes = 1048576; // 1 megabyte
30
31// The maximum duration (in milliseconds) allowed for fetching the PAC script.
32// Responses exceeding this will fail with ERR_TIMED_OUT.
33int max_duration_ms = 300000; // 5 minutes
34
[email protected]13a279e2009-04-13 17:32:3735// Returns true if |mime_type| is one of the known PAC mime type.
36bool IsPacMimeType(const std::string& mime_type) {
37 static const char * const kSupportedPacMimeTypes[] = {
38 "application/x-ns-proxy-autoconfig",
39 "application/x-javascript-config",
40 };
41 for (size_t i = 0; i < arraysize(kSupportedPacMimeTypes); ++i) {
42 if (LowerCaseEqualsASCII(mime_type, kSupportedPacMimeTypes[i]))
43 return true;
44 }
45 return false;
46}
47
[email protected]9b9ae9552010-07-01 22:20:5048// Converts |bytes| (which is encoded by |charset|) to UTF16, saving the resul
49// to |*utf16|.
[email protected]8f3c96342009-09-22 03:06:5450// If |charset| is empty, then we don't know what it was and guess.
[email protected]9b9ae9552010-07-01 22:20:5051void ConvertResponseToUTF16(const std::string& charset,
52 const std::string& bytes,
53 string16* utf16) {
[email protected]8f3c96342009-09-22 03:06:5454 const char* codepage;
55
56 if (charset.empty()) {
57 // Assume ISO-8859-1 if no charset was specified.
[email protected]d6e58c6e2009-10-10 20:40:5058 codepage = base::kCodepageLatin1;
[email protected]8f3c96342009-09-22 03:06:5459 } else {
60 // Otherwise trust the charset that was provided.
61 codepage = charset.c_str();
62 }
63
64 // We will be generous in the conversion -- if any characters lie
65 // outside of |charset| (i.e. invalid), then substitute them with
66 // U+FFFD rather than failing.
[email protected]9b9ae9552010-07-01 22:20:5067 base::CodepageToUTF16(bytes, codepage,
68 base::OnStringConversionError::SUBSTITUTE,
69 utf16);
[email protected]8f3c96342009-09-22 03:06:5470}
71
[email protected]677c90572008-12-10 09:03:1572} // namespace
73
74class ProxyScriptFetcherImpl : public ProxyScriptFetcher,
75 public URLRequest::Delegate {
76 public:
77 // Creates a ProxyScriptFetcher that issues requests through
78 // |url_request_context|. |url_request_context| must remain valid for the
79 // lifetime of ProxyScriptFetcherImpl.
80 explicit ProxyScriptFetcherImpl(URLRequestContext* url_request_context);
81
82 virtual ~ProxyScriptFetcherImpl();
83
84 // ProxyScriptFetcher methods:
85
[email protected]9b9ae9552010-07-01 22:20:5086 virtual int Fetch(const GURL& url, string16* text,
[email protected]620f5712009-08-04 22:43:1287 CompletionCallback* callback);
[email protected]677c90572008-12-10 09:03:1588 virtual void Cancel();
[email protected]20d296ddc2009-11-18 23:07:0889 virtual URLRequestContext* GetRequestContext();
[email protected]677c90572008-12-10 09:03:1590
91 // URLRequest::Delegate methods:
92
93 virtual void OnAuthRequired(URLRequest* request,
94 AuthChallengeInfo* auth_info);
95 virtual void OnSSLCertificateError(URLRequest* request, int cert_error,
96 X509Certificate* cert);
[email protected]677c90572008-12-10 09:03:1597 virtual void OnResponseStarted(URLRequest* request);
98 virtual void OnReadCompleted(URLRequest* request, int num_bytes);
99 virtual void OnResponseCompleted(URLRequest* request);
100
101 private:
102 // Read more bytes from the response.
103 void ReadBody(URLRequest* request);
104
105 // Called once the request has completed to notify the caller of
[email protected]9b9ae9552010-07-01 22:20:50106 // |response_code_| and |response_text_|.
[email protected]677c90572008-12-10 09:03:15107 void FetchCompleted();
108
109 // Clear out the state for the current request.
110 void ResetCurRequestState();
111
112 // Callback for time-out task of request with id |id|.
113 void OnTimeout(int id);
114
115 // Factory for creating the time-out task. This takes care of revoking
116 // outstanding tasks when |this| is deleted.
117 ScopedRunnableMethodFactory<ProxyScriptFetcherImpl> task_factory_;
118
119 // The context used for making network requests.
120 URLRequestContext* url_request_context_;
121
122 // Buffer that URLRequest writes into.
123 enum { kBufSize = 4096 };
[email protected]9dea9e1f2009-01-29 00:30:47124 scoped_refptr<net::IOBuffer> buf_;
[email protected]677c90572008-12-10 09:03:15125
126 // The next ID to use for |cur_request_| (monotonically increasing).
127 int next_id_;
128
129 // The current (in progress) request, or NULL.
130 scoped_ptr<URLRequest> cur_request_;
131
132 // State for current request (only valid when |cur_request_| is not NULL):
133
134 // Unique ID for the current request.
135 int cur_request_id_;
136
137 // Callback to invoke on completion of the fetch.
138 CompletionCallback* callback_;
139
140 // Holds the error condition that was hit on the current request, or OK.
141 int result_code_;
142
[email protected]9b9ae9552010-07-01 22:20:50143 // Holds the bytes read so far. Will not exceed |max_response_bytes|.
144 std::string bytes_read_so_far_;
145
146 // This buffer is owned by the owner of |callback|, and will be filled with
147 // UTF16 response on completion.
148 string16* result_text_;
[email protected]677c90572008-12-10 09:03:15149};
150
151ProxyScriptFetcherImpl::ProxyScriptFetcherImpl(
152 URLRequestContext* url_request_context)
153 : ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
154 url_request_context_(url_request_context),
[email protected]9dea9e1f2009-01-29 00:30:47155 buf_(new net::IOBuffer(kBufSize)),
[email protected]677c90572008-12-10 09:03:15156 next_id_(0),
157 cur_request_(NULL),
158 cur_request_id_(0),
159 callback_(NULL),
160 result_code_(OK),
[email protected]9b9ae9552010-07-01 22:20:50161 result_text_(NULL) {
[email protected]677c90572008-12-10 09:03:15162 DCHECK(url_request_context);
163}
164
165ProxyScriptFetcherImpl::~ProxyScriptFetcherImpl() {
166 // The URLRequest's destructor will cancel the outstanding request, and
167 // ensure that the delegate (this) is not called again.
168}
169
[email protected]620f5712009-08-04 22:43:12170int ProxyScriptFetcherImpl::Fetch(const GURL& url,
[email protected]9b9ae9552010-07-01 22:20:50171 string16* text,
[email protected]620f5712009-08-04 22:43:12172 CompletionCallback* callback) {
[email protected]677c90572008-12-10 09:03:15173 // It is invalid to call Fetch() while a request is already in progress.
174 DCHECK(!cur_request_.get());
175
176 DCHECK(callback);
[email protected]9b9ae9552010-07-01 22:20:50177 DCHECK(text);
[email protected]677c90572008-12-10 09:03:15178
179 cur_request_.reset(new URLRequest(url, this));
180 cur_request_->set_context(url_request_context_);
181 cur_request_->set_method("GET");
182
183 // Make sure that the PAC script is downloaded using a direct connection,
184 // to avoid circular dependencies (fetching is a part of proxy resolution).
[email protected]e0ef2c22009-06-03 23:54:44185 // Also disable the use of the disk cache. The cache is disabled so that if
186 // the user switches networks we don't potentially use the cached response
187 // from old network when we should in fact be re-fetching on the new network.
188 cur_request_->set_load_flags(LOAD_BYPASS_PROXY | LOAD_DISABLE_CACHE);
[email protected]677c90572008-12-10 09:03:15189
190 // Save the caller's info for notification on completion.
191 callback_ = callback;
[email protected]9b9ae9552010-07-01 22:20:50192 result_text_ = text;
193
194 bytes_read_so_far_.clear();
[email protected]677c90572008-12-10 09:03:15195
196 // Post a task to timeout this request if it takes too long.
197 cur_request_id_ = ++next_id_;
198 MessageLoop::current()->PostDelayedTask(FROM_HERE,
199 task_factory_.NewRunnableMethod(&ProxyScriptFetcherImpl::OnTimeout,
200 cur_request_id_),
201 static_cast<int>(max_duration_ms));
202
203 // Start the request.
204 cur_request_->Start();
[email protected]620f5712009-08-04 22:43:12205 return ERR_IO_PENDING;
[email protected]677c90572008-12-10 09:03:15206}
207
208void ProxyScriptFetcherImpl::Cancel() {
209 // ResetCurRequestState will free the URLRequest, which will cause
210 // cancellation.
211 ResetCurRequestState();
212}
213
[email protected]20d296ddc2009-11-18 23:07:08214URLRequestContext* ProxyScriptFetcherImpl::GetRequestContext() {
215 return url_request_context_;
216}
217
[email protected]677c90572008-12-10 09:03:15218void ProxyScriptFetcherImpl::OnAuthRequired(URLRequest* request,
219 AuthChallengeInfo* auth_info) {
220 DCHECK(request == cur_request_.get());
221 // TODO(eroman):
[email protected]13a279e2009-04-13 17:32:37222 LOG(WARNING) << "Auth required to fetch PAC script, aborting.";
[email protected]677c90572008-12-10 09:03:15223 result_code_ = ERR_NOT_IMPLEMENTED;
224 request->CancelAuth();
225}
226
227void ProxyScriptFetcherImpl::OnSSLCertificateError(URLRequest* request,
228 int cert_error,
229 X509Certificate* cert) {
230 DCHECK(request == cur_request_.get());
[email protected]13a279e2009-04-13 17:32:37231 LOG(WARNING) << "SSL certificate error when fetching PAC script, aborting.";
[email protected]677c90572008-12-10 09:03:15232 // Certificate errors are in same space as net errors.
233 result_code_ = cert_error;
234 request->Cancel();
235}
236
[email protected]677c90572008-12-10 09:03:15237void ProxyScriptFetcherImpl::OnResponseStarted(URLRequest* request) {
238 DCHECK(request == cur_request_.get());
239
240 if (!request->status().is_success()) {
241 OnResponseCompleted(request);
242 return;
243 }
244
245 // Require HTTP responses to have a success status code.
246 if (request->url().SchemeIs("http") || request->url().SchemeIs("https")) {
[email protected]f0a51fb52009-03-05 12:46:38247 // NOTE about status codes: We are like Firefox 3 in this respect.
[email protected]677c90572008-12-10 09:03:15248 // {IE 7, Safari 3, Opera 9.5} do not care about the status code.
249 if (request->GetResponseCode() != 200) {
[email protected]e0ef2c22009-06-03 23:54:44250 LOG(INFO) << "Fetched PAC script had (bad) status line: "
251 << request->response_headers()->GetStatusLine();
[email protected]677c90572008-12-10 09:03:15252 result_code_ = ERR_PAC_STATUS_NOT_OK;
253 request->Cancel();
254 return;
255 }
[email protected]13a279e2009-04-13 17:32:37256
257 // NOTE about mime types: We do not enforce mime types on PAC files.
258 // This is for compatibility with {IE 7, Firefox 3, Opera 9.5}. We will
259 // however log mismatches to help with debugging.
[email protected]e0ef2c22009-06-03 23:54:44260 std::string mime_type;
261 cur_request_->GetMimeType(&mime_type);
262 if (!IsPacMimeType(mime_type)) {
263 LOG(INFO) << "Fetched PAC script does not have a proper mime type: "
264 << mime_type;
[email protected]13a279e2009-04-13 17:32:37265 }
[email protected]677c90572008-12-10 09:03:15266 }
267
268 ReadBody(request);
269}
270
271void ProxyScriptFetcherImpl::OnReadCompleted(URLRequest* request,
272 int num_bytes) {
273 DCHECK(request == cur_request_.get());
274 if (num_bytes > 0) {
275 // Enforce maximum size bound.
[email protected]9b9ae9552010-07-01 22:20:50276 if (num_bytes + bytes_read_so_far_.size() >
[email protected]677c90572008-12-10 09:03:15277 static_cast<size_t>(max_response_bytes)) {
278 result_code_ = ERR_FILE_TOO_BIG;
279 request->Cancel();
280 return;
281 }
[email protected]9b9ae9552010-07-01 22:20:50282 bytes_read_so_far_.append(buf_->data(), num_bytes);
[email protected]677c90572008-12-10 09:03:15283 ReadBody(request);
284 } else { // Error while reading, or EOF
285 OnResponseCompleted(request);
286 }
287}
288
289void ProxyScriptFetcherImpl::OnResponseCompleted(URLRequest* request) {
290 DCHECK(request == cur_request_.get());
291
292 // Use |result_code_| as the request's error if we have already set it to
293 // something specific.
294 if (result_code_ == OK && !request->status().is_success())
295 result_code_ = request->status().os_error();
296
297 FetchCompleted();
298}
299
300void ProxyScriptFetcherImpl::ReadBody(URLRequest* request) {
301 int num_bytes;
302 if (request->Read(buf_, kBufSize, &num_bytes)) {
303 OnReadCompleted(request, num_bytes);
304 } else if (!request->status().is_io_pending()) {
305 // Read failed synchronously.
306 OnResponseCompleted(request);
307 }
308}
309
310void ProxyScriptFetcherImpl::FetchCompleted() {
[email protected]8f3c96342009-09-22 03:06:54311 if (result_code_ == OK) {
[email protected]9b9ae9552010-07-01 22:20:50312 // The caller expects the response to be encoded as UTF16.
[email protected]8f3c96342009-09-22 03:06:54313 std::string charset;
314 cur_request_->GetCharset(&charset);
[email protected]9b9ae9552010-07-01 22:20:50315 ConvertResponseToUTF16(charset, bytes_read_so_far_, result_text_);
[email protected]8f3c96342009-09-22 03:06:54316 } else {
317 // On error, the caller expects empty string for bytes.
[email protected]9b9ae9552010-07-01 22:20:50318 result_text_->clear();
[email protected]8f3c96342009-09-22 03:06:54319 }
[email protected]677c90572008-12-10 09:03:15320
321 int result_code = result_code_;
322 CompletionCallback* callback = callback_;
323
324 ResetCurRequestState();
325
326 callback->Run(result_code);
327}
328
329void ProxyScriptFetcherImpl::ResetCurRequestState() {
330 cur_request_.reset();
331 cur_request_id_ = 0;
332 callback_ = NULL;
333 result_code_ = OK;
[email protected]9b9ae9552010-07-01 22:20:50334 result_text_ = NULL;
[email protected]677c90572008-12-10 09:03:15335}
336
337void ProxyScriptFetcherImpl::OnTimeout(int id) {
338 // Timeout tasks may outlive the URLRequest they reference. Make sure it
339 // is still applicable.
340 if (cur_request_id_ != id)
341 return;
342
343 DCHECK(cur_request_.get());
344 result_code_ = ERR_TIMED_OUT;
345 cur_request_->Cancel();
346}
347
348// static
349ProxyScriptFetcher* ProxyScriptFetcher::Create(
350 URLRequestContext* url_request_context) {
351 return new ProxyScriptFetcherImpl(url_request_context);
352}
353
354// static
355int ProxyScriptFetcher::SetTimeoutConstraintForUnittest(
356 int timeout_ms) {
357 int prev = max_duration_ms;
358 max_duration_ms = timeout_ms;
359 return prev;
360}
361
362// static
363size_t ProxyScriptFetcher::SetSizeConstraintForUnittest(size_t size_bytes) {
364 size_t prev = max_response_bytes;
365 max_response_bytes = size_bytes;
366 return prev;
367}
368
369} // namespace net