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