• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
20 namespace net {
21 
URLRequestFtpJob(URLRequest * request)22 URLRequestFtpJob::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
Factory(URLRequest * request,const std::string & scheme)34 URLRequestJob* 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 
GetMimeType(std::string * mime_type) const48 bool 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 
GetSocketAddress() const56 HostPortPair URLRequestFtpJob::GetSocketAddress() const {
57   if (!transaction_.get()) {
58     return HostPortPair();
59   }
60   return transaction_->GetResponseInfo()->socket_address;
61 }
62 
~URLRequestFtpJob()63 URLRequestFtpJob::~URLRequestFtpJob() {
64 }
65 
StartTransaction()66 void 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 
OnStartCompleted(int result)95 void 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 
OnReadCompleted(int result)132 void 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 
RestartTransactionWithAuth()145 void 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 
Start()164 void URLRequestFtpJob::Start() {
165   DCHECK(!transaction_.get());
166   request_info_.url = request_->url();
167   StartTransaction();
168 }
169 
Kill()170 void URLRequestFtpJob::Kill() {
171   if (!transaction_.get())
172     return;
173   transaction_.reset();
174   URLRequestJob::Kill();
175   method_factory_.RevokeAll();
176 }
177 
GetLoadState() const178 LoadState URLRequestFtpJob::GetLoadState() const {
179   return transaction_.get() ?
180       transaction_->GetLoadState() : LOAD_STATE_IDLE;
181 }
182 
NeedsAuth()183 bool 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 
GetAuthChallengeInfo(scoped_refptr<AuthChallengeInfo> * result)191 void 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 
SetAuth(const string16 & username,const string16 & password)204 void 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 
CancelAuth()217 void 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 
GetUploadProgress() const230 uint64 URLRequestFtpJob::GetUploadProgress() const {
231   return 0;
232 }
233 
ReadRawData(IOBuffer * buf,int buf_size,int * bytes_read)234 bool 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