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