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