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