summaryrefslogtreecommitdiffstats
path: root/net/url_request/url_request_ftp_job.cc
diff options
context:
space:
mode:
authorinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 22:42:52 +0000
committerinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 22:42:52 +0000
commit586acc5fe142f498261f52c66862fa417c3d52d2 (patch)
treec98b3417a883f2477029c8cd5888f4078681e24e /net/url_request/url_request_ftp_job.cc
parenta814a8d55429605fe6d7045045cd25b6bf624580 (diff)
downloadchromium_src-586acc5fe142f498261f52c66862fa417c3d52d2.zip
chromium_src-586acc5fe142f498261f52c66862fa417c3d52d2.tar.gz
chromium_src-586acc5fe142f498261f52c66862fa417c3d52d2.tar.bz2
Add net to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@14 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/url_request/url_request_ftp_job.cc')
-rw-r--r--net/url_request/url_request_ftp_job.cc547
1 files changed, 547 insertions, 0 deletions
diff --git a/net/url_request/url_request_ftp_job.cc b/net/url_request/url_request_ftp_job.cc
new file mode 100644
index 0000000..f619609
--- /dev/null
+++ b/net/url_request/url_request_ftp_job.cc
@@ -0,0 +1,547 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/url_request/url_request_ftp_job.h"
+
+#include <windows.h>
+#include <wininet.h>
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "net/base/auth.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_util.h"
+#include "net/base/wininet_util.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/base/escape.h"
+
+using std::string;
+
+using net::WinInetUtil;
+
+// When building the directory listing, the period to wait before notifying
+// the parent class that we wrote the data.
+#define kFtpBufferTimeMs 50
+
+static bool UnescapeAndValidatePath(const URLRequest* request,
+ std::string* unescaped_path) {
+ // Path in GURL is %-encoded UTF-8. FTP servers do not
+ // understand %-escaped path so that we have to unescape leading to an
+ // unescaped UTF-8 path. Then, the presence of NULL, CR and LF is checked
+ // because they're not allowed in FTP.
+ // TODO(jungshik) Even though RFC 2640 specifies that UTF-8 be used.
+ // There are many FTP servers that use legacy encodings. For them,
+ // we need to identify the encoding and convert to that encoding.
+ static const std::string kInvalidChars("\x00\x0d\x0a", 3);
+ *unescaped_path = UnescapeURLComponent(request->url().path(),
+ UnescapeRule::SPACES | UnescapeRule::PERCENTS);
+ if (unescaped_path->find_first_of(kInvalidChars) != std::string::npos) {
+ SetLastError(ERROR_INTERNET_INVALID_URL);
+ // GURL path should not contain '%00' which is NULL(0x00) when unescaped.
+ // URLRequestFtpJob should not have been invoked for an invalid GURL.
+ DCHECK(unescaped_path->find(std::string("\x00", 1)) == std::string::npos) <<
+ "Path should not contain %00.";
+ return false;
+ }
+ return true;
+}
+
+// static
+URLRequestJob* URLRequestFtpJob::Factory(URLRequest* request,
+ const std::string &scheme) {
+ DCHECK(scheme == "ftp");
+
+ if (request->url().has_port() &&
+ !net_util::IsPortAllowedByFtp(request->url().IntPort()))
+ return new URLRequestErrorJob(request, net::ERR_UNSAFE_PORT);
+
+ return new URLRequestFtpJob(request);
+}
+
+URLRequestFtpJob::URLRequestFtpJob(URLRequest* request)
+ : URLRequestInetJob(request), state_(START), is_directory_(false),
+ dest_(NULL), dest_size_(0) {
+}
+
+URLRequestFtpJob::~URLRequestFtpJob() {
+}
+
+void URLRequestFtpJob::Start() {
+ GURL parts(request_->url());
+ const std::string& scheme = parts.scheme();
+
+ // We should only be dealing with FTP at this point:
+ DCHECK(LowerCaseEqualsASCII(scheme, "ftp"));
+
+ SendRequest();
+}
+
+bool URLRequestFtpJob::GetMimeType(std::string* mime_type) {
+ if (!is_directory_)
+ return false;
+
+ mime_type->assign("text/html");
+ return true;
+}
+
+void URLRequestFtpJob::OnCancelAuth() {
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestFtpJob::ContinueNotifyHeadersComplete));
+}
+
+void URLRequestFtpJob::OnSetAuth() {
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestFtpJob::SendRequest));
+}
+
+void URLRequestFtpJob::SendRequest() {
+ state_ = CONNECTING;
+
+ DWORD flags =
+ INTERNET_FLAG_KEEP_CONNECTION |
+ INTERNET_FLAG_EXISTING_CONNECT |
+ INTERNET_FLAG_PASSIVE |
+ INTERNET_FLAG_RAW_DATA;
+
+ // It doesn't make sense to ask for both a cache validation and a
+ // reload at the same time.
+ DCHECK(!((request_->load_flags() & net::LOAD_VALIDATE_CACHE) &&
+ (request_->load_flags() & net::LOAD_BYPASS_CACHE)));
+
+ if (request_->load_flags() & net::LOAD_BYPASS_CACHE)
+ flags |= INTERNET_FLAG_RELOAD;
+
+ // Apply authentication if we have any, otherwise authenticate
+ // according to FTP defaults. (See InternetConnect documentation.)
+ // First, check if we have auth in cache, then check URL.
+ // That way a user can re-enter credentials, and we'll try with their
+ // latest input rather than always trying what they specified
+ // in the url (if anything).
+ string username, password;
+ bool have_auth = false;
+ if (server_auth_ != NULL && server_auth_->state == AUTH_STATE_HAVE_AUTH) {
+ // Add auth info to cache
+ have_auth = true;
+ username = WideToUTF8(server_auth_->username);
+ password = WideToUTF8(server_auth_->password);
+ request_->context()->ftp_auth_cache()->Add(request_->url().host(),
+ server_auth_.get());
+ } else {
+ if (request_->url().has_username()) {
+ username = request_->url().username();
+ password = request_->url().has_password() ? request_->url().password() :
+ "";
+ have_auth = true;
+ }
+ }
+
+ int port = request_->url().has_port() ?
+ request_->url().IntPort() : INTERNET_DEFAULT_FTP_PORT;
+
+ connection_handle_ = InternetConnectA(GetTheInternet(),
+ request_->url().host().c_str(),
+ port,
+ have_auth ? username.c_str() : NULL,
+ have_auth ? password.c_str() : NULL,
+ INTERNET_SERVICE_FTP, flags,
+ reinterpret_cast<DWORD_PTR>(this));
+
+ if (connection_handle_) {
+ OnConnect();
+ } else {
+ ProcessRequestError(GetLastError());
+ }
+}
+
+void URLRequestFtpJob::OnIOComplete(const AsyncResult& result) {
+ if (state_ == CONNECTING) {
+ switch (result.dwError) {
+ case ERROR_NO_MORE_FILES:
+ // url is an empty directory
+ OnStartDirectoryTraversal();
+ OnFinishDirectoryTraversal();
+ return;
+ case ERROR_INTERNET_LOGIN_FAILURE:
+ // fall through
+ case ERROR_INTERNET_INCORRECT_USER_NAME:
+ // fall through
+ case ERROR_INTERNET_INCORRECT_PASSWORD:
+ if (server_auth_ != NULL &&
+ server_auth_->state == AUTH_STATE_HAVE_AUTH) {
+ request_->context()->ftp_auth_cache()->Remove(request_->url().host());
+ } else {
+ server_auth_ = new AuthData();
+ }
+ // Try again, prompting for authentication.
+ server_auth_->state = AUTH_STATE_NEED_AUTH;
+ // The io completed fine, the error was due to invalid auth.
+ SetStatus(URLRequestStatus());
+ NotifyHeadersComplete();
+ return;
+ case ERROR_SUCCESS:
+ connection_handle_ = (HINTERNET)result.dwResult;
+ OnConnect();
+ return;
+ case ERROR_INTERNET_EXTENDED_ERROR: {
+ DWORD extended_err(ERROR_SUCCESS);
+ DWORD size = 1;
+ char buffer[1];
+ if (!InternetGetLastResponseInfoA(&extended_err, buffer, &size))
+ // We don't care about the error text here, so the only acceptable
+ // error is one regarding insufficient buffer length.
+ DCHECK(GetLastError() == ERROR_INSUFFICIENT_BUFFER);
+ if (extended_err != ERROR_SUCCESS) {
+ CleanupConnection();
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED,
+ WinInetUtil::OSErrorToNetError(extended_err)));
+ return;
+ }
+ // Fall through in the case we saw ERROR_INTERNET_EXTENDED_ERROR but
+ // InternetGetLastResponseInfo gave us no additional information.
+ }
+ default:
+ CleanupConnection();
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED,
+ WinInetUtil::OSErrorToNetError(result.dwError)));
+ return;
+ }
+ } else if (state_ == SETTING_CUR_DIRECTORY) {
+ OnSetCurrentDirectory(result.dwError);
+ } else if (state_ == FINDING_FIRST_FILE) {
+ if (result.dwError != ERROR_SUCCESS) {
+ DWORD result_error = result.dwError;
+ CleanupConnection();
+ // Fixup the error message from our directory/file guessing.
+ if (!is_directory_ && result_error == ERROR_NO_MORE_FILES)
+ result_error = ERROR_PATH_NOT_FOUND;
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED,
+ WinInetUtil::OSErrorToNetError(result_error)));
+ return;
+ }
+ request_handle_ = (HINTERNET)result.dwResult;
+ OnFindFirstFile(result.dwError);
+ } else if (state_ == GETTING_DIRECTORY) {
+ OnFindFile(result.dwError);
+ } else if (state_ == GETTING_FILE_HANDLE) {
+ if (result.dwError != ERROR_SUCCESS) {
+ CleanupConnection();
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED,
+ WinInetUtil::OSErrorToNetError(result.dwError)));
+ return;
+ }
+ // start reading file contents
+ state_ = GETTING_FILE;
+ request_handle_ = (HINTERNET)result.dwResult;
+ NotifyHeadersComplete();
+ } else {
+ // we don't have IO outstanding. Pass to our base class.
+ URLRequestInetJob::OnIOComplete(result);
+ }
+}
+
+bool URLRequestFtpJob::NeedsAuth() {
+ // Note that we only have to worry about cases where an actual FTP server
+ // requires auth (and not a proxy), because connecting to FTP via proxy
+ // effectively means the browser communicates via HTTP, and uses HTTP's
+ // Proxy-Authenticate protocol when proxy servers require auth.
+ return ((server_auth_ != NULL) &&
+ server_auth_->state == AUTH_STATE_NEED_AUTH);
+}
+
+void URLRequestFtpJob::GetAuthChallengeInfo(
+ scoped_refptr<AuthChallengeInfo>* result) {
+ DCHECK((server_auth_ != NULL) &&
+ (server_auth_->state == AUTH_STATE_NEED_AUTH));
+ scoped_refptr<AuthChallengeInfo> auth_info = new AuthChallengeInfo;
+ auth_info->is_proxy = false;
+ auth_info->host = UTF8ToWide(request_->url().host());
+ auth_info->scheme = L"";
+ auth_info->realm = L"";
+ result->swap(auth_info);
+}
+
+void URLRequestFtpJob::GetCachedAuthData(
+ const AuthChallengeInfo& auth_info,
+ scoped_refptr<AuthData>* auth_data) {
+ *auth_data = request_->context()->ftp_auth_cache()->
+ Lookup(WideToUTF8(auth_info.host));
+}
+
+void URLRequestFtpJob::OnConnect() {
+ DCHECK_EQ(state_, CONNECTING);
+
+ state_ = SETTING_CUR_DIRECTORY;
+ // Setting the directory lets us determine if the URL is a file,
+ // and also keeps the working directory for the FTP session in sync
+ // with what is being displayed in the browser.
+ if (request_->url().has_path()) {
+ std::string unescaped_path;
+ if (UnescapeAndValidatePath(request_, &unescaped_path) &&
+ FtpSetCurrentDirectoryA(connection_handle_,
+ unescaped_path.c_str())) {
+ OnSetCurrentDirectory(ERROR_SUCCESS);
+ } else {
+ ProcessRequestError(GetLastError());
+ }
+ }
+}
+
+void URLRequestFtpJob::OnSetCurrentDirectory(DWORD last_error) {
+ DCHECK_EQ(state_, SETTING_CUR_DIRECTORY);
+
+ is_directory_ = (last_error == ERROR_SUCCESS);
+ // if last_error is not ERROR_SUCCESS, the requested url is either
+ // a file or an invalid path. We optimistically try to read as a file,
+ // and if it fails, we fail.
+ state_ = FINDING_FIRST_FILE;
+
+ std::string unescaped_path;
+ bool is_path_valid = true;
+ if (request_->url().has_path()) {
+ is_path_valid = UnescapeAndValidatePath(request_, &unescaped_path);
+ }
+ if (is_path_valid &&
+ (request_handle_ = FtpFindFirstFileA(connection_handle_,
+ unescaped_path.c_str(),
+ &find_data_, 0,
+ reinterpret_cast<DWORD_PTR>(this)))) {
+ OnFindFirstFile(GetLastError());
+ } else {
+ ProcessRequestError(GetLastError());
+ }
+}
+
+void URLRequestFtpJob::FindNextFile() {
+ DWORD last_error;
+ if (InternetFindNextFileA(request_handle_, &find_data_)) {
+ last_error = ERROR_SUCCESS;
+ } else {
+ last_error = GetLastError();
+ // We'll get ERROR_NO_MORE_FILES if the directory is empty.
+ if (last_error != ERROR_NO_MORE_FILES) {
+ ProcessRequestError(last_error);
+ return;
+ }
+ }
+ // Use InvokeLater to call OnFindFile as it ends up calling us, so we don't
+ // to blow the stack.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestFtpJob::OnFindFile, last_error));
+}
+
+void URLRequestFtpJob::OnFindFirstFile(DWORD last_error) {
+ DCHECK_EQ(state_, FINDING_FIRST_FILE);
+ if (!is_directory_) {
+ // Note that it is not enough to just check !is_directory_ and assume
+ // the URL is a file, because is_directory_ is true iff we successfully
+ // set current directory to the URL path. Therefore, the URL could just
+ // be an invalid path. We proceed optimistically and fail in that case.
+ state_ = GETTING_FILE_HANDLE;
+ std::string unescaped_path;
+ if (UnescapeAndValidatePath(request_, &unescaped_path) &&
+ (request_handle_ = FtpOpenFileA(connection_handle_,
+ unescaped_path.c_str(),
+ GENERIC_READ,
+ INTERNET_FLAG_TRANSFER_BINARY,
+ reinterpret_cast<DWORD_PTR>(this)))) {
+ // Start reading file contents
+ state_ = GETTING_FILE;
+ NotifyHeadersComplete();
+ } else {
+ ProcessRequestError(GetLastError());
+ }
+ } else {
+ OnStartDirectoryTraversal();
+ // If we redirect in OnStartDirectoryTraversal() then this request job
+ // is cancelled.
+ if (request_handle_)
+ OnFindFile(last_error);
+ }
+}
+
+void URLRequestFtpJob::OnFindFile(DWORD last_error) {
+ DCHECK_EQ(state_, GETTING_DIRECTORY);
+
+ if (last_error == ERROR_SUCCESS) {
+ // TODO(jabdelmalek): need to add icons for files/folders.
+ int64 size =
+ (static_cast<unsigned __int64>(find_data_.nFileSizeHigh) << 32) |
+ find_data_.nFileSizeLow;
+
+ // We don't know the encoding, and can't assume utf8, so pass the 8bit
+ // directly to the browser for it to decide.
+ string file_entry = net_util::GetDirectoryListingEntry(
+ find_data_.cFileName, find_data_.dwFileAttributes, size,
+ &find_data_.ftLastWriteTime);
+ WriteData(&file_entry, true);
+
+ FindNextFile();
+ return;
+ }
+
+ DCHECK(last_error == ERROR_NO_MORE_FILES);
+ OnFinishDirectoryTraversal();
+}
+
+void URLRequestFtpJob::OnStartDirectoryTraversal() {
+ state_ = GETTING_DIRECTORY;
+
+ // Unescape the URL path and pass the raw 8bit directly to the browser.
+ string html = net_util::GetDirectoryListingHeader(
+ UnescapeURLComponent(request_->url().path(),
+ UnescapeRule::SPACES | UnescapeRule::PERCENTS));
+
+ // If this isn't top level directory (i.e. the path isn't "/",) add a link to
+ // the parent directory.
+ if (request_->url().path().length() > 1)
+ html.append(net_util::GetDirectoryListingEntry("..", 0, 0, NULL));
+
+ WriteData(&html, true);
+
+ NotifyHeadersComplete();
+}
+
+void URLRequestFtpJob::OnFinishDirectoryTraversal() {
+ state_ = DONE;
+}
+
+int URLRequestFtpJob::WriteData(const std::string* data,
+ bool call_io_complete) {
+ int written = 0;
+
+ if (data && data->length())
+ directory_html_.append(*data);
+
+ if (dest_) {
+ size_t bytes_to_copy = std::min(static_cast<size_t>(dest_size_),
+ directory_html_.length());
+ if (bytes_to_copy) {
+ memcpy(dest_, directory_html_.c_str(), bytes_to_copy);
+ directory_html_.erase(0, bytes_to_copy);
+ dest_ = NULL;
+ dest_size_ = NULL;
+ written = static_cast<int>(bytes_to_copy);
+
+ if (call_io_complete) {
+ // Wait a little bit before telling the parent class that we wrote
+ // data. This avoids excessive cycles of us getting one file entry and
+ // telling the parent class to Read().
+ MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestFtpJob::ContinueIOComplete, written),
+ kFtpBufferTimeMs);
+ }
+ }
+ }
+
+ return written;
+}
+
+void URLRequestFtpJob::ContinueIOComplete(int bytes_written) {
+ AsyncResult result;
+ result.dwResult = bytes_written;
+ result.dwError = ERROR_SUCCESS;
+ URLRequestInetJob::OnIOComplete(result);
+}
+
+void URLRequestFtpJob::ContinueNotifyHeadersComplete() {
+ NotifyHeadersComplete();
+}
+
+int URLRequestFtpJob::CallInternetRead(char* dest, int dest_size,
+ int *bytes_read) {
+ int result;
+
+ if (is_directory_) {
+ // Copy the html that we created from the directory listing that we got
+ // from InternetFindNextFile.
+ DCHECK(dest_ == NULL);
+ dest_ = dest;
+ dest_size_ = dest_size;
+
+ DCHECK(state_ == GETTING_DIRECTORY || state_ == DONE);
+ int written = WriteData(NULL, false);
+ if (written) {
+ *bytes_read = written;
+ result = ERROR_SUCCESS;
+ } else {
+ result = state_ == GETTING_DIRECTORY ? ERROR_IO_PENDING : ERROR_SUCCESS;
+ }
+ } else {
+ DWORD bytes_to_read = dest_size;
+ bytes_read_ = 0;
+ // InternetReadFileEx doesn't work for asynchronous FTP, InternetReadFile
+ // must be used instead.
+ if (!InternetReadFile(request_handle_, dest, bytes_to_read, &bytes_read_))
+ return GetLastError();
+
+ *bytes_read = static_cast<int>(bytes_read_);
+ result = ERROR_SUCCESS;
+ }
+
+ return result;
+}
+
+bool URLRequestFtpJob::GetReadBytes(const AsyncResult& result,
+ int* bytes_read) {
+ if (is_directory_) {
+ *bytes_read = static_cast<int>(result.dwResult);
+ } else {
+ if (!result.dwResult)
+ return false;
+
+ // IE5 and later return the number of read bytes in the
+ // INTERNET_ASYNC_RESULT structure. IE4 holds on to the pointer passed in
+ // to InternetReadFile and store it there.
+ *bytes_read = bytes_read_;
+
+ if (!*bytes_read)
+ *bytes_read = result.dwError;
+ }
+
+ return true;
+}
+
+bool URLRequestFtpJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ if (is_directory_) {
+ std::string ftp_path = request_->url().path();
+ if (!ftp_path.empty() && ('/' != ftp_path[ftp_path.length() - 1])) {
+ ftp_path.push_back('/');
+ GURL::Replacements replacements;
+ replacements.SetPathStr(ftp_path);
+
+ *location = request_->url().ReplaceComponents(replacements);
+ *http_status_code = 301; // simulate a permanent redirect
+ return true;
+ }
+ }
+
+ return false;
+}