OLD | NEW |
| (Empty) |
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/url_request/url_request_ftp_job.h" | |
6 | |
7 #include "base/compiler_specific.h" | |
8 #include "base/message_loop/message_loop.h" | |
9 #include "base/profiler/scoped_tracker.h" | |
10 #include "base/strings/utf_string_conversions.h" | |
11 #include "net/base/auth.h" | |
12 #include "net/base/host_port_pair.h" | |
13 #include "net/base/load_flags.h" | |
14 #include "net/base/net_errors.h" | |
15 #include "net/base/net_util.h" | |
16 #include "net/ftp/ftp_auth_cache.h" | |
17 #include "net/ftp/ftp_response_info.h" | |
18 #include "net/ftp/ftp_transaction_factory.h" | |
19 #include "net/http/http_response_headers.h" | |
20 #include "net/http/http_transaction_factory.h" | |
21 #include "net/url_request/url_request.h" | |
22 #include "net/url_request/url_request_context.h" | |
23 #include "net/url_request/url_request_error_job.h" | |
24 | |
25 namespace net { | |
26 | |
27 URLRequestFtpJob::URLRequestFtpJob( | |
28 URLRequest* request, | |
29 NetworkDelegate* network_delegate, | |
30 FtpTransactionFactory* ftp_transaction_factory, | |
31 FtpAuthCache* ftp_auth_cache) | |
32 : URLRequestJob(request, network_delegate), | |
33 priority_(DEFAULT_PRIORITY), | |
34 proxy_service_(request_->context()->proxy_service()), | |
35 pac_request_(NULL), | |
36 http_response_info_(NULL), | |
37 read_in_progress_(false), | |
38 ftp_transaction_factory_(ftp_transaction_factory), | |
39 ftp_auth_cache_(ftp_auth_cache), | |
40 weak_factory_(this) { | |
41 DCHECK(proxy_service_); | |
42 DCHECK(ftp_transaction_factory); | |
43 DCHECK(ftp_auth_cache); | |
44 } | |
45 | |
46 URLRequestFtpJob::~URLRequestFtpJob() { | |
47 if (pac_request_) | |
48 proxy_service_->CancelPacRequest(pac_request_); | |
49 } | |
50 | |
51 bool URLRequestFtpJob::IsSafeRedirect(const GURL& location) { | |
52 // Disallow all redirects. | |
53 return false; | |
54 } | |
55 | |
56 bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const { | |
57 if (proxy_info_.is_direct()) { | |
58 if (ftp_transaction_->GetResponseInfo()->is_directory_listing) { | |
59 *mime_type = "text/vnd.chromium.ftp-dir"; | |
60 return true; | |
61 } | |
62 } else { | |
63 // No special handling of MIME type is needed. As opposed to direct FTP | |
64 // transaction, we do not get a raw directory listing to parse. | |
65 return http_transaction_->GetResponseInfo()-> | |
66 headers->GetMimeType(mime_type); | |
67 } | |
68 return false; | |
69 } | |
70 | |
71 void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo* info) { | |
72 if (http_response_info_) | |
73 *info = *http_response_info_; | |
74 } | |
75 | |
76 HostPortPair URLRequestFtpJob::GetSocketAddress() const { | |
77 if (proxy_info_.is_direct()) { | |
78 if (!ftp_transaction_) | |
79 return HostPortPair(); | |
80 return ftp_transaction_->GetResponseInfo()->socket_address; | |
81 } else { | |
82 if (!http_transaction_) | |
83 return HostPortPair(); | |
84 return http_transaction_->GetResponseInfo()->socket_address; | |
85 } | |
86 } | |
87 | |
88 void URLRequestFtpJob::SetPriority(RequestPriority priority) { | |
89 priority_ = priority; | |
90 if (http_transaction_) | |
91 http_transaction_->SetPriority(priority); | |
92 } | |
93 | |
94 void URLRequestFtpJob::Start() { | |
95 DCHECK(!pac_request_); | |
96 DCHECK(!ftp_transaction_); | |
97 DCHECK(!http_transaction_); | |
98 | |
99 int rv = OK; | |
100 if (request_->load_flags() & LOAD_BYPASS_PROXY) { | |
101 proxy_info_.UseDirect(); | |
102 } else { | |
103 DCHECK_EQ(request_->context()->proxy_service(), proxy_service_); | |
104 rv = proxy_service_->ResolveProxy( | |
105 request_->url(), | |
106 request_->load_flags(), | |
107 &proxy_info_, | |
108 base::Bind(&URLRequestFtpJob::OnResolveProxyComplete, | |
109 base::Unretained(this)), | |
110 &pac_request_, | |
111 NULL, | |
112 request_->net_log()); | |
113 | |
114 if (rv == ERR_IO_PENDING) | |
115 return; | |
116 } | |
117 OnResolveProxyComplete(rv); | |
118 } | |
119 | |
120 void URLRequestFtpJob::Kill() { | |
121 if (ftp_transaction_) | |
122 ftp_transaction_.reset(); | |
123 if (http_transaction_) | |
124 http_transaction_.reset(); | |
125 URLRequestJob::Kill(); | |
126 weak_factory_.InvalidateWeakPtrs(); | |
127 } | |
128 | |
129 void URLRequestFtpJob::OnResolveProxyComplete(int result) { | |
130 pac_request_ = NULL; | |
131 | |
132 if (result != OK) { | |
133 OnStartCompletedAsync(result); | |
134 return; | |
135 } | |
136 | |
137 // Remove unsupported proxies from the list. | |
138 proxy_info_.RemoveProxiesWithoutScheme( | |
139 ProxyServer::SCHEME_DIRECT | | |
140 ProxyServer::SCHEME_HTTP | | |
141 ProxyServer::SCHEME_HTTPS); | |
142 | |
143 // TODO(phajdan.jr): Implement proxy fallback, http://crbug.com/171495 . | |
144 if (proxy_info_.is_direct()) | |
145 StartFtpTransaction(); | |
146 else if (proxy_info_.is_http() || proxy_info_.is_https()) | |
147 StartHttpTransaction(); | |
148 else | |
149 OnStartCompletedAsync(ERR_NO_SUPPORTED_PROXIES); | |
150 } | |
151 | |
152 void URLRequestFtpJob::StartFtpTransaction() { | |
153 // Create a transaction. | |
154 DCHECK(!ftp_transaction_); | |
155 | |
156 ftp_request_info_.url = request_->url(); | |
157 ftp_transaction_.reset(ftp_transaction_factory_->CreateTransaction()); | |
158 | |
159 // No matter what, we want to report our status as IO pending since we will | |
160 // be notifying our consumer asynchronously via OnStartCompleted. | |
161 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); | |
162 int rv; | |
163 if (ftp_transaction_) { | |
164 rv = ftp_transaction_->Start( | |
165 &ftp_request_info_, | |
166 base::Bind(&URLRequestFtpJob::OnStartCompleted, | |
167 base::Unretained(this)), | |
168 request_->net_log()); | |
169 if (rv == ERR_IO_PENDING) | |
170 return; | |
171 } else { | |
172 rv = ERR_FAILED; | |
173 } | |
174 // The transaction started synchronously, but we need to notify the | |
175 // URLRequest delegate via the message loop. | |
176 OnStartCompletedAsync(rv); | |
177 } | |
178 | |
179 void URLRequestFtpJob::StartHttpTransaction() { | |
180 // Create a transaction. | |
181 DCHECK(!http_transaction_); | |
182 | |
183 // Do not cache FTP responses sent through HTTP proxy. | |
184 request_->SetLoadFlags(request_->load_flags() | | |
185 LOAD_DISABLE_CACHE | | |
186 LOAD_DO_NOT_SAVE_COOKIES | | |
187 LOAD_DO_NOT_SEND_COOKIES); | |
188 | |
189 http_request_info_.url = request_->url(); | |
190 http_request_info_.method = request_->method(); | |
191 http_request_info_.load_flags = request_->load_flags(); | |
192 | |
193 int rv = request_->context()->http_transaction_factory()->CreateTransaction( | |
194 priority_, &http_transaction_); | |
195 if (rv == OK) { | |
196 rv = http_transaction_->Start( | |
197 &http_request_info_, | |
198 base::Bind(&URLRequestFtpJob::OnStartCompleted, | |
199 base::Unretained(this)), | |
200 request_->net_log()); | |
201 if (rv == ERR_IO_PENDING) | |
202 return; | |
203 } | |
204 // The transaction started synchronously, but we need to notify the | |
205 // URLRequest delegate via the message loop. | |
206 OnStartCompletedAsync(rv); | |
207 } | |
208 | |
209 void URLRequestFtpJob::OnStartCompleted(int result) { | |
210 // Clear the IO_PENDING status | |
211 SetStatus(URLRequestStatus()); | |
212 | |
213 // Note that ftp_transaction_ may be NULL due to a creation failure. | |
214 if (ftp_transaction_) { | |
215 // FTP obviously doesn't have HTTP Content-Length header. We have to pass | |
216 // the content size information manually. | |
217 set_expected_content_size( | |
218 ftp_transaction_->GetResponseInfo()->expected_content_size); | |
219 } | |
220 | |
221 if (result == OK) { | |
222 if (http_transaction_) { | |
223 http_response_info_ = http_transaction_->GetResponseInfo(); | |
224 SetProxyServer(http_response_info_->proxy_server); | |
225 | |
226 if (http_response_info_->headers->response_code() == 401 || | |
227 http_response_info_->headers->response_code() == 407) { | |
228 HandleAuthNeededResponse(); | |
229 return; | |
230 } | |
231 } | |
232 NotifyHeadersComplete(); | |
233 } else if (ftp_transaction_ && | |
234 ftp_transaction_->GetResponseInfo()->needs_auth) { | |
235 HandleAuthNeededResponse(); | |
236 return; | |
237 } else { | |
238 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); | |
239 } | |
240 } | |
241 | |
242 void URLRequestFtpJob::OnStartCompletedAsync(int result) { | |
243 base::MessageLoop::current()->PostTask( | |
244 FROM_HERE, | |
245 base::Bind(&URLRequestFtpJob::OnStartCompleted, | |
246 weak_factory_.GetWeakPtr(), result)); | |
247 } | |
248 | |
249 void URLRequestFtpJob::OnReadCompleted(int result) { | |
250 read_in_progress_ = false; | |
251 if (result == 0) { | |
252 NotifyDone(URLRequestStatus()); | |
253 } else if (result < 0) { | |
254 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); | |
255 } else { | |
256 // Clear the IO_PENDING status | |
257 SetStatus(URLRequestStatus()); | |
258 } | |
259 NotifyReadComplete(result); | |
260 } | |
261 | |
262 void URLRequestFtpJob::RestartTransactionWithAuth() { | |
263 DCHECK(auth_data_.get() && auth_data_->state == AUTH_STATE_HAVE_AUTH); | |
264 | |
265 // No matter what, we want to report our status as IO pending since we will | |
266 // be notifying our consumer asynchronously via OnStartCompleted. | |
267 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); | |
268 | |
269 int rv; | |
270 if (proxy_info_.is_direct()) { | |
271 rv = ftp_transaction_->RestartWithAuth( | |
272 auth_data_->credentials, | |
273 base::Bind(&URLRequestFtpJob::OnStartCompleted, | |
274 base::Unretained(this))); | |
275 } else { | |
276 rv = http_transaction_->RestartWithAuth( | |
277 auth_data_->credentials, | |
278 base::Bind(&URLRequestFtpJob::OnStartCompleted, | |
279 base::Unretained(this))); | |
280 } | |
281 if (rv == ERR_IO_PENDING) | |
282 return; | |
283 | |
284 OnStartCompletedAsync(rv); | |
285 } | |
286 | |
287 LoadState URLRequestFtpJob::GetLoadState() const { | |
288 // TODO(pkasting): Remove ScopedTracker below once crbug.com/455952 is | |
289 // fixed. | |
290 tracked_objects::ScopedTracker tracking_profile( | |
291 FROM_HERE_WITH_EXPLICIT_FUNCTION( | |
292 "455952 URLRequestFtpJob::GetLoadState")); | |
293 if (proxy_info_.is_direct()) { | |
294 return ftp_transaction_ ? | |
295 ftp_transaction_->GetLoadState() : LOAD_STATE_IDLE; | |
296 } else { | |
297 return http_transaction_ ? | |
298 http_transaction_->GetLoadState() : LOAD_STATE_IDLE; | |
299 } | |
300 } | |
301 | |
302 bool URLRequestFtpJob::NeedsAuth() { | |
303 return auth_data_.get() && auth_data_->state == AUTH_STATE_NEED_AUTH; | |
304 } | |
305 | |
306 void URLRequestFtpJob::GetAuthChallengeInfo( | |
307 scoped_refptr<AuthChallengeInfo>* result) { | |
308 DCHECK(NeedsAuth()); | |
309 | |
310 if (http_response_info_) { | |
311 *result = http_response_info_->auth_challenge; | |
312 return; | |
313 } | |
314 | |
315 scoped_refptr<AuthChallengeInfo> auth_info(new AuthChallengeInfo); | |
316 auth_info->is_proxy = false; | |
317 auth_info->challenger = HostPortPair::FromURL(request_->url()); | |
318 // scheme and realm are kept empty. | |
319 DCHECK(auth_info->scheme.empty()); | |
320 DCHECK(auth_info->realm.empty()); | |
321 result->swap(auth_info); | |
322 } | |
323 | |
324 void URLRequestFtpJob::SetAuth(const AuthCredentials& credentials) { | |
325 DCHECK(ftp_transaction_ || http_transaction_); | |
326 DCHECK(NeedsAuth()); | |
327 | |
328 auth_data_->state = AUTH_STATE_HAVE_AUTH; | |
329 auth_data_->credentials = credentials; | |
330 | |
331 if (ftp_transaction_) { | |
332 ftp_auth_cache_->Add(request_->url().GetOrigin(), | |
333 auth_data_->credentials); | |
334 } | |
335 | |
336 RestartTransactionWithAuth(); | |
337 } | |
338 | |
339 void URLRequestFtpJob::CancelAuth() { | |
340 DCHECK(ftp_transaction_ || http_transaction_); | |
341 DCHECK(NeedsAuth()); | |
342 | |
343 auth_data_->state = AUTH_STATE_CANCELED; | |
344 | |
345 // Once the auth is cancelled, we proceed with the request as though | |
346 // there were no auth. Schedule this for later so that we don't cause | |
347 // any recursing into the caller as a result of this call. | |
348 OnStartCompletedAsync(OK); | |
349 } | |
350 | |
351 UploadProgress URLRequestFtpJob::GetUploadProgress() const { | |
352 return UploadProgress(); | |
353 } | |
354 | |
355 bool URLRequestFtpJob::ReadRawData(IOBuffer* buf, | |
356 int buf_size, | |
357 int *bytes_read) { | |
358 // TODO(vadimt): Remove ScopedTracker below once crbug.com/423948 is fixed. | |
359 tracked_objects::ScopedTracker tracking_profile( | |
360 FROM_HERE_WITH_EXPLICIT_FUNCTION("423948 URLRequestFtpJob::ReadRawData")); | |
361 | |
362 DCHECK_NE(buf_size, 0); | |
363 DCHECK(bytes_read); | |
364 DCHECK(!read_in_progress_); | |
365 | |
366 int rv; | |
367 if (proxy_info_.is_direct()) { | |
368 rv = ftp_transaction_->Read(buf, buf_size, | |
369 base::Bind(&URLRequestFtpJob::OnReadCompleted, | |
370 base::Unretained(this))); | |
371 } else { | |
372 rv = http_transaction_->Read(buf, buf_size, | |
373 base::Bind(&URLRequestFtpJob::OnReadCompleted, | |
374 base::Unretained(this))); | |
375 } | |
376 | |
377 if (rv >= 0) { | |
378 *bytes_read = rv; | |
379 return true; | |
380 } | |
381 | |
382 if (rv == ERR_IO_PENDING) { | |
383 read_in_progress_ = true; | |
384 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); | |
385 } else { | |
386 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); | |
387 } | |
388 return false; | |
389 } | |
390 | |
391 void URLRequestFtpJob::HandleAuthNeededResponse() { | |
392 GURL origin = request_->url().GetOrigin(); | |
393 | |
394 if (auth_data_.get()) { | |
395 if (auth_data_->state == AUTH_STATE_CANCELED) { | |
396 NotifyHeadersComplete(); | |
397 return; | |
398 } | |
399 | |
400 if (ftp_transaction_ && auth_data_->state == AUTH_STATE_HAVE_AUTH) | |
401 ftp_auth_cache_->Remove(origin, auth_data_->credentials); | |
402 } else { | |
403 auth_data_ = new AuthData; | |
404 } | |
405 auth_data_->state = AUTH_STATE_NEED_AUTH; | |
406 | |
407 FtpAuthCache::Entry* cached_auth = NULL; | |
408 if (ftp_transaction_ && ftp_transaction_->GetResponseInfo()->needs_auth) | |
409 cached_auth = ftp_auth_cache_->Lookup(origin); | |
410 if (cached_auth) { | |
411 // Retry using cached auth data. | |
412 SetAuth(cached_auth->credentials); | |
413 } else { | |
414 // Prompt for a username/password. | |
415 NotifyHeadersComplete(); | |
416 } | |
417 } | |
418 | |
419 } // namespace net | |
OLD | NEW |