diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 22:42:52 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 22:42:52 +0000 |
commit | 586acc5fe142f498261f52c66862fa417c3d52d2 (patch) | |
tree | c98b3417a883f2477029c8cd5888f4078681e24e /net/url_request/url_request_ftp_job.cc | |
parent | a814a8d55429605fe6d7045045cd25b6bf624580 (diff) | |
download | chromium_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.cc | 547 |
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; +} |