blob: f2e5a392474e070b1a80a8ff6e77692724dc1e38 [file] [log] [blame]
[email protected]677c90572008-12-10 09:03:151// Copyright (c) 2008 The Chromium Authors. All rights reserved. Use of this
2// source code is governed by a BSD-style license that can be found in the
3// LICENSE file.
4
5#include "net/proxy/proxy_script_fetcher.h"
6
7#include "base/compiler_specific.h"
[email protected]13a279e2009-04-13 17:32:378#include "base/logging.h"
[email protected]677c90572008-12-10 09:03:159#include "base/message_loop.h"
[email protected]9dea9e1f2009-01-29 00:30:4710#include "base/ref_counted.h"
[email protected]677c90572008-12-10 09:03:1511#include "base/string_util.h"
[email protected]9dea9e1f2009-01-29 00:30:4712#include "net/base/io_buffer.h"
[email protected]677c90572008-12-10 09:03:1513#include "net/base/load_flags.h"
14#include "net/url_request/url_request.h"
15
[email protected]f0a51fb52009-03-05 12:46:3816// TODO(eroman):
[email protected]677c90572008-12-10 09:03:1517// - Support auth-prompts.
18
19namespace net {
20
21namespace {
22
23// The maximum size (in bytes) allowed for a PAC script. Responses exceeding
24// this will fail with ERR_FILE_TOO_BIG.
25int max_response_bytes = 1048576; // 1 megabyte
26
27// The maximum duration (in milliseconds) allowed for fetching the PAC script.
28// Responses exceeding this will fail with ERR_TIMED_OUT.
29int max_duration_ms = 300000; // 5 minutes
30
[email protected]13a279e2009-04-13 17:32:3731// Returns true if |mime_type| is one of the known PAC mime type.
32bool IsPacMimeType(const std::string& mime_type) {
33 static const char * const kSupportedPacMimeTypes[] = {
34 "application/x-ns-proxy-autoconfig",
35 "application/x-javascript-config",
36 };
37 for (size_t i = 0; i < arraysize(kSupportedPacMimeTypes); ++i) {
38 if (LowerCaseEqualsASCII(mime_type, kSupportedPacMimeTypes[i]))
39 return true;
40 }
41 return false;
42}
43
[email protected]677c90572008-12-10 09:03:1544} // namespace
45
46class ProxyScriptFetcherImpl : public ProxyScriptFetcher,
47 public URLRequest::Delegate {
48 public:
49 // Creates a ProxyScriptFetcher that issues requests through
50 // |url_request_context|. |url_request_context| must remain valid for the
51 // lifetime of ProxyScriptFetcherImpl.
52 explicit ProxyScriptFetcherImpl(URLRequestContext* url_request_context);
53
54 virtual ~ProxyScriptFetcherImpl();
55
56 // ProxyScriptFetcher methods:
57
58 virtual void Fetch(const GURL& url, std::string* bytes,
59 CompletionCallback* callback);
60 virtual void Cancel();
61
62 // URLRequest::Delegate methods:
63
64 virtual void OnAuthRequired(URLRequest* request,
65 AuthChallengeInfo* auth_info);
66 virtual void OnSSLCertificateError(URLRequest* request, int cert_error,
67 X509Certificate* cert);
68 virtual void OnReceivedRedirect(URLRequest* request, const GURL& to_url);
69 virtual void OnResponseStarted(URLRequest* request);
70 virtual void OnReadCompleted(URLRequest* request, int num_bytes);
71 virtual void OnResponseCompleted(URLRequest* request);
72
73 private:
74 // Read more bytes from the response.
75 void ReadBody(URLRequest* request);
76
77 // Called once the request has completed to notify the caller of
78 // |response_code_| and |response_bytes_|.
79 void FetchCompleted();
80
81 // Clear out the state for the current request.
82 void ResetCurRequestState();
83
84 // Callback for time-out task of request with id |id|.
85 void OnTimeout(int id);
86
87 // Factory for creating the time-out task. This takes care of revoking
88 // outstanding tasks when |this| is deleted.
89 ScopedRunnableMethodFactory<ProxyScriptFetcherImpl> task_factory_;
90
91 // The context used for making network requests.
92 URLRequestContext* url_request_context_;
93
94 // Buffer that URLRequest writes into.
95 enum { kBufSize = 4096 };
[email protected]9dea9e1f2009-01-29 00:30:4796 scoped_refptr<net::IOBuffer> buf_;
[email protected]677c90572008-12-10 09:03:1597
98 // The next ID to use for |cur_request_| (monotonically increasing).
99 int next_id_;
100
101 // The current (in progress) request, or NULL.
102 scoped_ptr<URLRequest> cur_request_;
103
104 // State for current request (only valid when |cur_request_| is not NULL):
105
106 // Unique ID for the current request.
107 int cur_request_id_;
108
109 // Callback to invoke on completion of the fetch.
110 CompletionCallback* callback_;
111
112 // Holds the error condition that was hit on the current request, or OK.
113 int result_code_;
114
115 // Holds the bytes read so far. Will not exceed |max_response_bytes|. This
116 // buffer is owned by the owner of |callback|.
117 std::string* result_bytes_;
118};
119
120ProxyScriptFetcherImpl::ProxyScriptFetcherImpl(
121 URLRequestContext* url_request_context)
122 : ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
123 url_request_context_(url_request_context),
[email protected]9dea9e1f2009-01-29 00:30:47124 buf_(new net::IOBuffer(kBufSize)),
[email protected]677c90572008-12-10 09:03:15125 next_id_(0),
126 cur_request_(NULL),
127 cur_request_id_(0),
128 callback_(NULL),
129 result_code_(OK),
130 result_bytes_(NULL) {
131 DCHECK(url_request_context);
132}
133
134ProxyScriptFetcherImpl::~ProxyScriptFetcherImpl() {
135 // The URLRequest's destructor will cancel the outstanding request, and
136 // ensure that the delegate (this) is not called again.
137}
138
139void ProxyScriptFetcherImpl::Fetch(const GURL& url,
140 std::string* bytes,
141 CompletionCallback* callback) {
142 // It is invalid to call Fetch() while a request is already in progress.
143 DCHECK(!cur_request_.get());
144
145 DCHECK(callback);
146 DCHECK(bytes);
147
148 cur_request_.reset(new URLRequest(url, this));
149 cur_request_->set_context(url_request_context_);
150 cur_request_->set_method("GET");
151
152 // Make sure that the PAC script is downloaded using a direct connection,
153 // to avoid circular dependencies (fetching is a part of proxy resolution).
154 cur_request_->set_load_flags(LOAD_BYPASS_PROXY);
155
156 // Save the caller's info for notification on completion.
157 callback_ = callback;
158 result_bytes_ = bytes;
159 result_bytes_->clear();
160
161 // Post a task to timeout this request if it takes too long.
162 cur_request_id_ = ++next_id_;
163 MessageLoop::current()->PostDelayedTask(FROM_HERE,
164 task_factory_.NewRunnableMethod(&ProxyScriptFetcherImpl::OnTimeout,
165 cur_request_id_),
166 static_cast<int>(max_duration_ms));
167
168 // Start the request.
169 cur_request_->Start();
170}
171
172void ProxyScriptFetcherImpl::Cancel() {
173 // ResetCurRequestState will free the URLRequest, which will cause
174 // cancellation.
175 ResetCurRequestState();
176}
177
178void ProxyScriptFetcherImpl::OnAuthRequired(URLRequest* request,
179 AuthChallengeInfo* auth_info) {
180 DCHECK(request == cur_request_.get());
181 // TODO(eroman):
[email protected]13a279e2009-04-13 17:32:37182 LOG(WARNING) << "Auth required to fetch PAC script, aborting.";
[email protected]677c90572008-12-10 09:03:15183 result_code_ = ERR_NOT_IMPLEMENTED;
184 request->CancelAuth();
185}
186
187void ProxyScriptFetcherImpl::OnSSLCertificateError(URLRequest* request,
188 int cert_error,
189 X509Certificate* cert) {
190 DCHECK(request == cur_request_.get());
[email protected]13a279e2009-04-13 17:32:37191 LOG(WARNING) << "SSL certificate error when fetching PAC script, aborting.";
[email protected]677c90572008-12-10 09:03:15192 // Certificate errors are in same space as net errors.
193 result_code_ = cert_error;
194 request->Cancel();
195}
196
197void ProxyScriptFetcherImpl::OnReceivedRedirect(URLRequest* request,
198 const GURL& to_url) {
199 DCHECK(request == cur_request_.get());
200 // OK, thanks for telling.
201}
202
203void ProxyScriptFetcherImpl::OnResponseStarted(URLRequest* request) {
204 DCHECK(request == cur_request_.get());
205
206 if (!request->status().is_success()) {
207 OnResponseCompleted(request);
208 return;
209 }
210
211 // Require HTTP responses to have a success status code.
212 if (request->url().SchemeIs("http") || request->url().SchemeIs("https")) {
[email protected]f0a51fb52009-03-05 12:46:38213 // NOTE about status codes: We are like Firefox 3 in this respect.
[email protected]677c90572008-12-10 09:03:15214 // {IE 7, Safari 3, Opera 9.5} do not care about the status code.
215 if (request->GetResponseCode() != 200) {
216 result_code_ = ERR_PAC_STATUS_NOT_OK;
217 request->Cancel();
218 return;
219 }
[email protected]13a279e2009-04-13 17:32:37220
221 // NOTE about mime types: We do not enforce mime types on PAC files.
222 // This is for compatibility with {IE 7, Firefox 3, Opera 9.5}. We will
223 // however log mismatches to help with debugging.
224 if (logging::GetMinLogLevel() <= logging::LOG_INFO) {
225 std::string mime_type;
226 cur_request_->GetMimeType(&mime_type);
[email protected]7417a7d2009-05-22 01:40:17227 if (!IsPacMimeType(mime_type)) {
[email protected]13a279e2009-04-13 17:32:37228 LOG(INFO) << "Fetched PAC script does not have a proper mime type: "
229 << mime_type;
230 }
231 }
[email protected]677c90572008-12-10 09:03:15232 }
233
234 ReadBody(request);
235}
236
237void ProxyScriptFetcherImpl::OnReadCompleted(URLRequest* request,
238 int num_bytes) {
239 DCHECK(request == cur_request_.get());
240 if (num_bytes > 0) {
241 // Enforce maximum size bound.
242 if (num_bytes + result_bytes_->size() >
243 static_cast<size_t>(max_response_bytes)) {
244 result_code_ = ERR_FILE_TOO_BIG;
245 request->Cancel();
246 return;
247 }
[email protected]9dea9e1f2009-01-29 00:30:47248 result_bytes_->append(buf_->data(), num_bytes);
[email protected]677c90572008-12-10 09:03:15249 ReadBody(request);
250 } else { // Error while reading, or EOF
251 OnResponseCompleted(request);
252 }
253}
254
255void ProxyScriptFetcherImpl::OnResponseCompleted(URLRequest* request) {
256 DCHECK(request == cur_request_.get());
257
258 // Use |result_code_| as the request's error if we have already set it to
259 // something specific.
260 if (result_code_ == OK && !request->status().is_success())
261 result_code_ = request->status().os_error();
262
263 FetchCompleted();
264}
265
266void ProxyScriptFetcherImpl::ReadBody(URLRequest* request) {
267 int num_bytes;
268 if (request->Read(buf_, kBufSize, &num_bytes)) {
269 OnReadCompleted(request, num_bytes);
270 } else if (!request->status().is_io_pending()) {
271 // Read failed synchronously.
272 OnResponseCompleted(request);
273 }
274}
275
276void ProxyScriptFetcherImpl::FetchCompleted() {
277 // On error, the caller expects empty string for bytes.
278 if (result_code_ != OK)
279 result_bytes_->clear();
280
281 int result_code = result_code_;
282 CompletionCallback* callback = callback_;
283
284 ResetCurRequestState();
285
286 callback->Run(result_code);
287}
288
289void ProxyScriptFetcherImpl::ResetCurRequestState() {
290 cur_request_.reset();
291 cur_request_id_ = 0;
292 callback_ = NULL;
293 result_code_ = OK;
294 result_bytes_ = NULL;
295}
296
297void ProxyScriptFetcherImpl::OnTimeout(int id) {
298 // Timeout tasks may outlive the URLRequest they reference. Make sure it
299 // is still applicable.
300 if (cur_request_id_ != id)
301 return;
302
303 DCHECK(cur_request_.get());
304 result_code_ = ERR_TIMED_OUT;
305 cur_request_->Cancel();
306}
307
308// static
309ProxyScriptFetcher* ProxyScriptFetcher::Create(
310 URLRequestContext* url_request_context) {
311 return new ProxyScriptFetcherImpl(url_request_context);
312}
313
314// static
315int ProxyScriptFetcher::SetTimeoutConstraintForUnittest(
316 int timeout_ms) {
317 int prev = max_duration_ms;
318 max_duration_ms = timeout_ms;
319 return prev;
320}
321
322// static
323size_t ProxyScriptFetcher::SetSizeConstraintForUnittest(size_t size_bytes) {
324 size_t prev = max_response_bytes;
325 max_response_bytes = size_bytes;
326 return prev;
327}
328
329} // namespace net