This source file includes following definitions.
- IsValidFTPCommandString
- GetErrorClass
- GetNetErrorCodeForFtpResponseCode
- ExtractPortFromEPSVResponse
- ExtractPortFromPASVResponse
- state_after_data_connect_complete_
- Stop
- RestartIgnoringLastError
- Start
- RestartWithAuth
- Read
- GetResponseInfo
- GetLoadState
- GetUploadProgress
- ResetStateForRestart
- ResetDataConnectionAfterError
- DoCallback
- OnIOComplete
- ProcessCtrlResponse
- SendFtpCommand
- GetRequestPathForFtpCommand
- DetectTypecode
- DoLoop
- DoCtrlResolveHost
- DoCtrlResolveHostComplete
- DoCtrlConnect
- DoCtrlConnectComplete
- DoCtrlRead
- DoCtrlReadComplete
- DoCtrlWrite
- DoCtrlWriteComplete
- DoCtrlWriteUSER
- ProcessResponseUSER
- DoCtrlWritePASS
- ProcessResponsePASS
- DoCtrlWriteSYST
- ProcessResponseSYST
- DoCtrlWritePWD
- ProcessResponsePWD
- DoCtrlWriteTYPE
- ProcessResponseTYPE
- DoCtrlWriteEPSV
- ProcessResponseEPSV
- DoCtrlWritePASV
- ProcessResponsePASV
- DoCtrlWriteRETR
- ProcessResponseRETR
- DoCtrlWriteSIZE
- ProcessResponseSIZE
- DoCtrlWriteCWD
- ProcessResponseCWD
- ProcessResponseCWDNotADirectory
- DoCtrlWriteLIST
- ProcessResponseLIST
- DoCtrlWriteQUIT
- ProcessResponseQUIT
- DoDataConnect
- DoDataConnectComplete
- DoDataRead
- DoDataReadComplete
- RecordDataConnectionError
#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.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/string_split.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_log.h"
#include "net/base/net_util.h"
#include "net/ftp/ftp_network_session.h"
#include "net/ftp/ftp_request_info.h"
#include "net/ftp/ftp_util.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/stream_socket.h"
const char kCRLF[] = "\r\n";
const int kCtrlBufLen = 1024;
namespace {
bool IsValidFTPCommandString(const std::string& input) {
if (input.find_first_of("\r\n") != std::string::npos)
return false;
return true;
}
enum ErrorClass {
ERROR_CLASS_INITIATED,
ERROR_CLASS_OK,
ERROR_CLASS_INFO_NEEDED,
ERROR_CLASS_TRANSIENT_ERROR,
ERROR_CLASS_PERMANENT_ERROR,
};
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;
NOTREACHED() << response_code;
return ERROR_CLASS_PERMANENT_ERROR;
}
int GetNetErrorCodeForFtpResponseCode(int response_code) {
switch (response_code) {
case 421:
return net::ERR_FTP_SERVICE_UNAVAILABLE;
case 426:
return net::ERR_FTP_TRANSFER_ABORTED;
case 450:
return net::ERR_FTP_FILE_BUSY;
case 500:
case 501:
return net::ERR_FTP_SYNTAX_ERROR;
case 502:
case 504:
return net::ERR_FTP_COMMAND_NOT_SUPPORTED;
case 503:
return net::ERR_FTP_BAD_COMMAND_SEQUENCE;
default:
return net::ERR_FTP_FAILED;
}
}
bool ExtractPortFromEPSVResponse(const net::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;
}
bool ExtractPortFromPASVResponse(const net::FtpCtrlResponse& response,
int* port) {
if (response.lines.size() != 1)
return false;
std::string line(response.lines[0]);
if (!IsStringASCII(line))
return false;
if (line.length() < 2)
return false;
size_t paren_pos = line.find('(');
if (paren_pos == std::string::npos) {
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 {
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);
}
std::vector<std::string> pieces;
base::SplitString(line, ',', &pieces);
if (pieces.size() != 6)
return false;
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 net {
FtpNetworkTransaction::FtpNetworkTransaction(
FtpNetworkSession* session,
ClientSocketFactory* socket_factory)
: command_sent_(COMMAND_NONE),
io_callback_(base::Bind(&FtpNetworkTransaction::OnIOComplete,
base::Unretained(this))),
session_(session),
request_(NULL),
resolver_(session->host_resolver()),
read_ctrl_buf_(new IOBuffer(kCtrlBufLen)),
read_data_buf_len_(0),
last_error_(OK),
system_type_(SYSTEM_TYPE_UNKNOWN),
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_CTRL_WRITE_SIZE) {}
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::RestartIgnoringLastError(
const CompletionCallback& callback) {
return ERR_NOT_IMPLEMENTED;
}
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_CTRL_WRITE_SIZE;
}
void FtpNetworkTransaction::ResetDataConnectionAfterError(State next_state) {
state_after_data_connect_complete_ = next_state;
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());
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:
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;
}
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:
return Stop(ERR_INVALID_RESPONSE);
}
}
return rv;
}
int FtpNetworkTransaction::SendFtpCommand(const std::string& command,
const std::string& command_for_log,
Command cmd) {
DCHECK(!ctrl_response_buffer_->ResponseAvailable());
DCHECK(!write_command_buf_.get());
DCHECK(!write_buf_.get());
if (!IsValidFTPCommandString(command)) {
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());
std::string::size_type pos = gurl_path.rfind(';');
if (pos != std::string::npos)
gurl_path.resize(pos);
path.append(gurl_path);
}
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;
path = net::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());
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));
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) {
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) {
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) {
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()) {
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) {
write_buf_ = NULL;
write_command_buf_ = NULL;
next_state_ = STATE_CTRL_READ;
} else {
next_state_ = STATE_CTRL_WRITE;
}
return OK;
}
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;
}
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;
}
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: {
std::string line = response.lines[0];
if (IsStringASCII(line)) {
line = StringToLowerASCII(line);
base::RemoveChars(line, base::kWhitespaceASCII, &line);
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:
next_state_ = STATE_CTRL_WRITE_PWD;
break;
default:
NOTREACHED();
return Stop(ERR_UNEXPECTED);
}
return OK;
}
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: {
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;
}
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_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV;
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;
}
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:
if (!ExtractPortFromEPSVResponse( response, &data_connection_port_))
return Stop(ERR_INVALID_RESPONSE);
if (data_connection_port_ < 1024 ||
!IsPortAllowedByFtp(data_connection_port_))
return Stop(ERR_UNSAFE_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;
}
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:
if (!ExtractPortFromPASVResponse(response, &data_connection_port_))
return Stop(ERR_INVALID_RESPONSE);
if (data_connection_port_ < 1024 ||
!IsPortAllowedByFtp(data_connection_port_))
return Stop(ERR_UNSAFE_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;
}
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) {
switch (GetErrorClass(response.status_code)) {
case ERROR_CLASS_INITIATED:
resource_type_ = RESOURCE_TYPE_FILE;
break;
case ERROR_CLASS_OK:
resource_type_ = RESOURCE_TYPE_FILE;
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:
if (response.status_code != 550 || resource_type_ == RESOURCE_TYPE_FILE)
return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
resource_type_ = RESOURCE_TYPE_DIRECTORY;
next_state_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV;
break;
default:
NOTREACHED();
return Stop(ERR_UNEXPECTED);
}
DCHECK_NE(RESOURCE_TYPE_UNKNOWN, resource_type_);
return OK;
}
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) {
State state_after_size;
if (resource_type_ == RESOURCE_TYPE_FILE)
state_after_size = STATE_CTRL_WRITE_RETR;
else
state_after_size = STATE_CTRL_WRITE_CWD;
switch (GetErrorClass(response.status_code)) {
case ERROR_CLASS_INITIATED:
next_state_ = state_after_size;
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);
response_.expected_content_size = size;
next_state_ = state_after_size;
break;
case ERROR_CLASS_INFO_NEEDED:
next_state_ = state_after_size;
break;
case ERROR_CLASS_TRANSIENT_ERROR:
ResetDataConnectionAfterError(state_after_size);
break;
case ERROR_CLASS_PERMANENT_ERROR:
if (resource_type_ == RESOURCE_TYPE_UNKNOWN &&
response.status_code != 550) {
return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
}
ResetDataConnectionAfterError(state_after_size);
break;
default:
NOTREACHED();
return Stop(ERR_UNEXPECTED);
}
return OK;
}
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) {
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:
next_state_ = STATE_CTRL_WRITE_LIST;
break;
case ERROR_CLASS_INFO_NEEDED:
return Stop(ERR_INVALID_RESPONSE);
case ERROR_CLASS_TRANSIENT_ERROR:
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) {
return Stop(ERR_FILE_NOT_FOUND);
}
resource_type_ = RESOURCE_TYPE_FILE;
ResetDataConnectionAfterError(STATE_CTRL_WRITE_RETR);
return OK;
}
int FtpNetworkTransaction::DoCtrlWriteLIST() {
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) {
switch (GetErrorClass(response.status_code)) {
case ERROR_CLASS_INITIATED:
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;
}
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_;
}
int FtpNetworkTransaction::DoDataConnect() {
next_state_ = STATE_DATA_CONNECT_COMPLETE;
IPEndPoint ip_endpoint;
AddressList data_address;
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_) {
use_epsv_ = false;
next_state_ = STATE_CTRL_WRITE_PASV;
return OK;
}
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()) {
data_socket_.reset();
if (ctrl_socket_->IsConnected()) {
next_state_ = STATE_CTRL_READ;
return OK;
}
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;
}
void FtpNetworkTransaction::RecordDataConnectionError(int result) {
enum {
NET_ERROR_OK = 0,
NET_ERROR_ACCESS_DENIED = 1,
NET_ERROR_TIMED_OUT = 2,
NET_ERROR_CONNECTION_BROKEN = 3,
NET_ERROR_CONNECTION_REFUSED = 4,
NET_ERROR_INTERNET_DISCONNECTED = 5,
NET_ERROR_ADDRESS_UNREACHABLE = 6,
NET_ERROR_UNEXPECTED = 7,
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);
}
}