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/location.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "net/base/auth.h"
13 #include "net/base/host_port_pair.h"
14 #include "net/base/load_flags.h"
15 #include "net/base/net_errors.h"
16 #include "net/base/net_util.h"
17 #include "net/ftp/ftp_auth_cache.h"
18 #include "net/ftp/ftp_response_info.h"
19 #include "net/ftp/ftp_transaction_factory.h"
20 #include "net/http/http_response_headers.h"
21 #include "net/http/http_transaction_factory.h"
22 #include "net/url_request/url_request.h"
23 #include "net/url_request/url_request_context.h"
24 #include "net/url_request/url_request_error_job.h"
28 URLRequestFtpJob::URLRequestFtpJob(
30 NetworkDelegate
* network_delegate
,
31 FtpTransactionFactory
* ftp_transaction_factory
,
32 FtpAuthCache
* ftp_auth_cache
)
33 : URLRequestJob(request
, network_delegate
),
34 priority_(DEFAULT_PRIORITY
),
35 proxy_service_(request_
->context()->proxy_service()),
37 http_response_info_(NULL
),
38 read_in_progress_(false),
39 ftp_transaction_factory_(ftp_transaction_factory
),
40 ftp_auth_cache_(ftp_auth_cache
),
42 DCHECK(proxy_service_
);
43 DCHECK(ftp_transaction_factory
);
44 DCHECK(ftp_auth_cache
);
47 URLRequestFtpJob::~URLRequestFtpJob() {
49 proxy_service_
->CancelPacRequest(pac_request_
);
52 bool URLRequestFtpJob::IsSafeRedirect(const GURL
& location
) {
53 // Disallow all redirects.
57 bool URLRequestFtpJob::GetMimeType(std::string
* mime_type
) const {
58 if (proxy_info_
.is_direct()) {
59 if (ftp_transaction_
->GetResponseInfo()->is_directory_listing
) {
60 *mime_type
= "text/vnd.chromium.ftp-dir";
64 // No special handling of MIME type is needed. As opposed to direct FTP
65 // transaction, we do not get a raw directory listing to parse.
66 return http_transaction_
->GetResponseInfo()->
67 headers
->GetMimeType(mime_type
);
72 void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo
* info
) {
73 if (http_response_info_
)
74 *info
= *http_response_info_
;
77 HostPortPair
URLRequestFtpJob::GetSocketAddress() const {
78 if (proxy_info_
.is_direct()) {
79 if (!ftp_transaction_
)
80 return HostPortPair();
81 return ftp_transaction_
->GetResponseInfo()->socket_address
;
83 if (!http_transaction_
)
84 return HostPortPair();
85 return http_transaction_
->GetResponseInfo()->socket_address
;
89 void URLRequestFtpJob::SetPriority(RequestPriority priority
) {
91 if (http_transaction_
)
92 http_transaction_
->SetPriority(priority
);
95 void URLRequestFtpJob::Start() {
96 DCHECK(!pac_request_
);
97 DCHECK(!ftp_transaction_
);
98 DCHECK(!http_transaction_
);
101 if (request_
->load_flags() & LOAD_BYPASS_PROXY
) {
102 proxy_info_
.UseDirect();
104 DCHECK_EQ(request_
->context()->proxy_service(), proxy_service_
);
105 rv
= proxy_service_
->ResolveProxy(
107 request_
->load_flags(),
109 base::Bind(&URLRequestFtpJob::OnResolveProxyComplete
,
110 base::Unretained(this)),
113 request_
->net_log());
115 if (rv
== ERR_IO_PENDING
)
118 OnResolveProxyComplete(rv
);
121 void URLRequestFtpJob::Kill() {
122 if (ftp_transaction_
)
123 ftp_transaction_
.reset();
124 if (http_transaction_
)
125 http_transaction_
.reset();
126 URLRequestJob::Kill();
127 weak_factory_
.InvalidateWeakPtrs();
130 void URLRequestFtpJob::OnResolveProxyComplete(int result
) {
134 OnStartCompletedAsync(result
);
138 // Remove unsupported proxies from the list.
139 proxy_info_
.RemoveProxiesWithoutScheme(
140 ProxyServer::SCHEME_DIRECT
|
141 ProxyServer::SCHEME_HTTP
|
142 ProxyServer::SCHEME_HTTPS
);
144 // TODO(phajdan.jr): Implement proxy fallback, http://crbug.com/171495 .
145 if (proxy_info_
.is_direct())
146 StartFtpTransaction();
147 else if (proxy_info_
.is_http() || proxy_info_
.is_https())
148 StartHttpTransaction();
150 OnStartCompletedAsync(ERR_NO_SUPPORTED_PROXIES
);
153 void URLRequestFtpJob::StartFtpTransaction() {
154 // Create a transaction.
155 DCHECK(!ftp_transaction_
);
157 ftp_request_info_
.url
= request_
->url();
158 ftp_transaction_
.reset(ftp_transaction_factory_
->CreateTransaction());
160 // No matter what, we want to report our status as IO pending since we will
161 // be notifying our consumer asynchronously via OnStartCompleted.
162 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING
, 0));
164 if (ftp_transaction_
) {
165 rv
= ftp_transaction_
->Start(
167 base::Bind(&URLRequestFtpJob::OnStartCompleted
,
168 base::Unretained(this)),
169 request_
->net_log());
170 if (rv
== ERR_IO_PENDING
)
175 // The transaction started synchronously, but we need to notify the
176 // URLRequest delegate via the message loop.
177 OnStartCompletedAsync(rv
);
180 void URLRequestFtpJob::StartHttpTransaction() {
181 // Create a transaction.
182 DCHECK(!http_transaction_
);
184 // Do not cache FTP responses sent through HTTP proxy.
185 request_
->SetLoadFlags(request_
->load_flags() |
187 LOAD_DO_NOT_SAVE_COOKIES
|
188 LOAD_DO_NOT_SEND_COOKIES
);
190 http_request_info_
.url
= request_
->url();
191 http_request_info_
.method
= request_
->method();
192 http_request_info_
.load_flags
= request_
->load_flags();
194 int rv
= request_
->context()->http_transaction_factory()->CreateTransaction(
195 priority_
, &http_transaction_
);
197 rv
= http_transaction_
->Start(
199 base::Bind(&URLRequestFtpJob::OnStartCompleted
,
200 base::Unretained(this)),
201 request_
->net_log());
202 if (rv
== ERR_IO_PENDING
)
205 // The transaction started synchronously, but we need to notify the
206 // URLRequest delegate via the message loop.
207 OnStartCompletedAsync(rv
);
210 void URLRequestFtpJob::OnStartCompleted(int result
) {
211 // Clear the IO_PENDING status
212 SetStatus(URLRequestStatus());
214 // Note that ftp_transaction_ may be NULL due to a creation failure.
215 if (ftp_transaction_
) {
216 // FTP obviously doesn't have HTTP Content-Length header. We have to pass
217 // the content size information manually.
218 set_expected_content_size(
219 ftp_transaction_
->GetResponseInfo()->expected_content_size
);
223 if (http_transaction_
) {
224 http_response_info_
= http_transaction_
->GetResponseInfo();
225 SetProxyServer(http_response_info_
->proxy_server
);
227 if (http_response_info_
->headers
->response_code() == 401 ||
228 http_response_info_
->headers
->response_code() == 407) {
229 HandleAuthNeededResponse();
233 NotifyHeadersComplete();
234 } else if (ftp_transaction_
&&
235 ftp_transaction_
->GetResponseInfo()->needs_auth
) {
236 HandleAuthNeededResponse();
239 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED
, result
));
243 void URLRequestFtpJob::OnStartCompletedAsync(int result
) {
244 base::ThreadTaskRunnerHandle::Get()->PostTask(
245 FROM_HERE
, base::Bind(&URLRequestFtpJob::OnStartCompleted
,
246 weak_factory_
.GetWeakPtr(), result
));
249 void URLRequestFtpJob::OnReadCompleted(int result
) {
250 read_in_progress_
= false;
252 NotifyDone(URLRequestStatus());
253 } else if (result
< 0) {
254 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED
, result
));
256 // Clear the IO_PENDING status
257 SetStatus(URLRequestStatus());
259 NotifyReadComplete(result
);
262 void URLRequestFtpJob::RestartTransactionWithAuth() {
263 DCHECK(auth_data_
.get() && auth_data_
->state
== AUTH_STATE_HAVE_AUTH
);
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));
270 if (proxy_info_
.is_direct()) {
271 rv
= ftp_transaction_
->RestartWithAuth(
272 auth_data_
->credentials
,
273 base::Bind(&URLRequestFtpJob::OnStartCompleted
,
274 base::Unretained(this)));
276 rv
= http_transaction_
->RestartWithAuth(
277 auth_data_
->credentials
,
278 base::Bind(&URLRequestFtpJob::OnStartCompleted
,
279 base::Unretained(this)));
281 if (rv
== ERR_IO_PENDING
)
284 OnStartCompletedAsync(rv
);
287 LoadState
URLRequestFtpJob::GetLoadState() const {
288 if (proxy_info_
.is_direct()) {
289 return ftp_transaction_
?
290 ftp_transaction_
->GetLoadState() : LOAD_STATE_IDLE
;
292 return http_transaction_
?
293 http_transaction_
->GetLoadState() : LOAD_STATE_IDLE
;
297 bool URLRequestFtpJob::NeedsAuth() {
298 return auth_data_
.get() && auth_data_
->state
== AUTH_STATE_NEED_AUTH
;
301 void URLRequestFtpJob::GetAuthChallengeInfo(
302 scoped_refptr
<AuthChallengeInfo
>* result
) {
305 if (http_response_info_
) {
306 *result
= http_response_info_
->auth_challenge
;
310 scoped_refptr
<AuthChallengeInfo
> auth_info(new AuthChallengeInfo
);
311 auth_info
->is_proxy
= false;
312 auth_info
->challenger
= HostPortPair::FromURL(request_
->url());
313 // scheme and realm are kept empty.
314 DCHECK(auth_info
->scheme
.empty());
315 DCHECK(auth_info
->realm
.empty());
316 result
->swap(auth_info
);
319 void URLRequestFtpJob::SetAuth(const AuthCredentials
& credentials
) {
320 DCHECK(ftp_transaction_
|| http_transaction_
);
323 auth_data_
->state
= AUTH_STATE_HAVE_AUTH
;
324 auth_data_
->credentials
= credentials
;
326 if (ftp_transaction_
) {
327 ftp_auth_cache_
->Add(request_
->url().GetOrigin(),
328 auth_data_
->credentials
);
331 RestartTransactionWithAuth();
334 void URLRequestFtpJob::CancelAuth() {
335 DCHECK(ftp_transaction_
|| http_transaction_
);
338 auth_data_
->state
= AUTH_STATE_CANCELED
;
340 // Once the auth is cancelled, we proceed with the request as though
341 // there were no auth. Schedule this for later so that we don't cause
342 // any recursing into the caller as a result of this call.
343 OnStartCompletedAsync(OK
);
346 UploadProgress
URLRequestFtpJob::GetUploadProgress() const {
347 return UploadProgress();
350 bool URLRequestFtpJob::ReadRawData(IOBuffer
* buf
,
353 DCHECK_NE(buf_size
, 0);
355 DCHECK(!read_in_progress_
);
358 if (proxy_info_
.is_direct()) {
359 rv
= ftp_transaction_
->Read(buf
, buf_size
,
360 base::Bind(&URLRequestFtpJob::OnReadCompleted
,
361 base::Unretained(this)));
363 rv
= http_transaction_
->Read(buf
, buf_size
,
364 base::Bind(&URLRequestFtpJob::OnReadCompleted
,
365 base::Unretained(this)));
373 if (rv
== ERR_IO_PENDING
) {
374 read_in_progress_
= true;
375 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING
, 0));
377 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED
, rv
));
382 void URLRequestFtpJob::HandleAuthNeededResponse() {
383 GURL origin
= request_
->url().GetOrigin();
385 if (auth_data_
.get()) {
386 if (auth_data_
->state
== AUTH_STATE_CANCELED
) {
387 NotifyHeadersComplete();
391 if (ftp_transaction_
&& auth_data_
->state
== AUTH_STATE_HAVE_AUTH
)
392 ftp_auth_cache_
->Remove(origin
, auth_data_
->credentials
);
394 auth_data_
= new AuthData
;
396 auth_data_
->state
= AUTH_STATE_NEED_AUTH
;
398 FtpAuthCache::Entry
* cached_auth
= NULL
;
399 if (ftp_transaction_
&& ftp_transaction_
->GetResponseInfo()->needs_auth
)
400 cached_auth
= ftp_auth_cache_
->Lookup(origin
);
402 // Retry using cached auth data.
403 SetAuth(cached_auth
->credentials
);
405 // Prompt for a username/password.
406 NotifyHeadersComplete();