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