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