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/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"
27 URLRequestFtpJob::URLRequestFtpJob(
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()),
36 http_response_info_(NULL
),
37 read_in_progress_(false),
38 ftp_transaction_factory_(ftp_transaction_factory
),
39 ftp_auth_cache_(ftp_auth_cache
),
41 DCHECK(proxy_service_
);
42 DCHECK(ftp_transaction_factory
);
43 DCHECK(ftp_auth_cache
);
46 URLRequestFtpJob::~URLRequestFtpJob() {
48 proxy_service_
->CancelPacRequest(pac_request_
);
51 bool URLRequestFtpJob::IsSafeRedirect(const GURL
& location
) {
52 // Disallow all redirects.
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";
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
);
71 void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo
* info
) {
72 if (http_response_info_
)
73 *info
= *http_response_info_
;
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
;
82 if (!http_transaction_
)
83 return HostPortPair();
84 return http_transaction_
->GetResponseInfo()->socket_address
;
88 void URLRequestFtpJob::SetPriority(RequestPriority priority
) {
90 if (http_transaction_
)
91 http_transaction_
->SetPriority(priority
);
94 void URLRequestFtpJob::Start() {
95 DCHECK(!pac_request_
);
96 DCHECK(!ftp_transaction_
);
97 DCHECK(!http_transaction_
);
100 if (request_
->load_flags() & LOAD_BYPASS_PROXY
) {
101 proxy_info_
.UseDirect();
103 DCHECK_EQ(request_
->context()->proxy_service(), proxy_service_
);
104 rv
= proxy_service_
->ResolveProxy(
106 request_
->load_flags(),
108 base::Bind(&URLRequestFtpJob::OnResolveProxyComplete
,
109 base::Unretained(this)),
112 request_
->net_log());
114 if (rv
== ERR_IO_PENDING
)
117 OnResolveProxyComplete(rv
);
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();
129 void URLRequestFtpJob::OnResolveProxyComplete(int result
) {
133 OnStartCompletedAsync(result
);
137 // Remove unsupported proxies from the list.
138 proxy_info_
.RemoveProxiesWithoutScheme(
139 ProxyServer::SCHEME_DIRECT
|
140 ProxyServer::SCHEME_HTTP
|
141 ProxyServer::SCHEME_HTTPS
);
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();
149 OnStartCompletedAsync(ERR_NO_SUPPORTED_PROXIES
);
152 void URLRequestFtpJob::StartFtpTransaction() {
153 // Create a transaction.
154 DCHECK(!ftp_transaction_
);
156 ftp_request_info_
.url
= request_
->url();
157 ftp_transaction_
.reset(ftp_transaction_factory_
->CreateTransaction());
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));
163 if (ftp_transaction_
) {
164 rv
= ftp_transaction_
->Start(
166 base::Bind(&URLRequestFtpJob::OnStartCompleted
,
167 base::Unretained(this)),
168 request_
->net_log());
169 if (rv
== ERR_IO_PENDING
)
174 // The transaction started synchronously, but we need to notify the
175 // URLRequest delegate via the message loop.
176 OnStartCompletedAsync(rv
);
179 void URLRequestFtpJob::StartHttpTransaction() {
180 // Create a transaction.
181 DCHECK(!http_transaction_
);
183 // Do not cache FTP responses sent through HTTP proxy.
184 request_
->SetLoadFlags(request_
->load_flags() |
186 LOAD_DO_NOT_SAVE_COOKIES
|
187 LOAD_DO_NOT_SEND_COOKIES
);
189 http_request_info_
.url
= request_
->url();
190 http_request_info_
.method
= request_
->method();
191 http_request_info_
.load_flags
= request_
->load_flags();
193 int rv
= request_
->context()->http_transaction_factory()->CreateTransaction(
194 priority_
, &http_transaction_
);
196 rv
= http_transaction_
->Start(
198 base::Bind(&URLRequestFtpJob::OnStartCompleted
,
199 base::Unretained(this)),
200 request_
->net_log());
201 if (rv
== ERR_IO_PENDING
)
204 // The transaction started synchronously, but we need to notify the
205 // URLRequest delegate via the message loop.
206 OnStartCompletedAsync(rv
);
209 void URLRequestFtpJob::OnStartCompleted(int result
) {
210 // Clear the IO_PENDING status
211 SetStatus(URLRequestStatus());
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
);
222 if (http_transaction_
) {
223 http_response_info_
= http_transaction_
->GetResponseInfo();
224 SetProxyServer(http_response_info_
->proxy_server
);
226 if (http_response_info_
->headers
->response_code() == 401 ||
227 http_response_info_
->headers
->response_code() == 407) {
228 HandleAuthNeededResponse();
232 NotifyHeadersComplete();
233 } else if (ftp_transaction_
&&
234 ftp_transaction_
->GetResponseInfo()->needs_auth
) {
235 HandleAuthNeededResponse();
238 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED
, result
));
242 void URLRequestFtpJob::OnStartCompletedAsync(int result
) {
243 base::MessageLoop::current()->PostTask(
245 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 // TODO(vadimt): Remove ScopedTracker below once crbug.com/423948 is fixed.
354 tracked_objects::ScopedTracker
tracking_profile(
355 FROM_HERE_WITH_EXPLICIT_FUNCTION("423948 URLRequestFtpJob::ReadRawData"));
357 DCHECK_NE(buf_size
, 0);
359 DCHECK(!read_in_progress_
);
362 if (proxy_info_
.is_direct()) {
363 rv
= ftp_transaction_
->Read(buf
, buf_size
,
364 base::Bind(&URLRequestFtpJob::OnReadCompleted
,
365 base::Unretained(this)));
367 rv
= http_transaction_
->Read(buf
, buf_size
,
368 base::Bind(&URLRequestFtpJob::OnReadCompleted
,
369 base::Unretained(this)));
377 if (rv
== ERR_IO_PENDING
) {
378 read_in_progress_
= true;
379 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING
, 0));
381 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED
, rv
));
386 void URLRequestFtpJob::HandleAuthNeededResponse() {
387 GURL origin
= request_
->url().GetOrigin();
389 if (auth_data_
.get()) {
390 if (auth_data_
->state
== AUTH_STATE_CANCELED
) {
391 NotifyHeadersComplete();
395 if (ftp_transaction_
&& auth_data_
->state
== AUTH_STATE_HAVE_AUTH
)
396 ftp_auth_cache_
->Remove(origin
, auth_data_
->credentials
);
398 auth_data_
= new AuthData
;
400 auth_data_
->state
= AUTH_STATE_NEED_AUTH
;
402 FtpAuthCache::Entry
* cached_auth
= NULL
;
403 if (ftp_transaction_
&& ftp_transaction_
->GetResponseInfo()->needs_auth
)
404 cached_auth
= ftp_auth_cache_
->Lookup(origin
);
406 // Retry using cached auth data.
407 SetAuth(cached_auth
->credentials
);
409 // Prompt for a username/password.
410 NotifyHeadersComplete();