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