url_request_ftp_job.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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/net_errors.h"
13#include "net/base/net_util.h"
14#include "net/ftp/ftp_response_info.h"
15#include "net/ftp/ftp_transaction_factory.h"
16#include "net/url_request/url_request.h"
17#include "net/url_request/url_request_context.h"
18#include "net/url_request/url_request_error_job.h"
19
20namespace net {
21
22URLRequestFtpJob::URLRequestFtpJob(
23    URLRequest* request,
24    NetworkDelegate* network_delegate,
25    FtpTransactionFactory* ftp_transaction_factory,
26    FtpAuthCache* ftp_auth_cache)
27    : URLRequestJob(request, network_delegate),
28      read_in_progress_(false),
29      ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
30      ftp_transaction_factory_(ftp_transaction_factory),
31      ftp_auth_cache_(ftp_auth_cache) {
32  DCHECK(ftp_transaction_factory);
33  DCHECK(ftp_auth_cache);
34}
35
36// static
37URLRequestJob* URLRequestFtpJob::Factory(URLRequest* request,
38                                         NetworkDelegate* network_delegate,
39                                         const std::string& scheme) {
40  DCHECK_EQ(scheme, "ftp");
41
42  int port = request->url().IntPort();
43  if (request->url().has_port() &&
44      !IsPortAllowedByFtp(port) && !IsPortAllowedByOverride(port)) {
45    return new URLRequestErrorJob(request,
46                                  network_delegate,
47                                  ERR_UNSAFE_PORT);
48  }
49
50  return new URLRequestFtpJob(request,
51                              network_delegate,
52                              request->context()->ftp_transaction_factory(),
53                              request->context()->ftp_auth_cache());
54}
55
56bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const {
57  if (transaction_->GetResponseInfo()->is_directory_listing) {
58    *mime_type = "text/vnd.chromium.ftp-dir";
59    return true;
60  }
61  return false;
62}
63
64HostPortPair URLRequestFtpJob::GetSocketAddress() const {
65  if (!transaction_.get()) {
66    return HostPortPair();
67  }
68  return transaction_->GetResponseInfo()->socket_address;
69}
70
71URLRequestFtpJob::~URLRequestFtpJob() {
72}
73
74void URLRequestFtpJob::StartTransaction() {
75  // Create a transaction.
76  DCHECK(!transaction_.get());
77
78  transaction_.reset(ftp_transaction_factory_->CreateTransaction());
79
80  // No matter what, we want to report our status as IO pending since we will
81  // be notifying our consumer asynchronously via OnStartCompleted.
82  SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
83  int rv;
84  if (transaction_.get()) {
85    rv = transaction_->Start(
86        &request_info_,
87        base::Bind(&URLRequestFtpJob::OnStartCompleted,
88                   base::Unretained(this)),
89        request_->net_log());
90    if (rv == ERR_IO_PENDING)
91      return;
92  } else {
93    rv = ERR_FAILED;
94  }
95  // The transaction started synchronously, but we need to notify the
96  // URLRequest delegate via the message loop.
97  MessageLoop::current()->PostTask(
98      FROM_HERE,
99      base::Bind(&URLRequestFtpJob::OnStartCompleted,
100                 weak_factory_.GetWeakPtr(), rv));
101}
102
103void URLRequestFtpJob::OnStartCompleted(int result) {
104  // Clear the IO_PENDING status
105  SetStatus(URLRequestStatus());
106
107  // Note that transaction_ may be NULL due to a creation failure.
108  if (transaction_.get()) {
109    // FTP obviously doesn't have HTTP Content-Length header. We have to pass
110    // the content size information manually.
111    set_expected_content_size(
112        transaction_->GetResponseInfo()->expected_content_size);
113  }
114
115  if (result == OK) {
116    NotifyHeadersComplete();
117  } else if (transaction_.get() &&
118             transaction_->GetResponseInfo()->needs_auth) {
119    GURL origin = request_->url().GetOrigin();
120    if (server_auth_ && server_auth_->state == AUTH_STATE_HAVE_AUTH) {
121      ftp_auth_cache_->Remove(origin, server_auth_->credentials);
122    } else if (!server_auth_) {
123      server_auth_ = new AuthData();
124    }
125    server_auth_->state = AUTH_STATE_NEED_AUTH;
126
127    FtpAuthCache::Entry* cached_auth = ftp_auth_cache_->Lookup(origin);
128    if (cached_auth) {
129      // Retry using cached auth data.
130      SetAuth(cached_auth->credentials);
131    } else {
132      // Prompt for a username/password.
133      NotifyHeadersComplete();
134    }
135  } else {
136    NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
137  }
138}
139
140void URLRequestFtpJob::OnReadCompleted(int result) {
141  read_in_progress_ = false;
142  if (result == 0) {
143    NotifyDone(URLRequestStatus());
144  } else if (result < 0) {
145    NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
146  } else {
147    // Clear the IO_PENDING status
148    SetStatus(URLRequestStatus());
149  }
150  NotifyReadComplete(result);
151}
152
153void URLRequestFtpJob::RestartTransactionWithAuth() {
154  DCHECK(server_auth_ && server_auth_->state == AUTH_STATE_HAVE_AUTH);
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
160  int rv = transaction_->RestartWithAuth(
161      server_auth_->credentials,
162      base::Bind(&URLRequestFtpJob::OnStartCompleted,
163                 base::Unretained(this)));
164  if (rv == ERR_IO_PENDING)
165    return;
166
167  MessageLoop::current()->PostTask(
168      FROM_HERE,
169      base::Bind(&URLRequestFtpJob::OnStartCompleted,
170                 weak_factory_.GetWeakPtr(), rv));
171}
172
173void URLRequestFtpJob::Start() {
174  DCHECK(!transaction_.get());
175  request_info_.url = request_->url();
176  StartTransaction();
177}
178
179void URLRequestFtpJob::Kill() {
180  if (!transaction_.get())
181    return;
182  transaction_.reset();
183  URLRequestJob::Kill();
184  weak_factory_.InvalidateWeakPtrs();
185}
186
187LoadState URLRequestFtpJob::GetLoadState() const {
188  return transaction_.get() ?
189      transaction_->GetLoadState() : LOAD_STATE_IDLE;
190}
191
192bool URLRequestFtpJob::NeedsAuth() {
193  // Note that we only have to worry about cases where an actual FTP server
194  // requires auth (and not a proxy), because connecting to FTP via proxy
195  // effectively means the browser communicates via HTTP, and uses HTTP's
196  // Proxy-Authenticate protocol when proxy servers require auth.
197  return server_auth_ && server_auth_->state == AUTH_STATE_NEED_AUTH;
198}
199
200void URLRequestFtpJob::GetAuthChallengeInfo(
201    scoped_refptr<AuthChallengeInfo>* result) {
202  DCHECK((server_auth_ != NULL) &&
203         (server_auth_->state == AUTH_STATE_NEED_AUTH));
204  scoped_refptr<AuthChallengeInfo> auth_info(new AuthChallengeInfo);
205  auth_info->is_proxy = false;
206  auth_info->challenger = HostPortPair::FromURL(request_->url());
207  // scheme and realm are kept empty.
208  DCHECK(auth_info->scheme.empty());
209  DCHECK(auth_info->realm.empty());
210  result->swap(auth_info);
211}
212
213void URLRequestFtpJob::SetAuth(const AuthCredentials& credentials) {
214  DCHECK(NeedsAuth());
215  server_auth_->state = AUTH_STATE_HAVE_AUTH;
216  server_auth_->credentials = credentials;
217
218  ftp_auth_cache_->Add(request_->url().GetOrigin(), server_auth_->credentials);
219
220  RestartTransactionWithAuth();
221}
222
223void URLRequestFtpJob::CancelAuth() {
224  DCHECK(NeedsAuth());
225  server_auth_->state = AUTH_STATE_CANCELED;
226
227  // Once the auth is cancelled, we proceed with the request as though
228  // there were no auth.  Schedule this for later so that we don't cause
229  // any recursing into the caller as a result of this call.
230  MessageLoop::current()->PostTask(
231      FROM_HERE,
232      base::Bind(&URLRequestFtpJob::OnStartCompleted,
233                 weak_factory_.GetWeakPtr(), OK));
234}
235
236UploadProgress URLRequestFtpJob::GetUploadProgress() const {
237  return UploadProgress();
238}
239
240bool URLRequestFtpJob::ReadRawData(IOBuffer* buf,
241                                   int buf_size,
242                                   int *bytes_read) {
243  DCHECK_NE(buf_size, 0);
244  DCHECK(bytes_read);
245  DCHECK(!read_in_progress_);
246
247  int rv = transaction_->Read(buf, buf_size,
248                              base::Bind(&URLRequestFtpJob::OnReadCompleted,
249                                         base::Unretained(this)));
250  if (rv >= 0) {
251    *bytes_read = rv;
252    return true;
253  }
254
255  if (rv == ERR_IO_PENDING) {
256    read_in_progress_ = true;
257    SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
258  } else {
259    NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
260  }
261  return false;
262}
263
264}  // namespace net
265