|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "net/ftp/ftp_network_transaction.h" | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/bind_helpers.h" | 
|  | #include "base/compiler_specific.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "base/strings/string_split.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "base/values.h" | 
|  | #include "net/base/address_list.h" | 
|  | #include "net/base/connection_type_histograms.h" | 
|  | #include "net/base/escape.h" | 
|  | #include "net/base/net_errors.h" | 
|  | #include "net/base/net_util.h" | 
|  | #include "net/base/port_util.h" | 
|  | #include "net/ftp/ftp_request_info.h" | 
|  | #include "net/ftp/ftp_util.h" | 
|  | #include "net/log/net_log.h" | 
|  | #include "net/socket/client_socket_factory.h" | 
|  | #include "net/socket/stream_socket.h" | 
|  | #include "url/url_constants.h" | 
|  |  | 
|  | namespace net { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char kCRLF[] = "\r\n"; | 
|  |  | 
|  | const int kCtrlBufLen = 1024; | 
|  |  | 
|  | // Returns true if |input| can be safely used as a part of FTP command. | 
|  | bool IsValidFTPCommandString(const std::string& input) { | 
|  | // RFC 959 only allows ASCII strings, but at least Firefox can send non-ASCII | 
|  | // characters in the command if the request path contains them. To be | 
|  | // compatible, we do the same and allow non-ASCII characters in a command. | 
|  |  | 
|  | // Protect agains newline injection attack. | 
|  | if (input.find_first_of("\r\n") != std::string::npos) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | enum ErrorClass { | 
|  | // The requested action was initiated. The client should expect another | 
|  | // reply before issuing the next command. | 
|  | ERROR_CLASS_INITIATED, | 
|  |  | 
|  | // The requested action has been successfully completed. | 
|  | ERROR_CLASS_OK, | 
|  |  | 
|  | // The command has been accepted, but to complete the operation, more | 
|  | // information must be sent by the client. | 
|  | ERROR_CLASS_INFO_NEEDED, | 
|  |  | 
|  | // The command was not accepted and the requested action did not take place. | 
|  | // This condition is temporary, and the client is encouraged to restart the | 
|  | // command sequence. | 
|  | ERROR_CLASS_TRANSIENT_ERROR, | 
|  |  | 
|  | // The command was not accepted and the requested action did not take place. | 
|  | // This condition is rather permanent, and the client is discouraged from | 
|  | // repeating the exact request. | 
|  | ERROR_CLASS_PERMANENT_ERROR, | 
|  | }; | 
|  |  | 
|  | // Returns the error class for given response code. Caller should ensure | 
|  | // that |response_code| is in range 100-599. | 
|  | ErrorClass GetErrorClass(int response_code) { | 
|  | if (response_code >= 100 && response_code <= 199) | 
|  | return ERROR_CLASS_INITIATED; | 
|  |  | 
|  | if (response_code >= 200 && response_code <= 299) | 
|  | return ERROR_CLASS_OK; | 
|  |  | 
|  | if (response_code >= 300 && response_code <= 399) | 
|  | return ERROR_CLASS_INFO_NEEDED; | 
|  |  | 
|  | if (response_code >= 400 && response_code <= 499) | 
|  | return ERROR_CLASS_TRANSIENT_ERROR; | 
|  |  | 
|  | if (response_code >= 500 && response_code <= 599) | 
|  | return ERROR_CLASS_PERMANENT_ERROR; | 
|  |  | 
|  | // We should not be called on invalid error codes. | 
|  | NOTREACHED() << response_code; | 
|  | return ERROR_CLASS_PERMANENT_ERROR; | 
|  | } | 
|  |  | 
|  | // Returns network error code for received FTP |response_code|. | 
|  | int GetNetErrorCodeForFtpResponseCode(int response_code) { | 
|  | switch (response_code) { | 
|  | case 421: | 
|  | return ERR_FTP_SERVICE_UNAVAILABLE; | 
|  | case 426: | 
|  | return ERR_FTP_TRANSFER_ABORTED; | 
|  | case 450: | 
|  | return ERR_FTP_FILE_BUSY; | 
|  | case 500: | 
|  | case 501: | 
|  | return ERR_FTP_SYNTAX_ERROR; | 
|  | case 502: | 
|  | case 504: | 
|  | return ERR_FTP_COMMAND_NOT_SUPPORTED; | 
|  | case 503: | 
|  | return ERR_FTP_BAD_COMMAND_SEQUENCE; | 
|  | default: | 
|  | return ERR_FTP_FAILED; | 
|  | } | 
|  | } | 
|  |  | 
|  | // From RFC 2428 Section 3: | 
|  | //   The text returned in response to the EPSV command MUST be: | 
|  | //     <some text> (<d><d><d><tcp-port><d>) | 
|  | //   <d> is a delimiter character, ideally to be | | 
|  | bool ExtractPortFromEPSVResponse(const FtpCtrlResponse& response, int* port) { | 
|  | if (response.lines.size() != 1) | 
|  | return false; | 
|  | const char* ptr = response.lines[0].c_str(); | 
|  | while (*ptr && *ptr != '(') | 
|  | ++ptr; | 
|  | if (!*ptr) | 
|  | return false; | 
|  | char sep = *(++ptr); | 
|  | if (!sep || isdigit(sep) || *(++ptr) != sep || *(++ptr) != sep) | 
|  | return false; | 
|  | if (!isdigit(*(++ptr))) | 
|  | return false; | 
|  | *port = *ptr - '0'; | 
|  | while (isdigit(*(++ptr))) { | 
|  | *port *= 10; | 
|  | *port += *ptr - '0'; | 
|  | } | 
|  | if (*ptr != sep) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // There are two way we can receive IP address and port. | 
|  | // (127,0,0,1,23,21) IP address and port encapsulated in (). | 
|  | // 127,0,0,1,23,21  IP address and port without (). | 
|  | // | 
|  | // See RFC 959, Section 4.1.2 | 
|  | bool ExtractPortFromPASVResponse(const FtpCtrlResponse& response, int* port) { | 
|  | if (response.lines.size() != 1) | 
|  | return false; | 
|  |  | 
|  | std::string line(response.lines[0]); | 
|  | if (!base::IsStringASCII(line)) | 
|  | return false; | 
|  | if (line.length() < 2) | 
|  | return false; | 
|  |  | 
|  | size_t paren_pos = line.find('('); | 
|  | if (paren_pos == std::string::npos) { | 
|  | // Find the first comma and use it to locate the beginning | 
|  | // of the response data. | 
|  | size_t comma_pos = line.find(','); | 
|  | if (comma_pos == std::string::npos) | 
|  | return false; | 
|  |  | 
|  | size_t space_pos = line.rfind(' ', comma_pos); | 
|  | if (space_pos != std::string::npos) | 
|  | line = line.substr(space_pos + 1); | 
|  | } else { | 
|  | // Remove the parentheses and use the text inside them. | 
|  | size_t closing_paren_pos = line.rfind(')'); | 
|  | if (closing_paren_pos == std::string::npos) | 
|  | return false; | 
|  | if (closing_paren_pos <= paren_pos) | 
|  | return false; | 
|  |  | 
|  | line = line.substr(paren_pos + 1, closing_paren_pos - paren_pos - 1); | 
|  | } | 
|  |  | 
|  | // Split the line into comma-separated pieces and extract | 
|  | // the last two. | 
|  | std::vector<base::StringPiece> pieces = base::SplitStringPiece( | 
|  | line, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); | 
|  | if (pieces.size() != 6) | 
|  | return false; | 
|  |  | 
|  | // Ignore the IP address supplied in the response. We are always going | 
|  | // to connect back to the same server to prevent FTP PASV port scanning. | 
|  | int p0, p1; | 
|  | if (!base::StringToInt(pieces[4], &p0)) | 
|  | return false; | 
|  | if (!base::StringToInt(pieces[5], &p1)) | 
|  | return false; | 
|  | *port = (p0 << 8) + p1; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | FtpNetworkTransaction::FtpNetworkTransaction( | 
|  | HostResolver* resolver, | 
|  | ClientSocketFactory* socket_factory) | 
|  | : command_sent_(COMMAND_NONE), | 
|  | io_callback_(base::Bind(&FtpNetworkTransaction::OnIOComplete, | 
|  | base::Unretained(this))), | 
|  | request_(NULL), | 
|  | resolver_(resolver), | 
|  | read_ctrl_buf_(new IOBuffer(kCtrlBufLen)), | 
|  | read_data_buf_len_(0), | 
|  | last_error_(OK), | 
|  | system_type_(SYSTEM_TYPE_UNKNOWN), | 
|  | // Use image (binary) transfer by default. It should always work, | 
|  | // whereas the ascii transfer may damage binary data. | 
|  | data_type_(DATA_TYPE_IMAGE), | 
|  | resource_type_(RESOURCE_TYPE_UNKNOWN), | 
|  | use_epsv_(true), | 
|  | data_connection_port_(0), | 
|  | socket_factory_(socket_factory), | 
|  | next_state_(STATE_NONE), | 
|  | state_after_data_connect_complete_(STATE_NONE) { | 
|  | } | 
|  |  | 
|  | FtpNetworkTransaction::~FtpNetworkTransaction() { | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::Stop(int error) { | 
|  | if (command_sent_ == COMMAND_QUIT) | 
|  | return error; | 
|  |  | 
|  | next_state_ = STATE_CTRL_WRITE_QUIT; | 
|  | last_error_ = error; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::Start(const FtpRequestInfo* request_info, | 
|  | const CompletionCallback& callback, | 
|  | const BoundNetLog& net_log) { | 
|  | net_log_ = net_log; | 
|  | request_ = request_info; | 
|  |  | 
|  | ctrl_response_buffer_.reset(new FtpCtrlResponseBuffer(net_log_)); | 
|  |  | 
|  | if (request_->url.has_username()) { | 
|  | base::string16 username; | 
|  | base::string16 password; | 
|  | GetIdentityFromURL(request_->url, &username, &password); | 
|  | credentials_.Set(username, password); | 
|  | } else { | 
|  | credentials_.Set(base::ASCIIToUTF16("anonymous"), | 
|  | base::ASCIIToUTF16("chrome@example.com")); | 
|  | } | 
|  |  | 
|  | DetectTypecode(); | 
|  |  | 
|  | next_state_ = STATE_CTRL_RESOLVE_HOST; | 
|  | int rv = DoLoop(OK); | 
|  | if (rv == ERR_IO_PENDING) | 
|  | user_callback_ = callback; | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::RestartWithAuth(const AuthCredentials& credentials, | 
|  | const CompletionCallback& callback) { | 
|  | ResetStateForRestart(); | 
|  |  | 
|  | credentials_ = credentials; | 
|  |  | 
|  | next_state_ = STATE_CTRL_RESOLVE_HOST; | 
|  | int rv = DoLoop(OK); | 
|  | if (rv == ERR_IO_PENDING) | 
|  | user_callback_ = callback; | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::Read(IOBuffer* buf, | 
|  | int buf_len, | 
|  | const CompletionCallback& callback) { | 
|  | DCHECK(buf); | 
|  | DCHECK_GT(buf_len, 0); | 
|  |  | 
|  | read_data_buf_ = buf; | 
|  | read_data_buf_len_ = buf_len; | 
|  |  | 
|  | next_state_ = STATE_DATA_READ; | 
|  | int rv = DoLoop(OK); | 
|  | if (rv == ERR_IO_PENDING) | 
|  | user_callback_ = callback; | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | const FtpResponseInfo* FtpNetworkTransaction::GetResponseInfo() const { | 
|  | return &response_; | 
|  | } | 
|  |  | 
|  | LoadState FtpNetworkTransaction::GetLoadState() const { | 
|  | if (next_state_ == STATE_CTRL_RESOLVE_HOST_COMPLETE) | 
|  | return LOAD_STATE_RESOLVING_HOST; | 
|  |  | 
|  | if (next_state_ == STATE_CTRL_CONNECT_COMPLETE || | 
|  | next_state_ == STATE_DATA_CONNECT_COMPLETE) | 
|  | return LOAD_STATE_CONNECTING; | 
|  |  | 
|  | if (next_state_ == STATE_DATA_READ_COMPLETE) | 
|  | return LOAD_STATE_READING_RESPONSE; | 
|  |  | 
|  | if (command_sent_ == COMMAND_RETR && read_data_buf_.get()) | 
|  | return LOAD_STATE_READING_RESPONSE; | 
|  |  | 
|  | if (command_sent_ == COMMAND_QUIT) | 
|  | return LOAD_STATE_IDLE; | 
|  |  | 
|  | if (command_sent_ != COMMAND_NONE) | 
|  | return LOAD_STATE_SENDING_REQUEST; | 
|  |  | 
|  | return LOAD_STATE_IDLE; | 
|  | } | 
|  |  | 
|  | uint64 FtpNetworkTransaction::GetUploadProgress() const { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void FtpNetworkTransaction::ResetStateForRestart() { | 
|  | command_sent_ = COMMAND_NONE; | 
|  | user_callback_.Reset(); | 
|  | response_ = FtpResponseInfo(); | 
|  | read_ctrl_buf_ = new IOBuffer(kCtrlBufLen); | 
|  | ctrl_response_buffer_.reset(new FtpCtrlResponseBuffer(net_log_)); | 
|  | read_data_buf_ = NULL; | 
|  | read_data_buf_len_ = 0; | 
|  | if (write_buf_.get()) | 
|  | write_buf_->SetOffset(0); | 
|  | last_error_ = OK; | 
|  | data_connection_port_ = 0; | 
|  | ctrl_socket_.reset(); | 
|  | data_socket_.reset(); | 
|  | next_state_ = STATE_NONE; | 
|  | state_after_data_connect_complete_ = STATE_NONE; | 
|  | } | 
|  |  | 
|  | void FtpNetworkTransaction::EstablishDataConnection(State state_after_connect) { | 
|  | DCHECK(state_after_connect == STATE_CTRL_WRITE_RETR || | 
|  | state_after_connect == STATE_CTRL_WRITE_LIST); | 
|  | state_after_data_connect_complete_ = state_after_connect; | 
|  | next_state_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV; | 
|  | } | 
|  |  | 
|  | void FtpNetworkTransaction::DoCallback(int rv) { | 
|  | DCHECK(rv != ERR_IO_PENDING); | 
|  | DCHECK(!user_callback_.is_null()); | 
|  |  | 
|  | // Since Run may result in Read being called, clear callback_ up front. | 
|  | CompletionCallback c = user_callback_; | 
|  | user_callback_.Reset(); | 
|  | c.Run(rv); | 
|  | } | 
|  |  | 
|  | void FtpNetworkTransaction::OnIOComplete(int result) { | 
|  | int rv = DoLoop(result); | 
|  | if (rv != ERR_IO_PENDING) | 
|  | DoCallback(rv); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessCtrlResponse() { | 
|  | FtpCtrlResponse response = ctrl_response_buffer_->PopResponse(); | 
|  |  | 
|  | int rv = OK; | 
|  | switch (command_sent_) { | 
|  | case COMMAND_NONE: | 
|  | // TODO(phajdan.jr): https://crbug.com/526721: Check for errors in the | 
|  | // welcome message. | 
|  | next_state_ = STATE_CTRL_WRITE_USER; | 
|  | break; | 
|  | case COMMAND_USER: | 
|  | rv = ProcessResponseUSER(response); | 
|  | break; | 
|  | case COMMAND_PASS: | 
|  | rv = ProcessResponsePASS(response); | 
|  | break; | 
|  | case COMMAND_SYST: | 
|  | rv = ProcessResponseSYST(response); | 
|  | break; | 
|  | case COMMAND_PWD: | 
|  | rv = ProcessResponsePWD(response); | 
|  | break; | 
|  | case COMMAND_TYPE: | 
|  | rv = ProcessResponseTYPE(response); | 
|  | break; | 
|  | case COMMAND_EPSV: | 
|  | rv = ProcessResponseEPSV(response); | 
|  | break; | 
|  | case COMMAND_PASV: | 
|  | rv = ProcessResponsePASV(response); | 
|  | break; | 
|  | case COMMAND_SIZE: | 
|  | rv = ProcessResponseSIZE(response); | 
|  | break; | 
|  | case COMMAND_RETR: | 
|  | rv = ProcessResponseRETR(response); | 
|  | break; | 
|  | case COMMAND_CWD: | 
|  | rv = ProcessResponseCWD(response); | 
|  | break; | 
|  | case COMMAND_LIST: | 
|  | rv = ProcessResponseLIST(response); | 
|  | break; | 
|  | case COMMAND_QUIT: | 
|  | rv = ProcessResponseQUIT(response); | 
|  | break; | 
|  | default: | 
|  | LOG(DFATAL) << "Unexpected value of command_sent_: " << command_sent_; | 
|  | return ERR_UNEXPECTED; | 
|  | } | 
|  |  | 
|  | // We may get multiple responses for some commands, | 
|  | // see http://crbug.com/18036. | 
|  | while (ctrl_response_buffer_->ResponseAvailable() && rv == OK) { | 
|  | response = ctrl_response_buffer_->PopResponse(); | 
|  |  | 
|  | switch (command_sent_) { | 
|  | case COMMAND_RETR: | 
|  | rv = ProcessResponseRETR(response); | 
|  | break; | 
|  | case COMMAND_LIST: | 
|  | rv = ProcessResponseLIST(response); | 
|  | break; | 
|  | default: | 
|  | // Multiple responses for other commands are invalid. | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | } | 
|  | } | 
|  |  | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | // Used to prepare and send FTP command. | 
|  | int FtpNetworkTransaction::SendFtpCommand(const std::string& command, | 
|  | const std::string& command_for_log, | 
|  | Command cmd) { | 
|  | // If we send a new command when we still have unprocessed responses | 
|  | // for previous commands, the response receiving code will have no way to know | 
|  | // which responses are for which command. | 
|  | DCHECK(!ctrl_response_buffer_->ResponseAvailable()); | 
|  |  | 
|  | DCHECK(!write_command_buf_.get()); | 
|  | DCHECK(!write_buf_.get()); | 
|  |  | 
|  | if (!IsValidFTPCommandString(command)) { | 
|  | // Callers should validate the command themselves and return a more specific | 
|  | // error code. | 
|  | NOTREACHED(); | 
|  | return Stop(ERR_UNEXPECTED); | 
|  | } | 
|  |  | 
|  | command_sent_ = cmd; | 
|  |  | 
|  | write_command_buf_ = new IOBufferWithSize(command.length() + 2); | 
|  | write_buf_ = new DrainableIOBuffer(write_command_buf_.get(), | 
|  | write_command_buf_->size()); | 
|  | memcpy(write_command_buf_->data(), command.data(), command.length()); | 
|  | memcpy(write_command_buf_->data() + command.length(), kCRLF, 2); | 
|  |  | 
|  | net_log_.AddEvent(NetLog::TYPE_FTP_COMMAND_SENT, | 
|  | NetLog::StringCallback("command", &command_for_log)); | 
|  |  | 
|  | next_state_ = STATE_CTRL_WRITE; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | std::string FtpNetworkTransaction::GetRequestPathForFtpCommand( | 
|  | bool is_directory) const { | 
|  | std::string path(current_remote_directory_); | 
|  | if (request_->url.has_path()) { | 
|  | std::string gurl_path(request_->url.path()); | 
|  |  | 
|  | // Get rid of the typecode, see RFC 1738 section 3.2.2. FTP url-path. | 
|  | std::string::size_type pos = gurl_path.rfind(';'); | 
|  | if (pos != std::string::npos) | 
|  | gurl_path.resize(pos); | 
|  |  | 
|  | path.append(gurl_path); | 
|  | } | 
|  | // Make sure that if the path is expected to be a file, it won't end | 
|  | // with a trailing slash. | 
|  | if (!is_directory && path.length() > 1 && path[path.length() - 1] == '/') | 
|  | path.erase(path.length() - 1); | 
|  | UnescapeRule::Type unescape_rules = UnescapeRule::SPACES | | 
|  | UnescapeRule::URL_SPECIAL_CHARS; | 
|  | // This may unescape to non-ASCII characters, but we allow that. See the | 
|  | // comment for IsValidFTPCommandString. | 
|  | path = UnescapeURLComponent(path, unescape_rules); | 
|  |  | 
|  | if (system_type_ == SYSTEM_TYPE_VMS) { | 
|  | if (is_directory) | 
|  | path = FtpUtil::UnixDirectoryPathToVMS(path); | 
|  | else | 
|  | path = FtpUtil::UnixFilePathToVMS(path); | 
|  | } | 
|  |  | 
|  | DCHECK(IsValidFTPCommandString(path)); | 
|  | return path; | 
|  | } | 
|  |  | 
|  | void FtpNetworkTransaction::DetectTypecode() { | 
|  | if (!request_->url.has_path()) | 
|  | return; | 
|  | std::string gurl_path(request_->url.path()); | 
|  |  | 
|  | // Extract the typecode, see RFC 1738 section 3.2.2. FTP url-path. | 
|  | std::string::size_type pos = gurl_path.rfind(';'); | 
|  | if (pos == std::string::npos) | 
|  | return; | 
|  | std::string typecode_string(gurl_path.substr(pos)); | 
|  | if (typecode_string == ";type=a") { | 
|  | data_type_ = DATA_TYPE_ASCII; | 
|  | resource_type_ = RESOURCE_TYPE_FILE; | 
|  | } else if (typecode_string == ";type=i") { | 
|  | data_type_ = DATA_TYPE_IMAGE; | 
|  | resource_type_ = RESOURCE_TYPE_FILE; | 
|  | } else if (typecode_string == ";type=d") { | 
|  | resource_type_ = RESOURCE_TYPE_DIRECTORY; | 
|  | } | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::DoLoop(int result) { | 
|  | DCHECK(next_state_ != STATE_NONE); | 
|  |  | 
|  | int rv = result; | 
|  | do { | 
|  | State state = next_state_; | 
|  | next_state_ = STATE_NONE; | 
|  | switch (state) { | 
|  | case STATE_CTRL_RESOLVE_HOST: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlResolveHost(); | 
|  | break; | 
|  | case STATE_CTRL_RESOLVE_HOST_COMPLETE: | 
|  | rv = DoCtrlResolveHostComplete(rv); | 
|  | break; | 
|  | case STATE_CTRL_CONNECT: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlConnect(); | 
|  | break; | 
|  | case STATE_CTRL_CONNECT_COMPLETE: | 
|  | rv = DoCtrlConnectComplete(rv); | 
|  | break; | 
|  | case STATE_CTRL_READ: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlRead(); | 
|  | break; | 
|  | case STATE_CTRL_READ_COMPLETE: | 
|  | rv = DoCtrlReadComplete(rv); | 
|  | break; | 
|  | case STATE_CTRL_WRITE: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlWrite(); | 
|  | break; | 
|  | case STATE_CTRL_WRITE_COMPLETE: | 
|  | rv = DoCtrlWriteComplete(rv); | 
|  | break; | 
|  | case STATE_CTRL_WRITE_USER: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlWriteUSER(); | 
|  | break; | 
|  | case STATE_CTRL_WRITE_PASS: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlWritePASS(); | 
|  | break; | 
|  | case STATE_CTRL_WRITE_SYST: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlWriteSYST(); | 
|  | break; | 
|  | case STATE_CTRL_WRITE_PWD: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlWritePWD(); | 
|  | break; | 
|  | case STATE_CTRL_WRITE_TYPE: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlWriteTYPE(); | 
|  | break; | 
|  | case STATE_CTRL_WRITE_EPSV: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlWriteEPSV(); | 
|  | break; | 
|  | case STATE_CTRL_WRITE_PASV: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlWritePASV(); | 
|  | break; | 
|  | case STATE_CTRL_WRITE_RETR: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlWriteRETR(); | 
|  | break; | 
|  | case STATE_CTRL_WRITE_SIZE: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlWriteSIZE(); | 
|  | break; | 
|  | case STATE_CTRL_WRITE_CWD: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlWriteCWD(); | 
|  | break; | 
|  | case STATE_CTRL_WRITE_LIST: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlWriteLIST(); | 
|  | break; | 
|  | case STATE_CTRL_WRITE_QUIT: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoCtrlWriteQUIT(); | 
|  | break; | 
|  | case STATE_DATA_CONNECT: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoDataConnect(); | 
|  | break; | 
|  | case STATE_DATA_CONNECT_COMPLETE: | 
|  | rv = DoDataConnectComplete(rv); | 
|  | break; | 
|  | case STATE_DATA_READ: | 
|  | DCHECK(rv == OK); | 
|  | rv = DoDataRead(); | 
|  | break; | 
|  | case STATE_DATA_READ_COMPLETE: | 
|  | rv = DoDataReadComplete(rv); | 
|  | break; | 
|  | default: | 
|  | NOTREACHED() << "bad state"; | 
|  | rv = ERR_UNEXPECTED; | 
|  | break; | 
|  | } | 
|  | } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::DoCtrlResolveHost() { | 
|  | next_state_ = STATE_CTRL_RESOLVE_HOST_COMPLETE; | 
|  |  | 
|  | HostResolver::RequestInfo info(HostPortPair::FromURL(request_->url)); | 
|  | // No known referrer. | 
|  | return resolver_.Resolve( | 
|  | info, | 
|  | DEFAULT_PRIORITY, | 
|  | &addresses_, | 
|  | base::Bind(&FtpNetworkTransaction::OnIOComplete, base::Unretained(this)), | 
|  | net_log_); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::DoCtrlResolveHostComplete(int result) { | 
|  | if (result == OK) | 
|  | next_state_ = STATE_CTRL_CONNECT; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::DoCtrlConnect() { | 
|  | next_state_ = STATE_CTRL_CONNECT_COMPLETE; | 
|  | ctrl_socket_ = socket_factory_->CreateTransportClientSocket( | 
|  | addresses_, net_log_.net_log(), net_log_.source()); | 
|  | net_log_.AddEvent( | 
|  | NetLog::TYPE_FTP_CONTROL_CONNECTION, | 
|  | ctrl_socket_->NetLog().source().ToEventParametersCallback()); | 
|  | return ctrl_socket_->Connect(io_callback_); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::DoCtrlConnectComplete(int result) { | 
|  | if (result == OK) { | 
|  | // Put the peer's IP address and port into the response. | 
|  | IPEndPoint ip_endpoint; | 
|  | result = ctrl_socket_->GetPeerAddress(&ip_endpoint); | 
|  | if (result == OK) { | 
|  | response_.socket_address = HostPortPair::FromIPEndPoint(ip_endpoint); | 
|  | next_state_ = STATE_CTRL_READ; | 
|  |  | 
|  | if (ip_endpoint.GetFamily() == ADDRESS_FAMILY_IPV4) { | 
|  | // Do not use EPSV for IPv4 connections. Some servers become confused | 
|  | // and we time out while waiting to connect. PASV is perfectly fine for | 
|  | // IPv4. Note that this blacklists IPv4 not to use EPSV instead of | 
|  | // whitelisting IPv6 to use it, to make the code more future-proof: | 
|  | // all future protocols should just use EPSV. | 
|  | use_epsv_ = false; | 
|  | } | 
|  | } | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::DoCtrlRead() { | 
|  | next_state_ = STATE_CTRL_READ_COMPLETE; | 
|  | return ctrl_socket_->Read(read_ctrl_buf_.get(), kCtrlBufLen, io_callback_); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::DoCtrlReadComplete(int result) { | 
|  | if (result == 0) { | 
|  | // Some servers (for example Pure-FTPd) apparently close the control | 
|  | // connection when anonymous login is not permitted. For more details | 
|  | // see http://crbug.com/25023. | 
|  | if (command_sent_ == COMMAND_USER && | 
|  | credentials_.username() == base::ASCIIToUTF16("anonymous")) { | 
|  | response_.needs_auth = true; | 
|  | } | 
|  | return Stop(ERR_EMPTY_RESPONSE); | 
|  | } | 
|  | if (result < 0) | 
|  | return Stop(result); | 
|  |  | 
|  | ctrl_response_buffer_->ConsumeData(read_ctrl_buf_->data(), result); | 
|  |  | 
|  | if (!ctrl_response_buffer_->ResponseAvailable()) { | 
|  | // Read more data from the control socket. | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | return ProcessCtrlResponse(); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::DoCtrlWrite() { | 
|  | next_state_ = STATE_CTRL_WRITE_COMPLETE; | 
|  |  | 
|  | return ctrl_socket_->Write( | 
|  | write_buf_.get(), write_buf_->BytesRemaining(), io_callback_); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::DoCtrlWriteComplete(int result) { | 
|  | if (result < 0) | 
|  | return result; | 
|  |  | 
|  | write_buf_->DidConsume(result); | 
|  | if (write_buf_->BytesRemaining() == 0) { | 
|  | // Clear the write buffer. | 
|  | write_buf_ = NULL; | 
|  | write_command_buf_ = NULL; | 
|  |  | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | } else { | 
|  | next_state_ = STATE_CTRL_WRITE; | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // FTP Commands and responses | 
|  |  | 
|  | // USER Command. | 
|  | int FtpNetworkTransaction::DoCtrlWriteUSER() { | 
|  | std::string command = "USER " + base::UTF16ToUTF8(credentials_.username()); | 
|  |  | 
|  | if (!IsValidFTPCommandString(command)) | 
|  | return Stop(ERR_MALFORMED_IDENTITY); | 
|  |  | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return SendFtpCommand(command, "USER ***", COMMAND_USER); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessResponseUSER( | 
|  | const FtpCtrlResponse& response) { | 
|  | switch (GetErrorClass(response.status_code)) { | 
|  | case ERROR_CLASS_OK: | 
|  | next_state_ = STATE_CTRL_WRITE_SYST; | 
|  | break; | 
|  | case ERROR_CLASS_INFO_NEEDED: | 
|  | next_state_ = STATE_CTRL_WRITE_PASS; | 
|  | break; | 
|  | case ERROR_CLASS_TRANSIENT_ERROR: | 
|  | case ERROR_CLASS_PERMANENT_ERROR: | 
|  | response_.needs_auth = true; | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return Stop(ERR_UNEXPECTED); | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // PASS command. | 
|  | int FtpNetworkTransaction::DoCtrlWritePASS() { | 
|  | std::string command = "PASS " + base::UTF16ToUTF8(credentials_.password()); | 
|  |  | 
|  | if (!IsValidFTPCommandString(command)) | 
|  | return Stop(ERR_MALFORMED_IDENTITY); | 
|  |  | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return SendFtpCommand(command, "PASS ***", COMMAND_PASS); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessResponsePASS( | 
|  | const FtpCtrlResponse& response) { | 
|  | switch (GetErrorClass(response.status_code)) { | 
|  | case ERROR_CLASS_OK: | 
|  | next_state_ = STATE_CTRL_WRITE_SYST; | 
|  | break; | 
|  | case ERROR_CLASS_INFO_NEEDED: | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | case ERROR_CLASS_TRANSIENT_ERROR: | 
|  | case ERROR_CLASS_PERMANENT_ERROR: | 
|  | response_.needs_auth = true; | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return Stop(ERR_UNEXPECTED); | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // SYST command. | 
|  | int FtpNetworkTransaction::DoCtrlWriteSYST() { | 
|  | std::string command = "SYST"; | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return SendFtpCommand(command, command, COMMAND_SYST); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessResponseSYST( | 
|  | const FtpCtrlResponse& response) { | 
|  | switch (GetErrorClass(response.status_code)) { | 
|  | case ERROR_CLASS_INITIATED: | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | case ERROR_CLASS_OK: { | 
|  | // All important info should be on the first line. | 
|  | std::string line = response.lines[0]; | 
|  | // The response should be ASCII, which allows us to do case-insensitive | 
|  | // comparisons easily. If it is not ASCII, we leave the system type | 
|  | // as unknown. | 
|  | if (base::IsStringASCII(line)) { | 
|  | line = base::ToLowerASCII(line); | 
|  |  | 
|  | // Remove all whitespace, to correctly handle cases like fancy "V M S" | 
|  | // response instead of "VMS". | 
|  | base::RemoveChars(line, base::kWhitespaceASCII, &line); | 
|  |  | 
|  | // The "magic" strings we test for below have been gathered by an | 
|  | // empirical study. VMS needs to come first because some VMS systems | 
|  | // also respond with "UNIX emulation", which is not perfect. It is much | 
|  | // more reliable to talk to these servers in their native language. | 
|  | if (line.find("vms") != std::string::npos) { | 
|  | system_type_ = SYSTEM_TYPE_VMS; | 
|  | } else if (line.find("l8") != std::string::npos || | 
|  | line.find("unix") != std::string::npos || | 
|  | line.find("bsd") != std::string::npos) { | 
|  | system_type_ = SYSTEM_TYPE_UNIX; | 
|  | } else if (line.find("win32") != std::string::npos || | 
|  | line.find("windows") != std::string::npos) { | 
|  | system_type_ = SYSTEM_TYPE_WINDOWS; | 
|  | } else if (line.find("os/2") != std::string::npos) { | 
|  | system_type_ = SYSTEM_TYPE_OS2; | 
|  | } | 
|  | } | 
|  | next_state_ = STATE_CTRL_WRITE_PWD; | 
|  | break; | 
|  | } | 
|  | case ERROR_CLASS_INFO_NEEDED: | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | case ERROR_CLASS_TRANSIENT_ERROR: | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | case ERROR_CLASS_PERMANENT_ERROR: | 
|  | // Server does not recognize the SYST command so proceed. | 
|  | next_state_ = STATE_CTRL_WRITE_PWD; | 
|  | break; | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return Stop(ERR_UNEXPECTED); | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // PWD command. | 
|  | int FtpNetworkTransaction::DoCtrlWritePWD() { | 
|  | std::string command = "PWD"; | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return SendFtpCommand(command, command, COMMAND_PWD); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessResponsePWD(const FtpCtrlResponse& response) { | 
|  | switch (GetErrorClass(response.status_code)) { | 
|  | case ERROR_CLASS_INITIATED: | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | case ERROR_CLASS_OK: { | 
|  | // The info we look for should be on the first line. | 
|  | std::string line = response.lines[0]; | 
|  | if (line.empty()) | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | std::string::size_type quote_pos = line.find('"'); | 
|  | if (quote_pos != std::string::npos) { | 
|  | line = line.substr(quote_pos + 1); | 
|  | quote_pos = line.find('"'); | 
|  | if (quote_pos == std::string::npos) | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | line = line.substr(0, quote_pos); | 
|  | } | 
|  | if (system_type_ == SYSTEM_TYPE_VMS) | 
|  | line = FtpUtil::VMSPathToUnix(line); | 
|  | if (line.length() && line[line.length() - 1] == '/') | 
|  | line.erase(line.length() - 1); | 
|  | current_remote_directory_ = line; | 
|  | next_state_ = STATE_CTRL_WRITE_TYPE; | 
|  | break; | 
|  | } | 
|  | case ERROR_CLASS_INFO_NEEDED: | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | case ERROR_CLASS_TRANSIENT_ERROR: | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | case ERROR_CLASS_PERMANENT_ERROR: | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return Stop(ERR_UNEXPECTED); | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // TYPE command. | 
|  | int FtpNetworkTransaction::DoCtrlWriteTYPE() { | 
|  | std::string command = "TYPE "; | 
|  | if (data_type_ == DATA_TYPE_ASCII) { | 
|  | command += "A"; | 
|  | } else if (data_type_ == DATA_TYPE_IMAGE) { | 
|  | command += "I"; | 
|  | } else { | 
|  | NOTREACHED(); | 
|  | return Stop(ERR_UNEXPECTED); | 
|  | } | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return SendFtpCommand(command, command, COMMAND_TYPE); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessResponseTYPE( | 
|  | const FtpCtrlResponse& response) { | 
|  | switch (GetErrorClass(response.status_code)) { | 
|  | case ERROR_CLASS_INITIATED: | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | case ERROR_CLASS_OK: | 
|  | next_state_ = STATE_CTRL_WRITE_SIZE; | 
|  | break; | 
|  | case ERROR_CLASS_INFO_NEEDED: | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | case ERROR_CLASS_TRANSIENT_ERROR: | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | case ERROR_CLASS_PERMANENT_ERROR: | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return Stop(ERR_UNEXPECTED); | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // EPSV command | 
|  | int FtpNetworkTransaction::DoCtrlWriteEPSV() { | 
|  | const std::string command = "EPSV"; | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return SendFtpCommand(command, command, COMMAND_EPSV); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessResponseEPSV( | 
|  | const FtpCtrlResponse& response) { | 
|  | switch (GetErrorClass(response.status_code)) { | 
|  | case ERROR_CLASS_INITIATED: | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | case ERROR_CLASS_OK: { | 
|  | int port; | 
|  | if (!ExtractPortFromEPSVResponse(response, &port)) | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | if (IsWellKnownPort(port) || | 
|  | !IsPortAllowedForScheme(port, url::kFtpScheme)) { | 
|  | return Stop(ERR_UNSAFE_PORT); | 
|  | } | 
|  | data_connection_port_ = static_cast<uint16>(port); | 
|  | next_state_ = STATE_DATA_CONNECT; | 
|  | break; | 
|  | } | 
|  | case ERROR_CLASS_INFO_NEEDED: | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | case ERROR_CLASS_TRANSIENT_ERROR: | 
|  | case ERROR_CLASS_PERMANENT_ERROR: | 
|  | use_epsv_ = false; | 
|  | next_state_ = STATE_CTRL_WRITE_PASV; | 
|  | return OK; | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return Stop(ERR_UNEXPECTED); | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // PASV command | 
|  | int FtpNetworkTransaction::DoCtrlWritePASV() { | 
|  | std::string command = "PASV"; | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return SendFtpCommand(command, command, COMMAND_PASV); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessResponsePASV( | 
|  | const FtpCtrlResponse& response) { | 
|  | switch (GetErrorClass(response.status_code)) { | 
|  | case ERROR_CLASS_INITIATED: | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | case ERROR_CLASS_OK: { | 
|  | int port; | 
|  | if (!ExtractPortFromPASVResponse(response, &port)) | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | if (IsWellKnownPort(port) || | 
|  | !IsPortAllowedForScheme(port, url::kFtpScheme)) { | 
|  | return Stop(ERR_UNSAFE_PORT); | 
|  | } | 
|  | data_connection_port_ = static_cast<uint16>(port); | 
|  | next_state_ = STATE_DATA_CONNECT; | 
|  | break; | 
|  | } | 
|  | case ERROR_CLASS_INFO_NEEDED: | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | case ERROR_CLASS_TRANSIENT_ERROR: | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | case ERROR_CLASS_PERMANENT_ERROR: | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return Stop(ERR_UNEXPECTED); | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // RETR command | 
|  | int FtpNetworkTransaction::DoCtrlWriteRETR() { | 
|  | std::string command = "RETR " + GetRequestPathForFtpCommand(false); | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return SendFtpCommand(command, command, COMMAND_RETR); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessResponseRETR( | 
|  | const FtpCtrlResponse& response) { | 
|  | // Resource type should be either filled in by DetectTypecode() or | 
|  | // detected with CWD. RETR is sent only when the resource is a file. | 
|  | DCHECK_EQ(RESOURCE_TYPE_FILE, resource_type_); | 
|  |  | 
|  | switch (GetErrorClass(response.status_code)) { | 
|  | case ERROR_CLASS_INITIATED: | 
|  | // We want the client to start reading the response at this point. | 
|  | // It got here either through Start or RestartWithAuth. We want that | 
|  | // method to complete. Not setting next state here will make DoLoop exit | 
|  | // and in turn make Start/RestartWithAuth complete. | 
|  | break; | 
|  | case ERROR_CLASS_OK: | 
|  | next_state_ = STATE_CTRL_WRITE_QUIT; | 
|  | break; | 
|  | case ERROR_CLASS_INFO_NEEDED: | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | case ERROR_CLASS_TRANSIENT_ERROR: | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | case ERROR_CLASS_PERMANENT_ERROR: | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return Stop(ERR_UNEXPECTED); | 
|  | } | 
|  |  | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // SIZE command | 
|  | int FtpNetworkTransaction::DoCtrlWriteSIZE() { | 
|  | std::string command = "SIZE " + GetRequestPathForFtpCommand(false); | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return SendFtpCommand(command, command, COMMAND_SIZE); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessResponseSIZE( | 
|  | const FtpCtrlResponse& response) { | 
|  | switch (GetErrorClass(response.status_code)) { | 
|  | case ERROR_CLASS_INITIATED: | 
|  | break; | 
|  | case ERROR_CLASS_OK: | 
|  | if (response.lines.size() != 1) | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | int64 size; | 
|  | if (!base::StringToInt64(response.lines[0], &size)) | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | if (size < 0) | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  |  | 
|  | // A successful response to SIZE does not mean the resource is a file. | 
|  | // Some FTP servers (for example, the qnx one) send a SIZE even for | 
|  | // directories. | 
|  | response_.expected_content_size = size; | 
|  | break; | 
|  | case ERROR_CLASS_INFO_NEEDED: | 
|  | break; | 
|  | case ERROR_CLASS_TRANSIENT_ERROR: | 
|  | break; | 
|  | case ERROR_CLASS_PERMANENT_ERROR: | 
|  | // It's possible that SIZE failed because the path is a directory. | 
|  | // TODO(xunjieli): https://crbug.com/526724: Add a test for this case. | 
|  | if (resource_type_ == RESOURCE_TYPE_UNKNOWN && | 
|  | response.status_code != 550) { | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | } | 
|  | break; | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return Stop(ERR_UNEXPECTED); | 
|  | } | 
|  |  | 
|  | // If the resource is known beforehand to be a file, RETR should be issued, | 
|  | // otherwise do CWD which will detect the resource type. | 
|  | if (resource_type_ == RESOURCE_TYPE_FILE) | 
|  | EstablishDataConnection(STATE_CTRL_WRITE_RETR); | 
|  | else | 
|  | next_state_ = STATE_CTRL_WRITE_CWD; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // CWD command | 
|  | int FtpNetworkTransaction::DoCtrlWriteCWD() { | 
|  | std::string command = "CWD " + GetRequestPathForFtpCommand(true); | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return SendFtpCommand(command, command, COMMAND_CWD); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessResponseCWD(const FtpCtrlResponse& response) { | 
|  | // CWD should be invoked only when the resource is not a file. | 
|  | DCHECK_NE(RESOURCE_TYPE_FILE, resource_type_); | 
|  |  | 
|  | switch (GetErrorClass(response.status_code)) { | 
|  | case ERROR_CLASS_INITIATED: | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | case ERROR_CLASS_OK: | 
|  | resource_type_ = RESOURCE_TYPE_DIRECTORY; | 
|  | EstablishDataConnection(STATE_CTRL_WRITE_LIST); | 
|  | break; | 
|  | case ERROR_CLASS_INFO_NEEDED: | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | case ERROR_CLASS_TRANSIENT_ERROR: | 
|  | // Some FTP servers send response 451 (not a valid CWD response according | 
|  | // to RFC 959) instead of 550. | 
|  | if (response.status_code == 451) | 
|  | return ProcessResponseCWDNotADirectory(); | 
|  |  | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | case ERROR_CLASS_PERMANENT_ERROR: | 
|  | if (response.status_code == 550) | 
|  | return ProcessResponseCWDNotADirectory(); | 
|  |  | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return Stop(ERR_UNEXPECTED); | 
|  | } | 
|  |  | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessResponseCWDNotADirectory() { | 
|  | if (resource_type_ == RESOURCE_TYPE_DIRECTORY) { | 
|  | // We're assuming that the resource is a directory, but the server | 
|  | // says it's not true. The most probable interpretation is that it | 
|  | // doesn't exist (with FTP we can't be sure). | 
|  | return Stop(ERR_FILE_NOT_FOUND); | 
|  | } | 
|  |  | 
|  | // If it is not a directory, it is probably a file. | 
|  | resource_type_ = RESOURCE_TYPE_FILE; | 
|  |  | 
|  | EstablishDataConnection(STATE_CTRL_WRITE_RETR); | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // LIST command | 
|  | int FtpNetworkTransaction::DoCtrlWriteLIST() { | 
|  | // Use the -l option for mod_ftp configured in LISTIsNLST mode: the option | 
|  | // forces LIST output instead of NLST (which would be ambiguous for us | 
|  | // to parse). | 
|  | std::string command("LIST -l"); | 
|  | if (system_type_ == SYSTEM_TYPE_VMS) | 
|  | command = "LIST *.*;0"; | 
|  |  | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return SendFtpCommand(command, command, COMMAND_LIST); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessResponseLIST( | 
|  | const FtpCtrlResponse& response) { | 
|  | // Resource type should be either filled in by DetectTypecode() or | 
|  | // detected with CWD. LIST is sent only when the resource is a directory. | 
|  | DCHECK_EQ(RESOURCE_TYPE_DIRECTORY, resource_type_); | 
|  |  | 
|  | switch (GetErrorClass(response.status_code)) { | 
|  | case ERROR_CLASS_INITIATED: | 
|  | // We want the client to start reading the response at this point. | 
|  | // It got here either through Start or RestartWithAuth. We want that | 
|  | // method to complete. Not setting next state here will make DoLoop exit | 
|  | // and in turn make Start/RestartWithAuth complete. | 
|  | response_.is_directory_listing = true; | 
|  | break; | 
|  | case ERROR_CLASS_OK: | 
|  | response_.is_directory_listing = true; | 
|  | next_state_ = STATE_CTRL_WRITE_QUIT; | 
|  | break; | 
|  | case ERROR_CLASS_INFO_NEEDED: | 
|  | return Stop(ERR_INVALID_RESPONSE); | 
|  | case ERROR_CLASS_TRANSIENT_ERROR: | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | case ERROR_CLASS_PERMANENT_ERROR: | 
|  | return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code)); | 
|  | default: | 
|  | NOTREACHED(); | 
|  | return Stop(ERR_UNEXPECTED); | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // QUIT command | 
|  | int FtpNetworkTransaction::DoCtrlWriteQUIT() { | 
|  | std::string command = "QUIT"; | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return SendFtpCommand(command, command, COMMAND_QUIT); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::ProcessResponseQUIT( | 
|  | const FtpCtrlResponse& response) { | 
|  | ctrl_socket_->Disconnect(); | 
|  | return last_error_; | 
|  | } | 
|  |  | 
|  | // Data Connection | 
|  |  | 
|  | int FtpNetworkTransaction::DoDataConnect() { | 
|  | next_state_ = STATE_DATA_CONNECT_COMPLETE; | 
|  | IPEndPoint ip_endpoint; | 
|  | AddressList data_address; | 
|  | // Connect to the same host as the control socket to prevent PASV port | 
|  | // scanning attacks. | 
|  | int rv = ctrl_socket_->GetPeerAddress(&ip_endpoint); | 
|  | if (rv != OK) | 
|  | return Stop(rv); | 
|  | data_address = AddressList::CreateFromIPAddress( | 
|  | ip_endpoint.address(), data_connection_port_); | 
|  | data_socket_ = socket_factory_->CreateTransportClientSocket( | 
|  | data_address, net_log_.net_log(), net_log_.source()); | 
|  | net_log_.AddEvent( | 
|  | NetLog::TYPE_FTP_DATA_CONNECTION, | 
|  | data_socket_->NetLog().source().ToEventParametersCallback()); | 
|  | return data_socket_->Connect(io_callback_); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::DoDataConnectComplete(int result) { | 
|  | if (result != OK && use_epsv_) { | 
|  | // It's possible we hit a broken server, sadly. They can break in different | 
|  | // ways. Some time out, some reset a connection. Fall back to PASV. | 
|  | // TODO(phajdan.jr): https://crbug.com/526723: remember it for future | 
|  | // transactions with this server. | 
|  | use_epsv_ = false; | 
|  | next_state_ = STATE_CTRL_WRITE_PASV; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // Only record the connection error after we've applied all our fallbacks. | 
|  | // We want to capture the final error, one we're not going to recover from. | 
|  | RecordDataConnectionError(result); | 
|  |  | 
|  | if (result != OK) | 
|  | return Stop(result); | 
|  |  | 
|  | next_state_ = state_after_data_connect_complete_; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::DoDataRead() { | 
|  | DCHECK(read_data_buf_.get()); | 
|  | DCHECK_GT(read_data_buf_len_, 0); | 
|  |  | 
|  | if (data_socket_ == NULL || !data_socket_->IsConnected()) { | 
|  | // If we don't destroy the data socket completely, some servers will wait | 
|  | // for us (http://crbug.com/21127). The half-closed TCP connection needs | 
|  | // to be closed on our side too. | 
|  | data_socket_.reset(); | 
|  |  | 
|  | if (ctrl_socket_->IsConnected()) { | 
|  | // Wait for the server's response, we should get it before sending QUIT. | 
|  | next_state_ = STATE_CTRL_READ; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | // We are no longer connected to the server, so just finish the transaction. | 
|  | return Stop(OK); | 
|  | } | 
|  |  | 
|  | next_state_ = STATE_DATA_READ_COMPLETE; | 
|  | read_data_buf_->data()[0] = 0; | 
|  | return data_socket_->Read( | 
|  | read_data_buf_.get(), read_data_buf_len_, io_callback_); | 
|  | } | 
|  |  | 
|  | int FtpNetworkTransaction::DoDataReadComplete(int result) { | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // We're using a histogram as a group of counters, with one bucket for each | 
|  | // enumeration value.  We're only interested in the values of the counters. | 
|  | // Ignore the shape, average, and standard deviation of the histograms because | 
|  | // they are meaningless. | 
|  | // | 
|  | // We use two histograms.  In the first histogram we tally whether the user has | 
|  | // seen an error of that type during the session.  In the second histogram we | 
|  | // tally the total number of times the users sees each errer. | 
|  | void FtpNetworkTransaction::RecordDataConnectionError(int result) { | 
|  | // Gather data for http://crbug.com/3073. See how many users have trouble | 
|  | // establishing FTP data connection in passive FTP mode. | 
|  | enum { | 
|  | // Data connection successful. | 
|  | NET_ERROR_OK = 0, | 
|  |  | 
|  | // Local firewall blocked the connection. | 
|  | NET_ERROR_ACCESS_DENIED = 1, | 
|  |  | 
|  | // Connection timed out. | 
|  | NET_ERROR_TIMED_OUT = 2, | 
|  |  | 
|  | // Connection has been estabilished, but then got broken (either reset | 
|  | // or aborted). | 
|  | NET_ERROR_CONNECTION_BROKEN = 3, | 
|  |  | 
|  | // Connection has been refused. | 
|  | NET_ERROR_CONNECTION_REFUSED = 4, | 
|  |  | 
|  | // No connection to the internet. | 
|  | NET_ERROR_INTERNET_DISCONNECTED = 5, | 
|  |  | 
|  | // Could not reach the destination address. | 
|  | NET_ERROR_ADDRESS_UNREACHABLE = 6, | 
|  |  | 
|  | // A programming error in our network stack. | 
|  | NET_ERROR_UNEXPECTED = 7, | 
|  |  | 
|  | // Other kind of error. | 
|  | NET_ERROR_OTHER = 20, | 
|  |  | 
|  | NUM_OF_NET_ERROR_TYPES | 
|  | } type; | 
|  | switch (result) { | 
|  | case OK: | 
|  | type = NET_ERROR_OK; | 
|  | break; | 
|  | case ERR_ACCESS_DENIED: | 
|  | case ERR_NETWORK_ACCESS_DENIED: | 
|  | type = NET_ERROR_ACCESS_DENIED; | 
|  | break; | 
|  | case ERR_TIMED_OUT: | 
|  | type = NET_ERROR_TIMED_OUT; | 
|  | break; | 
|  | case ERR_CONNECTION_ABORTED: | 
|  | case ERR_CONNECTION_RESET: | 
|  | case ERR_CONNECTION_CLOSED: | 
|  | type = NET_ERROR_CONNECTION_BROKEN; | 
|  | break; | 
|  | case ERR_CONNECTION_FAILED: | 
|  | case ERR_CONNECTION_REFUSED: | 
|  | type = NET_ERROR_CONNECTION_REFUSED; | 
|  | break; | 
|  | case ERR_INTERNET_DISCONNECTED: | 
|  | type = NET_ERROR_INTERNET_DISCONNECTED; | 
|  | break; | 
|  | case ERR_ADDRESS_INVALID: | 
|  | case ERR_ADDRESS_UNREACHABLE: | 
|  | type = NET_ERROR_ADDRESS_UNREACHABLE; | 
|  | break; | 
|  | case ERR_UNEXPECTED: | 
|  | type = NET_ERROR_UNEXPECTED; | 
|  | break; | 
|  | default: | 
|  | type = NET_ERROR_OTHER; | 
|  | break; | 
|  | }; | 
|  | static bool had_error_type[NUM_OF_NET_ERROR_TYPES]; | 
|  |  | 
|  | DCHECK(type >= 0 && type < NUM_OF_NET_ERROR_TYPES); | 
|  | if (!had_error_type[type]) { | 
|  | had_error_type[type] = true; | 
|  | UMA_HISTOGRAM_ENUMERATION("Net.FtpDataConnectionErrorHappened", | 
|  | type, NUM_OF_NET_ERROR_TYPES); | 
|  | } | 
|  | UMA_HISTOGRAM_ENUMERATION("Net.FtpDataConnectionErrorCount", | 
|  | type, NUM_OF_NET_ERROR_TYPES); | 
|  | } | 
|  |  | 
|  | }  // namespace net |