url_request_ftp_job.cc revision f2477e01787aa58f445919b809d89e252beef54f
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
24namespace net {
25
26URLRequestFtpJob::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
45URLRequestFtpJob::~URLRequestFtpJob() {
46  if (pac_request_)
47    proxy_service_->CancelPacRequest(pac_request_);
48}
49
50bool URLRequestFtpJob::IsSafeRedirect(const GURL& location) {
51  // Disallow all redirects.
52  return false;
53}
54
55bool 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
70void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo* info) {
71  if (http_response_info_)
72    *info = *http_response_info_;
73}
74
75HostPortPair 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
87void URLRequestFtpJob::SetPriority(RequestPriority priority) {
88  priority_ = priority;
89  if (http_transaction_)
90    http_transaction_->SetPriority(priority);
91}
92
93void 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
117void 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
126void 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
149void 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
176void URLRequestFtpJob::StartHttpTransaction() {
177  // Create a transaction.
178  DCHECK(!http_transaction_);
179
180  // Do not cache FTP responses sent through HTTP proxy.
181  request_->SetLoadFlags(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
206void 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
238void URLRequestFtpJob::OnStartCompletedAsync(int result) {
239  base::MessageLoop::current()->PostTask(
240      FROM_HERE,
241      base::Bind(&URLRequestFtpJob::OnStartCompleted,
242                 weak_factory_.GetWeakPtr(), result));
243}
244
245void 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
258void 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
283LoadState 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
293bool URLRequestFtpJob::NeedsAuth() {
294  return auth_data_.get() && auth_data_->state == AUTH_STATE_NEED_AUTH;
295}
296
297void 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
315void 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
330void 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
342UploadProgress URLRequestFtpJob::GetUploadProgress() const {
343  return UploadProgress();
344}
345
346bool 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
378void 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
407