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.
5 #include "net/url_request/url_request_ftp_job.h"
7 #include "base/compiler_specific.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "net/base/auth.h"
11 #include "net/base/host_port_pair.h"
12 #include "net/base/load_flags.h"
13 #include "net/base/net_errors.h"
14 #include "net/base/net_util.h"
15 #include "net/ftp/ftp_auth_cache.h"
16 #include "net/ftp/ftp_response_info.h"
17 #include "net/ftp/ftp_transaction_factory.h"
18 #include "net/http/http_response_headers.h"
19 #include "net/http/http_transaction_factory.h"
20 #include "net/url_request/url_request.h"
21 #include "net/url_request/url_request_context.h"
22 #include "net/url_request/url_request_error_job.h"
26 URLRequestFtpJob::URLRequestFtpJob(
28 NetworkDelegate
* network_delegate
,
29 FtpTransactionFactory
* ftp_transaction_factory
,
30 FtpAuthCache
* ftp_auth_cache
)
31 : URLRequestJob(request
, network_delegate
),
32 priority_(DEFAULT_PRIORITY
),
33 proxy_service_(request_
->context()->proxy_service()),
35 http_response_info_(NULL
),
36 read_in_progress_(false),
37 ftp_transaction_factory_(ftp_transaction_factory
),
38 ftp_auth_cache_(ftp_auth_cache
),
40 DCHECK(proxy_service_
);
41 DCHECK(ftp_transaction_factory
);
42 DCHECK(ftp_auth_cache
);
45 URLRequestFtpJob::~URLRequestFtpJob() {
47 proxy_service_
->CancelPacRequest(pac_request_
);
50 bool URLRequestFtpJob::IsSafeRedirect(const GURL
& location
) {
51 // Disallow all redirects.
55 bool URLRequestFtpJob::GetMimeType(std::string
* mime_type
) const {
56 if (proxy_info_
.is_direct()) {
57 if (ftp_transaction_
->GetResponseInfo()->is_directory_listing
) {
58 *mime_type
= "text/vnd.chromium.ftp-dir";
62 // No special handling of MIME type is needed. As opposed to direct FTP
63 // transaction, we do not get a raw directory listing to parse.
64 return http_transaction_
->GetResponseInfo()->
65 headers
->GetMimeType(mime_type
);
70 void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo
* info
) {
71 if (http_response_info_
)
72 *info
= *http_response_info_
;
75 HostPortPair
URLRequestFtpJob::GetSocketAddress() const {
76 if (proxy_info_
.is_direct()) {
77 if (!ftp_transaction_
)
78 return HostPortPair();
79 return ftp_transaction_
->GetResponseInfo()->socket_address
;
81 if (!http_transaction_
)
82 return HostPortPair();
83 return http_transaction_
->GetResponseInfo()->socket_address
;
87 void URLRequestFtpJob::SetPriority(RequestPriority priority
) {
89 if (http_transaction_
)
90 http_transaction_
->SetPriority(priority
);
93 void URLRequestFtpJob::Start() {
94 DCHECK(!pac_request_
);
95 DCHECK(!ftp_transaction_
);
96 DCHECK(!http_transaction_
);
99 if (request_
->load_flags() & LOAD_BYPASS_PROXY
) {
100 proxy_info_
.UseDirect();
102 DCHECK_EQ(request_
->context()->proxy_service(), proxy_service_
);
103 rv
= proxy_service_
->ResolveProxy(
105 request_
->load_flags(),
107 base::Bind(&URLRequestFtpJob::OnResolveProxyComplete
,
108 base::Unretained(this)),
111 request_
->net_log());
113 if (rv
== ERR_IO_PENDING
)
116 OnResolveProxyComplete(rv
);
119 void URLRequestFtpJob::Kill() {
120 if (ftp_transaction_
)
121 ftp_transaction_
.reset();
122 if (http_transaction_
)
123 http_transaction_
.reset();
124 URLRequestJob::Kill();
125 weak_factory_
.InvalidateWeakPtrs();
128 void URLRequestFtpJob::OnResolveProxyComplete(int result
) {
132 OnStartCompletedAsync(result
);
136 // Remove unsupported proxies from the list.
137 proxy_info_
.RemoveProxiesWithoutScheme(
138 ProxyServer::SCHEME_DIRECT
|
139 ProxyServer::SCHEME_HTTP
|
140 ProxyServer::SCHEME_HTTPS
);
142 // TODO(phajdan.jr): Implement proxy fallback, http://crbug.com/171495 .
143 if (proxy_info_
.is_direct())
144 StartFtpTransaction();
145 else if (proxy_info_
.is_http() || proxy_info_
.is_https())
146 StartHttpTransaction();
148 OnStartCompletedAsync(ERR_NO_SUPPORTED_PROXIES
);
151 void URLRequestFtpJob::StartFtpTransaction() {
152 // Create a transaction.
153 DCHECK(!ftp_transaction_
);
155 ftp_request_info_
.url
= request_
->url();
156 ftp_transaction_
.reset(ftp_transaction_factory_
->CreateTransaction());
158 // No matter what, we want to report our status as IO pending since we will
159 // be notifying our consumer asynchronously via OnStartCompleted.
160 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING
, 0));
162 if (ftp_transaction_
) {
163 rv
= ftp_transaction_
->Start(
165 base::Bind(&URLRequestFtpJob::OnStartCompleted
,
166 base::Unretained(this)),
167 request_
->net_log());
168 if (rv
== ERR_IO_PENDING
)
173 // The transaction started synchronously, but we need to notify the
174 // URLRequest delegate via the message loop.
175 OnStartCompletedAsync(rv
);
178 void URLRequestFtpJob::StartHttpTransaction() {
179 // Create a transaction.
180 DCHECK(!http_transaction_
);
182 // Do not cache FTP responses sent through HTTP proxy.
183 request_
->SetLoadFlags(request_
->load_flags() |
185 LOAD_DO_NOT_SAVE_COOKIES
|
186 LOAD_DO_NOT_SEND_COOKIES
);
188 http_request_info_
.url
= request_
->url();
189 http_request_info_
.method
= request_
->method();
190 http_request_info_
.load_flags
= request_
->load_flags();
192 int rv
= request_
->context()->http_transaction_factory()->CreateTransaction(
193 priority_
, &http_transaction_
);
195 rv
= http_transaction_
->Start(
197 base::Bind(&URLRequestFtpJob::OnStartCompleted
,
198 base::Unretained(this)),
199 request_
->net_log());
200 if (rv
== ERR_IO_PENDING
)
203 // The transaction started synchronously, but we need to notify the
204 // URLRequest delegate via the message loop.
205 OnStartCompletedAsync(rv
);
208 void URLRequestFtpJob::OnStartCompleted(int result
) {
209 // Clear the IO_PENDING status
210 SetStatus(URLRequestStatus());
212 // Note that ftp_transaction_ may be NULL due to a creation failure.
213 if (ftp_transaction_
) {
214 // FTP obviously doesn't have HTTP Content-Length header. We have to pass
215 // the content size information manually.
216 set_expected_content_size(
217 ftp_transaction_
->GetResponseInfo()->expected_content_size
);
221 if (http_transaction_
) {
222 http_response_info_
= http_transaction_
->GetResponseInfo();
223 SetProxyServer(http_response_info_
->proxy_server
);
225 if (http_response_info_
->headers
->response_code() == 401 ||
226 http_response_info_
->headers
->response_code() == 407) {
227 HandleAuthNeededResponse();
231 NotifyHeadersComplete();
232 } else if (ftp_transaction_
&&
233 ftp_transaction_
->GetResponseInfo()->needs_auth
) {
234 HandleAuthNeededResponse();
237 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED
, result
));
241 void URLRequestFtpJob::OnStartCompletedAsync(int result
) {
242 base::MessageLoop::current()->PostTask(
244 base::Bind(&URLRequestFtpJob::OnStartCompleted
,
245 weak_factory_
.GetWeakPtr(), result
));
248 void URLRequestFtpJob::OnReadCompleted(int result
) {
249 read_in_progress_
= false;
251 NotifyDone(URLRequestStatus());
252 } else if (result
< 0) {
253 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED
, result
));
255 // Clear the IO_PENDING status
256 SetStatus(URLRequestStatus());
258 NotifyReadComplete(result
);
261 void URLRequestFtpJob::RestartTransactionWithAuth() {
262 DCHECK(auth_data_
.get() && auth_data_
->state
== AUTH_STATE_HAVE_AUTH
);
264 // No matter what, we want to report our status as IO pending since we will
265 // be notifying our consumer asynchronously via OnStartCompleted.
266 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING
, 0));
269 if (proxy_info_
.is_direct()) {
270 rv
= ftp_transaction_
->RestartWithAuth(
271 auth_data_
->credentials
,
272 base::Bind(&URLRequestFtpJob::OnStartCompleted
,
273 base::Unretained(this)));
275 rv
= http_transaction_
->RestartWithAuth(
276 auth_data_
->credentials
,
277 base::Bind(&URLRequestFtpJob::OnStartCompleted
,
278 base::Unretained(this)));
280 if (rv
== ERR_IO_PENDING
)
283 OnStartCompletedAsync(rv
);
286 LoadState
URLRequestFtpJob::GetLoadState() const {
287 if (proxy_info_
.is_direct()) {
288 return ftp_transaction_
?
289 ftp_transaction_
->GetLoadState() : LOAD_STATE_IDLE
;
291 return http_transaction_
?
292 http_transaction_
->GetLoadState() : LOAD_STATE_IDLE
;
296 bool URLRequestFtpJob::NeedsAuth() {
297 return auth_data_
.get() && auth_data_
->state
== AUTH_STATE_NEED_AUTH
;
300 void URLRequestFtpJob::GetAuthChallengeInfo(
301 scoped_refptr
<AuthChallengeInfo
>* result
) {
304 if (http_response_info_
) {
305 *result
= http_response_info_
->auth_challenge
;
309 scoped_refptr
<AuthChallengeInfo
> auth_info(new AuthChallengeInfo
);
310 auth_info
->is_proxy
= false;
311 auth_info
->challenger
= HostPortPair::FromURL(request_
->url());
312 // scheme and realm are kept empty.
313 DCHECK(auth_info
->scheme
.empty());
314 DCHECK(auth_info
->realm
.empty());
315 result
->swap(auth_info
);
318 void URLRequestFtpJob::SetAuth(const AuthCredentials
& credentials
) {
319 DCHECK(ftp_transaction_
|| http_transaction_
);
322 auth_data_
->state
= AUTH_STATE_HAVE_AUTH
;
323 auth_data_
->credentials
= credentials
;
325 if (ftp_transaction_
) {
326 ftp_auth_cache_
->Add(request_
->url().GetOrigin(),
327 auth_data_
->credentials
);
330 RestartTransactionWithAuth();
333 void URLRequestFtpJob::CancelAuth() {
334 DCHECK(ftp_transaction_
|| http_transaction_
);
337 auth_data_
->state
= AUTH_STATE_CANCELED
;
339 // Once the auth is cancelled, we proceed with the request as though
340 // there were no auth. Schedule this for later so that we don't cause
341 // any recursing into the caller as a result of this call.
342 OnStartCompletedAsync(OK
);
345 UploadProgress
URLRequestFtpJob::GetUploadProgress() const {
346 return UploadProgress();
349 bool URLRequestFtpJob::ReadRawData(IOBuffer
* buf
,
352 DCHECK_NE(buf_size
, 0);
354 DCHECK(!read_in_progress_
);
357 if (proxy_info_
.is_direct()) {
358 rv
= ftp_transaction_
->Read(buf
, buf_size
,
359 base::Bind(&URLRequestFtpJob::OnReadCompleted
,
360 base::Unretained(this)));
362 rv
= http_transaction_
->Read(buf
, buf_size
,
363 base::Bind(&URLRequestFtpJob::OnReadCompleted
,
364 base::Unretained(this)));
372 if (rv
== ERR_IO_PENDING
) {
373 read_in_progress_
= true;
374 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING
, 0));
376 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED
, rv
));
381 void URLRequestFtpJob::HandleAuthNeededResponse() {
382 GURL origin
= request_
->url().GetOrigin();
384 if (auth_data_
.get()) {
385 if (auth_data_
->state
== AUTH_STATE_CANCELED
) {
386 NotifyHeadersComplete();
390 if (ftp_transaction_
&& auth_data_
->state
== AUTH_STATE_HAVE_AUTH
)
391 ftp_auth_cache_
->Remove(origin
, auth_data_
->credentials
);
393 auth_data_
= new AuthData
;
395 auth_data_
->state
= AUTH_STATE_NEED_AUTH
;
397 FtpAuthCache::Entry
* cached_auth
= NULL
;
398 if (ftp_transaction_
&& ftp_transaction_
->GetResponseInfo()->needs_auth
)
399 cached_auth
= ftp_auth_cache_
->Lookup(origin
);
401 // Retry using cached auth data.
402 SetAuth(cached_auth
->credentials
);
404 // Prompt for a username/password.
405 NotifyHeadersComplete();