Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(255)

Side by Side Diff: net/url_request/url_request_ftp_job.cc

Issue 523034: Remove WinInet FTP code. (Closed) Base URL: http://src.chromium.org/svn/trunk/src/
Patch Set: Created 10 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « net/url_request/url_request_ftp_job.h ('k') | net/url_request/url_request_inet_job.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2006-2008 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 <windows.h>
8 #include <wininet.h>
9
10 #include "base/message_loop.h"
11 #include "base/string_util.h"
12 #include "base/sys_string_conversions.h"
13 #include "base/time.h"
14 #include "net/base/auth.h"
15 #include "net/base/escape.h"
16 #include "net/base/load_flags.h"
17 #include "net/base/net_errors.h"
18 #include "net/base/net_util.h"
19 #include "net/base/wininet_util.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 #include "net/url_request/url_request_new_ftp_job.h"
24
25 using std::string;
26
27 using net::WinInetUtil;
28
29 // When building the directory listing, the period to wait before notifying
30 // the parent class that we wrote the data.
31 #define kFtpBufferTimeMs 50
32
33 static bool UnescapeAndValidatePath(const URLRequest* request,
34 std::string* unescaped_path) {
35 // Path in GURL is %-encoded UTF-8. FTP servers do not
36 // understand %-escaped path so that we have to unescape leading to an
37 // unescaped UTF-8 path. Then, the presence of NULL, CR and LF is checked
38 // because they're not allowed in FTP.
39 // TODO(jungshik) Even though RFC 2640 specifies that UTF-8 be used.
40 // There are many FTP servers that use legacy encodings. For them,
41 // we need to identify the encoding and convert to that encoding.
42 static const std::string kInvalidChars("\x00\x0d\x0a", 3);
43 *unescaped_path = UnescapeURLComponent(request->url().path(),
44 UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS);
45 if (unescaped_path->find_first_of(kInvalidChars) != std::string::npos) {
46 SetLastError(ERROR_INTERNET_INVALID_URL);
47 // GURL path should not contain '%00' which is NULL(0x00) when unescaped.
48 // URLRequestFtpJob should not have been invoked for an invalid GURL.
49 DCHECK(unescaped_path->find(std::string("\x00", 1)) == std::string::npos) <<
50 "Path should not contain %00.";
51 return false;
52 }
53 return true;
54 }
55
56 // static
57 URLRequestJob* URLRequestFtpJob::Factory(URLRequest* request,
58 const std::string &scheme) {
59 // Checking whether we are using new or old FTP implementation.
60 if (request->context() && request->context()->ftp_transaction_factory())
61 return URLRequestNewFtpJob::Factory(request, scheme);
62
63 DCHECK(scheme == "ftp");
64
65 int port = request->url().IntPort();
66
67 if (request->url().has_port() &&
68 !net::IsPortAllowedByFtp(port) && !net::IsPortAllowedByOverride(port))
69 return new URLRequestErrorJob(request, net::ERR_UNSAFE_PORT);
70
71 return new URLRequestFtpJob(request);
72 }
73
74 URLRequestFtpJob::URLRequestFtpJob(URLRequest* request)
75 : URLRequestInetJob(request), state_(START), is_directory_(false),
76 dest_(NULL), dest_size_(0) {
77 }
78
79 URLRequestFtpJob::~URLRequestFtpJob() {
80 }
81
82 void URLRequestFtpJob::Start() {
83 GURL parts(request_->url());
84 const std::string& scheme = parts.scheme();
85
86 // We should only be dealing with FTP at this point:
87 DCHECK(LowerCaseEqualsASCII(scheme, "ftp"));
88
89 SendRequest();
90 }
91
92 bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const {
93 if (!is_directory_)
94 return false;
95
96 mime_type->assign("text/html");
97 return true;
98 }
99
100 void URLRequestFtpJob::OnCancelAuth() {
101 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
102 this, &URLRequestFtpJob::ContinueNotifyHeadersComplete));
103 }
104
105 void URLRequestFtpJob::OnSetAuth() {
106 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
107 this, &URLRequestFtpJob::SendRequest));
108 }
109
110 void URLRequestFtpJob::SendRequest() {
111 state_ = CONNECTING;
112
113 DWORD flags =
114 INTERNET_FLAG_KEEP_CONNECTION |
115 INTERNET_FLAG_EXISTING_CONNECT |
116 INTERNET_FLAG_PASSIVE |
117 INTERNET_FLAG_RAW_DATA;
118
119 // It doesn't make sense to ask for both a cache validation and a
120 // reload at the same time.
121 DCHECK(!((request_->load_flags() & net::LOAD_VALIDATE_CACHE) &&
122 (request_->load_flags() & net::LOAD_BYPASS_CACHE)));
123
124 if (request_->load_flags() & net::LOAD_BYPASS_CACHE)
125 flags |= INTERNET_FLAG_RELOAD;
126
127 // Apply authentication if we have any, otherwise authenticate
128 // according to FTP defaults. (See InternetConnect documentation.)
129 // First, check if we have auth in cache, then check URL.
130 // That way a user can re-enter credentials, and we'll try with their
131 // latest input rather than always trying what they specified
132 // in the url (if anything).
133 string username, password;
134 bool have_auth = false;
135 if (server_auth_ && server_auth_->state == net::AUTH_STATE_HAVE_AUTH) {
136 // Add auth info to cache
137 have_auth = true;
138 username = WideToUTF8(server_auth_->username);
139 password = WideToUTF8(server_auth_->password);
140 request_->context()->ftp_auth_cache()->Add(request_->url().GetOrigin(),
141 server_auth_->username,
142 server_auth_->password);
143 } else {
144 if (request_->url().has_username()) {
145 username = request_->url().username();
146 password = request_->url().has_password() ? request_->url().password() :
147 "";
148 have_auth = true;
149 }
150 }
151
152 int port = request_->url().has_port() ?
153 request_->url().IntPort() : INTERNET_DEFAULT_FTP_PORT;
154
155 connection_handle_ = InternetConnectA(
156 GetTheInternet(),
157 request_->url().HostNoBrackets().c_str(),
158 port,
159 have_auth ? username.c_str() : NULL,
160 have_auth ? password.c_str() : NULL,
161 INTERNET_SERVICE_FTP, flags,
162 reinterpret_cast<DWORD_PTR>(this));
163
164 if (connection_handle_) {
165 OnConnect();
166 } else {
167 ProcessRequestError(GetLastError());
168 }
169 }
170
171 void URLRequestFtpJob::OnIOComplete(const AsyncResult& result) {
172 if (state_ == CONNECTING) {
173 switch (result.dwError) {
174 case ERROR_NO_MORE_FILES:
175 // url is an empty directory
176 OnStartDirectoryTraversal();
177 OnFinishDirectoryTraversal();
178 return;
179 case ERROR_INTERNET_LOGIN_FAILURE:
180 // fall through
181 case ERROR_INTERNET_INCORRECT_USER_NAME:
182 // fall through
183 case ERROR_INTERNET_INCORRECT_PASSWORD: {
184 GURL origin = request_->url().GetOrigin();
185 if (server_auth_ != NULL &&
186 server_auth_->state == net::AUTH_STATE_HAVE_AUTH) {
187 request_->context()->ftp_auth_cache()->Remove(origin,
188 server_auth_->username,
189 server_auth_->password);
190 } else {
191 server_auth_ = new net::AuthData();
192 }
193 server_auth_->state = net::AUTH_STATE_NEED_AUTH;
194
195 net::FtpAuthCache::Entry* cached_auth =
196 request_->context()->ftp_auth_cache()->Lookup(origin);
197
198 if (cached_auth) {
199 // Retry using cached auth data.
200 SetAuth(cached_auth->username, cached_auth->password);
201 } else {
202 // The io completed fine, the error was due to invalid auth.
203 SetStatus(URLRequestStatus());
204
205 // Prompt for a username/password.
206 NotifyHeadersComplete();
207 }
208 return;
209 }
210 case ERROR_SUCCESS:
211 connection_handle_ = (HINTERNET)result.dwResult;
212 OnConnect();
213 return;
214 case ERROR_INTERNET_EXTENDED_ERROR: {
215 DWORD extended_err(ERROR_SUCCESS);
216 DWORD size = 1;
217 char buffer[1];
218 if (!InternetGetLastResponseInfoA(&extended_err, buffer, &size))
219 // We don't care about the error text here, so the only acceptable
220 // error is one regarding insufficient buffer length.
221 DCHECK(GetLastError() == ERROR_INSUFFICIENT_BUFFER);
222 if (extended_err != ERROR_SUCCESS) {
223 CleanupConnection();
224 NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED,
225 WinInetUtil::OSErrorToNetError(extended_err)));
226 return;
227 }
228 // Fall through in the case we saw ERROR_INTERNET_EXTENDED_ERROR but
229 // InternetGetLastResponseInfo gave us no additional information.
230 }
231 default:
232 CleanupConnection();
233 NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED,
234 WinInetUtil::OSErrorToNetError(result.dwError)));
235 return;
236 }
237 } else if (state_ == SETTING_CUR_DIRECTORY) {
238 OnSetCurrentDirectory(result.dwError);
239 } else if (state_ == FINDING_FIRST_FILE) {
240 // We don't fail here if result.dwError != ERROR_SUCCESS because
241 // getting an error here doesn't always mean the file is not found.
242 // FindFirstFileA() issue a LIST command and may fail on some
243 // ftp server when the requested object is a file. So ERROR_NO_MORE_FILES
244 // from FindFirstFileA() is not a reliable criteria for valid path
245 // or not, we should proceed optimistically by getting the file handle.
246 if (result.dwError != ERROR_SUCCESS &&
247 result.dwError != ERROR_NO_MORE_FILES) {
248 DWORD result_error = result.dwError;
249 CleanupConnection();
250 NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED,
251 WinInetUtil::OSErrorToNetError(result_error)));
252 return;
253 }
254 request_handle_ = (HINTERNET)result.dwResult;
255 OnFindFirstFile(result.dwError);
256 } else if (state_ == GETTING_DIRECTORY) {
257 OnFindFile(result.dwError);
258 } else if (state_ == GETTING_FILE_HANDLE) {
259 if (result.dwError != ERROR_SUCCESS) {
260 CleanupConnection();
261 NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED,
262 WinInetUtil::OSErrorToNetError(result.dwError)));
263 return;
264 }
265 // start reading file contents
266 state_ = GETTING_FILE;
267 request_handle_ = (HINTERNET)result.dwResult;
268 NotifyHeadersComplete();
269 } else {
270 // we don't have IO outstanding. Pass to our base class.
271 URLRequestInetJob::OnIOComplete(result);
272 }
273 }
274
275 bool URLRequestFtpJob::NeedsAuth() {
276 // Note that we only have to worry about cases where an actual FTP server
277 // requires auth (and not a proxy), because connecting to FTP via proxy
278 // effectively means the browser communicates via HTTP, and uses HTTP's
279 // Proxy-Authenticate protocol when proxy servers require auth.
280 return server_auth_ && server_auth_->state == net::AUTH_STATE_NEED_AUTH;
281 }
282
283 void URLRequestFtpJob::GetAuthChallengeInfo(
284 scoped_refptr<net::AuthChallengeInfo>* result) {
285 DCHECK((server_auth_ != NULL) &&
286 (server_auth_->state == net::AUTH_STATE_NEED_AUTH));
287 scoped_refptr<net::AuthChallengeInfo> auth_info = new net::AuthChallengeInfo;
288 auth_info->is_proxy = false;
289 auth_info->host_and_port = ASCIIToWide(
290 net::GetHostAndPort(request_->url()));
291 auth_info->scheme = L"";
292 auth_info->realm = L"";
293 result->swap(auth_info);
294 }
295
296 void URLRequestFtpJob::OnConnect() {
297 DCHECK_EQ(state_, CONNECTING);
298
299 state_ = SETTING_CUR_DIRECTORY;
300 // Setting the directory lets us determine if the URL is a file,
301 // and also keeps the working directory for the FTP session in sync
302 // with what is being displayed in the browser.
303 if (request_->url().has_path()) {
304 std::string unescaped_path;
305 if (UnescapeAndValidatePath(request_, &unescaped_path) &&
306 FtpSetCurrentDirectoryA(connection_handle_,
307 unescaped_path.c_str())) {
308 OnSetCurrentDirectory(ERROR_SUCCESS);
309 } else {
310 ProcessRequestError(GetLastError());
311 }
312 }
313 }
314
315 void URLRequestFtpJob::OnSetCurrentDirectory(DWORD last_error) {
316 DCHECK_EQ(state_, SETTING_CUR_DIRECTORY);
317
318 is_directory_ = (last_error == ERROR_SUCCESS);
319 // if last_error is not ERROR_SUCCESS, the requested url is either
320 // a file or an invalid path. We optimistically try to read as a file,
321 // and if it fails, we fail.
322 state_ = FINDING_FIRST_FILE;
323
324 std::string unescaped_path;
325 bool is_path_valid = true;
326 if (request_->url().has_path()) {
327 is_path_valid = UnescapeAndValidatePath(request_, &unescaped_path);
328 }
329 if (is_path_valid &&
330 (request_handle_ = FtpFindFirstFileA(connection_handle_,
331 unescaped_path.c_str(),
332 &find_data_, 0,
333 reinterpret_cast<DWORD_PTR>(this)))) {
334 OnFindFirstFile(GetLastError());
335 } else {
336 ProcessRequestError(GetLastError());
337 }
338 }
339
340 void URLRequestFtpJob::FindNextFile() {
341 DWORD last_error;
342 if (InternetFindNextFileA(request_handle_, &find_data_)) {
343 last_error = ERROR_SUCCESS;
344 } else {
345 last_error = GetLastError();
346 // We'll get ERROR_NO_MORE_FILES if the directory is empty.
347 if (last_error != ERROR_NO_MORE_FILES) {
348 ProcessRequestError(last_error);
349 return;
350 }
351 }
352 // Use InvokeLater to call OnFindFile as it ends up calling us, so we don't
353 // to blow the stack.
354 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
355 this, &URLRequestFtpJob::OnFindFile, last_error));
356 }
357
358 void URLRequestFtpJob::OnFindFirstFile(DWORD last_error) {
359 DCHECK_EQ(state_, FINDING_FIRST_FILE);
360 if (!is_directory_) {
361 // Note that it is not enough to just check !is_directory_ and assume
362 // the URL is a file, because is_directory_ is true iff we successfully
363 // set current directory to the URL path. Therefore, the URL could just
364 // be an invalid path. We proceed optimistically and fail in that case.
365 state_ = GETTING_FILE_HANDLE;
366 std::string unescaped_path;
367 if (UnescapeAndValidatePath(request_, &unescaped_path) &&
368 (request_handle_ = FtpOpenFileA(connection_handle_,
369 unescaped_path.c_str(),
370 GENERIC_READ,
371 INTERNET_FLAG_TRANSFER_BINARY,
372 reinterpret_cast<DWORD_PTR>(this)))) {
373 // Start reading file contents
374 state_ = GETTING_FILE;
375 NotifyHeadersComplete();
376 } else {
377 ProcessRequestError(GetLastError());
378 }
379 } else {
380 OnStartDirectoryTraversal();
381 // If we redirect in OnStartDirectoryTraversal() then this request job
382 // is cancelled.
383 if (request_handle_)
384 OnFindFile(last_error);
385 }
386 }
387
388 void URLRequestFtpJob::OnFindFile(DWORD last_error) {
389 DCHECK_EQ(state_, GETTING_DIRECTORY);
390
391 if (last_error == ERROR_SUCCESS) {
392 // TODO(jabdelmalek): need to add icons for files/folders.
393 int64 size =
394 (static_cast<unsigned __int64>(find_data_.nFileSizeHigh) << 32) |
395 find_data_.nFileSizeLow;
396
397 // We don't know the encoding used on an FTP server, but we
398 // use FtpFindFirstFileA, which I guess does NOT preserve
399 // the raw byte sequence because it's implemented in terms
400 // of FtpFindFirstFileW. Without the raw byte sequence, we
401 // can't apply the encoding detection or other heuristics
402 // to determine/guess the encoding. Neither can we use UTF-8
403 // used by a RFC-2640-compliant FTP server. In some cases (e.g.
404 // the default code page is an SBCS with almost all bytes assigned.
405 // In lucky cases, it's even possible with a DBCS), it's possible
406 // to recover the raw byte sequence in most cases. We can do
407 // some more here, but it's not worth the effort because we're
408 // going to replace this class with URLRequestNewFtpJob.
409 string file_entry = net::GetDirectoryListingEntry(
410 base::SysNativeMBToWide(find_data_.cFileName), std::string(),
411 false, size, base::Time::FromFileTime(find_data_.ftLastWriteTime));
412 WriteData(&file_entry, true);
413
414 FindNextFile();
415 return;
416 }
417
418 DCHECK(last_error == ERROR_NO_MORE_FILES);
419 OnFinishDirectoryTraversal();
420 }
421
422 void URLRequestFtpJob::OnStartDirectoryTraversal() {
423 state_ = GETTING_DIRECTORY;
424
425 // Unescape the URL path and pass the raw 8bit directly to the browser.
426 //
427 // Here we can try to detect the encoding although it may not be very
428 // reliable because it's not likely to be long enough. Because this class
429 // will be replaced by URLRequestNewFtpJob and is used only on Windows,
430 // we use SysNativeMBToWide as a stopgap measure.
431 string html = net::GetDirectoryListingHeader(
432 base::SysNativeMBToWide(UnescapeURLComponent(request_->url().path(),
433 UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS)));
434
435 // If this isn't top level directory (i.e. the path isn't "/",) add a link to
436 // the parent directory.
437 if (request_->url().path().length() > 1)
438 html.append(net::GetDirectoryListingEntry(L"..", std::string(),
439 false, 0, base::Time()));
440
441 WriteData(&html, true);
442
443 NotifyHeadersComplete();
444 }
445
446 void URLRequestFtpJob::OnFinishDirectoryTraversal() {
447 state_ = DONE;
448 }
449
450 int URLRequestFtpJob::WriteData(const std::string* data,
451 bool call_io_complete) {
452 int written = 0;
453
454 if (data && data->length())
455 directory_html_.append(*data);
456
457 if (dest_) {
458 size_t bytes_to_copy = std::min(static_cast<size_t>(dest_size_),
459 directory_html_.length());
460 if (bytes_to_copy) {
461 memcpy(dest_, directory_html_.c_str(), bytes_to_copy);
462 directory_html_.erase(0, bytes_to_copy);
463 dest_ = NULL;
464 dest_size_ = NULL;
465 written = static_cast<int>(bytes_to_copy);
466
467 if (call_io_complete) {
468 // Wait a little bit before telling the parent class that we wrote
469 // data. This avoids excessive cycles of us getting one file entry and
470 // telling the parent class to Read().
471 MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod(
472 this, &URLRequestFtpJob::ContinueIOComplete, written),
473 kFtpBufferTimeMs);
474 }
475 }
476 }
477
478 return written;
479 }
480
481 void URLRequestFtpJob::ContinueIOComplete(int bytes_written) {
482 AsyncResult result;
483 result.dwResult = bytes_written;
484 result.dwError = ERROR_SUCCESS;
485 URLRequestInetJob::OnIOComplete(result);
486 }
487
488 void URLRequestFtpJob::ContinueNotifyHeadersComplete() {
489 NotifyHeadersComplete();
490 }
491
492 int URLRequestFtpJob::CallInternetRead(char* dest, int dest_size,
493 int *bytes_read) {
494 int result;
495
496 if (is_directory_) {
497 // Copy the html that we created from the directory listing that we got
498 // from InternetFindNextFile.
499 DCHECK(dest_ == NULL);
500 dest_ = dest;
501 dest_size_ = dest_size;
502
503 DCHECK(state_ == GETTING_DIRECTORY || state_ == DONE);
504 int written = WriteData(NULL, false);
505 if (written) {
506 *bytes_read = written;
507 result = ERROR_SUCCESS;
508 } else {
509 result = state_ == GETTING_DIRECTORY ? ERROR_IO_PENDING : ERROR_SUCCESS;
510 }
511 } else {
512 DWORD bytes_to_read = dest_size;
513 bytes_read_ = 0;
514 // InternetReadFileEx doesn't work for asynchronous FTP, InternetReadFile
515 // must be used instead.
516 if (!InternetReadFile(request_handle_, dest, bytes_to_read, &bytes_read_))
517 return GetLastError();
518
519 *bytes_read = static_cast<int>(bytes_read_);
520 result = ERROR_SUCCESS;
521 }
522
523 return result;
524 }
525
526 bool URLRequestFtpJob::GetReadBytes(const AsyncResult& result,
527 int* bytes_read) {
528 if (is_directory_) {
529 *bytes_read = static_cast<int>(result.dwResult);
530 } else {
531 if (!result.dwResult)
532 return false;
533
534 // IE5 and later return the number of read bytes in the
535 // INTERNET_ASYNC_RESULT structure. IE4 holds on to the pointer passed in
536 // to InternetReadFile and store it there.
537 *bytes_read = bytes_read_;
538
539 if (!*bytes_read)
540 *bytes_read = result.dwError;
541 }
542
543 return true;
544 }
545
546 bool URLRequestFtpJob::IsRedirectResponse(GURL* location,
547 int* http_status_code) {
548 if (is_directory_) {
549 std::string ftp_path = request_->url().path();
550 if (!ftp_path.empty() && ('/' != ftp_path[ftp_path.length() - 1])) {
551 ftp_path.push_back('/');
552 GURL::Replacements replacements;
553 replacements.SetPathStr(ftp_path);
554
555 *location = request_->url().ReplaceComponents(replacements);
556 *http_status_code = 301; // simulate a permanent redirect
557 return true;
558 }
559 }
560
561 return false;
562 }
OLDNEW
« no previous file with comments | « net/url_request/url_request_ftp_job.h ('k') | net/url_request/url_request_inet_job.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698