blob: fd69e7b41b0ebba468c48305ca17f0d5a2695679 [file] [log] [blame]
// 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/containers/circular_deque.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "net/base/io_buffer.h"
#include "net/base/ip_endpoint.h"
#include "net/base/test_completion_callback.h"
#include "net/dns/mock_host_resolver.h"
#include "net/ftp/ftp_request_info.h"
#include "net/socket/socket_test_util.h"
#include "net/test/gtest_util.h"
#include "net/test/test_with_task_environment.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
using net::test::IsError;
using net::test::IsOk;
namespace {
// Size we use for IOBuffers used to receive data from the test data socket.
const int kBufferSize = 128;
} // namespace
namespace net {
class FtpSocketDataProvider : public SocketDataProvider {
public:
enum State {
NONE,
PRE_USER,
PRE_PASSWD,
PRE_SYST,
PRE_PWD,
PRE_TYPE,
PRE_SIZE,
PRE_LIST_EPSV,
PRE_LIST_PASV,
PRE_LIST,
PRE_RETR,
PRE_RETR_EPSV,
PRE_RETR_PASV,
PRE_CWD,
PRE_QUIT,
PRE_NOPASV,
QUIT
};
FtpSocketDataProvider()
: short_read_limit_(0),
allow_unconsumed_reads_(false),
failure_injection_state_(NONE),
multiline_welcome_(false),
use_epsv_(true),
data_type_('I') {
Init();
}
~FtpSocketDataProvider() override = default;
// SocketDataProvider implementation.
MockRead OnRead() override {
if (reads_.empty())
return MockRead(SYNCHRONOUS, ERR_UNEXPECTED);
MockRead result = reads_.front();
if (short_read_limit_ == 0 || result.data_len <= short_read_limit_) {
reads_.pop_front();
} else {
result.data_len = short_read_limit_;
reads_.front().data += result.data_len;
reads_.front().data_len -= result.data_len;
}
return result;
}
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_USER:
return Verify("USER anonymous\r\n", data, PRE_PASSWD,
"331 Password needed\r\n");
case PRE_PASSWD:
{
static const char response_one[] = "230 Welcome\r\n";
static const char response_multi[] =
"230- One\r\n230- Two\r\n230 Three\r\n";
return Verify("PASS chrome@example.com\r\n", data, PRE_SYST,
multiline_welcome_ ? response_multi : response_one);
}
case PRE_SYST:
return Verify("SYST\r\n", data, PRE_PWD, "215 UNIX\r\n");
case PRE_PWD:
return Verify("PWD\r\n", data, PRE_TYPE,
"257 \"/\" is your current location\r\n");
case PRE_TYPE:
return Verify(std::string("TYPE ") + data_type_ + "\r\n", data,
PRE_SIZE, "200 TYPE set successfully\r\n");
case PRE_LIST_EPSV:
return Verify("EPSV\r\n", data, PRE_LIST,
"227 Entering Extended Passive Mode (|||31744|)\r\n");
case PRE_LIST_PASV:
return Verify("PASV\r\n", data, PRE_LIST,
"227 Entering Passive Mode 127,0,0,1,123,123\r\n");
case PRE_RETR_EPSV:
return Verify("EPSV\r\n", data, PRE_RETR,
"227 Entering Extended Passive Mode (|||31744|)\r\n");
case PRE_RETR_PASV:
return Verify("PASV\r\n", data, PRE_RETR,
"227 Entering Passive Mode 127,0,0,1,123,123\r\n");
case PRE_NOPASV:
// Use unallocated 599 FTP error code to make sure it falls into the
// generic ERR_FTP_FAILED bucket.
return Verify("PASV\r\n", data, PRE_QUIT,
"599 fail\r\n");
case PRE_QUIT:
return Verify("QUIT\r\n", data, QUIT, "221 Goodbye.\r\n");
default:
NOTREACHED() << "State not handled " << state();
return MockWriteResult(ASYNC, ERR_UNEXPECTED);
}
}
void InjectFailure(State state, State next_state, const char* response) {
DCHECK_EQ(NONE, failure_injection_state_);
DCHECK_NE(NONE, state);
DCHECK_NE(NONE, next_state);
DCHECK_NE(state, next_state);
failure_injection_state_ = state;
failure_injection_next_state_ = next_state;
fault_response_ = response;
}
State state() const {
return state_;
}
void Reset() override {
reads_.clear();
Init();
}
bool AllReadDataConsumed() const override { return state_ == QUIT; }
bool AllWriteDataConsumed() const override { return state_ == QUIT; }
void set_multiline_welcome(bool multiline) { multiline_welcome_ = multiline; }
bool use_epsv() const { return use_epsv_; }
void set_use_epsv(bool use_epsv) { use_epsv_ = use_epsv; }
void set_data_type(char data_type) { data_type_ = data_type; }
int short_read_limit() const { return short_read_limit_; }
void set_short_read_limit(int limit) { short_read_limit_ = limit; }
void set_allow_unconsumed_reads(bool allow) {
allow_unconsumed_reads_ = allow;
}
protected:
void Init() {
state_ = PRE_USER;
SimulateRead("220 host TestFTPd\r\n");
}
// If protocol fault injection has been requested, adjusts state and mocked
// read and returns true.
bool InjectFault() {
if (state_ != failure_injection_state_)
return false;
SimulateRead(fault_response_);
state_ = failure_injection_next_state_;
return true;
}
MockWriteResult Verify(const std::string& expected,
const std::string& data,
State next_state,
const char* next_read,
const size_t next_read_length) {
EXPECT_EQ(expected, data);
if (expected == data) {
state_ = next_state;
SimulateRead(next_read, next_read_length);
return MockWriteResult(ASYNC, data.length());
}
return MockWriteResult(ASYNC, ERR_UNEXPECTED);
}
MockWriteResult Verify(const std::string& expected,
const std::string& data,
State next_state,
const char* next_read) {
return Verify(expected, data, next_state,
next_read, std::strlen(next_read));
}
// The next time there is a read from this socket, it will return |data|.
// Before calling SimulateRead next time, the previous data must be consumed.
void SimulateRead(const char* data, size_t length) {
if (!allow_unconsumed_reads_) {
EXPECT_TRUE(reads_.empty()) << "Unconsumed read: " << reads_.front().data;
}
reads_.push_back(MockRead(ASYNC, data, length));
}
void SimulateRead(const char* data) { SimulateRead(data, std::strlen(data)); }
private:
// List of reads to be consumed.
base::circular_deque<MockRead> reads_;
// Max number of bytes we will read at a time. 0 means no limit.
int short_read_limit_;
// If true, we'll not require the client to consume all data before we
// mock the next read.
bool allow_unconsumed_reads_;
State state_;
State failure_injection_state_;
State failure_injection_next_state_;
const char* fault_response_;
// If true, we will send multiple 230 lines as response after PASS.
bool multiline_welcome_;
// If true, we will use EPSV command.
bool use_epsv_;
// Data type to be used for TYPE command.
char data_type_;
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProvider);
};
class FtpSocketDataProviderDirectoryListing : public FtpSocketDataProvider {
public:
FtpSocketDataProviderDirectoryListing() = default;
~FtpSocketDataProviderDirectoryListing() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_SIZE:
return Verify("SIZE /\r\n", data, PRE_CWD,
"550 I can only retrieve regular files\r\n");
case PRE_CWD:
return Verify("CWD /\r\n", data,
use_epsv() ? PRE_LIST_EPSV : PRE_LIST_PASV, "200 OK\r\n");
case PRE_LIST:
return Verify("LIST -l\r\n", data, PRE_QUIT, "200 OK\r\n");
default:
return FtpSocketDataProvider::OnWrite(data);
}
}
private:
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderDirectoryListing);
};
class FtpSocketDataProviderDirectoryListingWithPasvFallback
: public FtpSocketDataProviderDirectoryListing {
public:
FtpSocketDataProviderDirectoryListingWithPasvFallback() = default;
~FtpSocketDataProviderDirectoryListingWithPasvFallback() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_LIST_EPSV:
return Verify("EPSV\r\n", data, PRE_LIST_PASV,
"500 no EPSV for you\r\n");
case PRE_SIZE:
return Verify("SIZE /\r\n", data, PRE_CWD,
"550 I can only retrieve regular files\r\n");
default:
return FtpSocketDataProviderDirectoryListing::OnWrite(data);
}
}
private:
DISALLOW_COPY_AND_ASSIGN(
FtpSocketDataProviderDirectoryListingWithPasvFallback);
};
class FtpSocketDataProviderVMSDirectoryListing : public FtpSocketDataProvider {
public:
FtpSocketDataProviderVMSDirectoryListing() = default;
~FtpSocketDataProviderVMSDirectoryListing() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_SYST:
return Verify("SYST\r\n", data, PRE_PWD, "215 VMS\r\n");
case PRE_PWD:
return Verify("PWD\r\n", data, PRE_TYPE,
"257 \"ANONYMOUS_ROOT:[000000]\"\r\n");
case PRE_LIST_EPSV:
return Verify("EPSV\r\n", data, PRE_LIST_PASV,
"500 Invalid command\r\n");
case PRE_SIZE:
return Verify("SIZE ANONYMOUS_ROOT:[000000]dir\r\n", data, PRE_CWD,
"550 I can only retrieve regular files\r\n");
case PRE_CWD:
return Verify("CWD ANONYMOUS_ROOT:[dir]\r\n", data,
use_epsv() ? PRE_LIST_EPSV : PRE_LIST_PASV, "200 OK\r\n");
case PRE_LIST:
return Verify("LIST *.*;0\r\n", data, PRE_QUIT, "200 OK\r\n");
default:
return FtpSocketDataProvider::OnWrite(data);
}
}
private:
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderVMSDirectoryListing);
};
class FtpSocketDataProviderVMSDirectoryListingRootDirectory
: public FtpSocketDataProvider {
public:
FtpSocketDataProviderVMSDirectoryListingRootDirectory() = default;
~FtpSocketDataProviderVMSDirectoryListingRootDirectory() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_SYST:
return Verify("SYST\r\n", data, PRE_PWD, "215 VMS\r\n");
case PRE_PWD:
return Verify("PWD\r\n", data, PRE_TYPE,
"257 \"ANONYMOUS_ROOT:[000000]\"\r\n");
case PRE_LIST_EPSV:
return Verify("EPSV\r\n", data, PRE_LIST_PASV,
"500 EPSV command unknown\r\n");
case PRE_SIZE:
return Verify("SIZE ANONYMOUS_ROOT\r\n", data, PRE_CWD,
"550 I can only retrieve regular files\r\n");
case PRE_CWD:
return Verify("CWD ANONYMOUS_ROOT:[000000]\r\n", data,
use_epsv() ? PRE_LIST_EPSV : PRE_LIST_PASV, "200 OK\r\n");
case PRE_LIST:
return Verify("LIST *.*;0\r\n", data, PRE_QUIT, "200 OK\r\n");
default:
return FtpSocketDataProvider::OnWrite(data);
}
}
private:
DISALLOW_COPY_AND_ASSIGN(
FtpSocketDataProviderVMSDirectoryListingRootDirectory);
};
class FtpSocketDataProviderFileDownloadWithFileTypecode
: public FtpSocketDataProvider {
public:
FtpSocketDataProviderFileDownloadWithFileTypecode() = default;
~FtpSocketDataProviderFileDownloadWithFileTypecode() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_SIZE:
return Verify("SIZE /file\r\n", data,
use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV, "213 18\r\n");
case PRE_RETR:
return Verify("RETR /file\r\n", data, PRE_QUIT, "200 OK\r\n");
default:
return FtpSocketDataProvider::OnWrite(data);
}
}
private:
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadWithFileTypecode);
};
class FtpSocketDataProviderFileDownload : public FtpSocketDataProvider {
public:
FtpSocketDataProviderFileDownload() = default;
~FtpSocketDataProviderFileDownload() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_SIZE:
return Verify(base::StringPrintf("SIZE %s\r\n", file_path_.c_str()),
data, PRE_CWD, "213 18\r\n");
case PRE_CWD:
return Verify(base::StringPrintf("CWD %s\r\n", file_path_.c_str()),
data, use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
"550 Not a directory\r\n");
case PRE_RETR:
return Verify(base::StringPrintf("RETR %s\r\n", file_path_.c_str()),
data, PRE_QUIT, "200 OK\r\n");
default:
return FtpSocketDataProvider::OnWrite(data);
}
}
void set_file_path(const std::string& file_path) { file_path_ = file_path; }
private:
std::string file_path_ = "/file";
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownload);
};
class FtpSocketDataProviderFileNotFound : public FtpSocketDataProvider {
public:
FtpSocketDataProviderFileNotFound() = default;
~FtpSocketDataProviderFileNotFound() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_SIZE:
return Verify("SIZE /file\r\n", data, PRE_CWD,
"550 File Not Found\r\n");
case PRE_CWD:
return Verify("CWD /file\r\n", data,
use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
"550 File Not Found\r\n");
case PRE_RETR:
return Verify("RETR /file\r\n", data, PRE_QUIT,
"550 File Not Found\r\n");
default:
return FtpSocketDataProvider::OnWrite(data);
}
}
private:
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileNotFound);
};
class FtpSocketDataProviderFileDownloadWithPasvFallback
: public FtpSocketDataProviderFileDownload {
public:
FtpSocketDataProviderFileDownloadWithPasvFallback() = default;
~FtpSocketDataProviderFileDownloadWithPasvFallback() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_RETR_EPSV:
return Verify("EPSV\r\n", data, PRE_RETR_PASV, "500 No can do\r\n");
case PRE_CWD:
return Verify("CWD /file\r\n", data,
use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
"550 Not a directory\r\n");
default:
return FtpSocketDataProviderFileDownload::OnWrite(data);
}
}
private:
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadWithPasvFallback);
};
class FtpSocketDataProviderFileDownloadZeroSize
: public FtpSocketDataProviderFileDownload {
public:
FtpSocketDataProviderFileDownloadZeroSize() = default;
~FtpSocketDataProviderFileDownloadZeroSize() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_SIZE:
return Verify("SIZE /file\r\n", data, PRE_CWD,
"213 0\r\n");
case PRE_CWD:
return Verify("CWD /file\r\n", data,
use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
"550 not a directory\r\n");
default:
return FtpSocketDataProviderFileDownload::OnWrite(data);
}
}
private:
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadZeroSize);
};
class FtpSocketDataProviderFileDownloadCWD451
: public FtpSocketDataProviderFileDownload {
public:
FtpSocketDataProviderFileDownloadCWD451() = default;
~FtpSocketDataProviderFileDownloadCWD451() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_CWD:
return Verify("CWD /file\r\n", data,
use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
"451 not a directory\r\n");
default:
return FtpSocketDataProviderFileDownload::OnWrite(data);
}
}
private:
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadCWD451);
};
class FtpSocketDataProviderVMSFileDownload : public FtpSocketDataProvider {
public:
FtpSocketDataProviderVMSFileDownload() = default;
~FtpSocketDataProviderVMSFileDownload() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_SYST:
return Verify("SYST\r\n", data, PRE_PWD, "215 VMS\r\n");
case PRE_PWD:
return Verify("PWD\r\n", data, PRE_TYPE,
"257 \"ANONYMOUS_ROOT:[000000]\"\r\n");
case PRE_LIST_EPSV:
return Verify("EPSV\r\n", data, PRE_LIST_PASV,
"500 EPSV command unknown\r\n");
case PRE_SIZE:
return Verify("SIZE ANONYMOUS_ROOT:[000000]file\r\n", data, PRE_CWD,
"213 18\r\n");
case PRE_CWD:
return Verify("CWD ANONYMOUS_ROOT:[file]\r\n", data,
use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
"550 Not a directory\r\n");
case PRE_RETR:
return Verify("RETR ANONYMOUS_ROOT:[000000]file\r\n", data, PRE_QUIT,
"200 OK\r\n");
default:
return FtpSocketDataProvider::OnWrite(data);
}
}
private:
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderVMSFileDownload);
};
class FtpSocketDataProviderFileDownloadInvalidResponse
: public FtpSocketDataProviderFileDownload {
public:
FtpSocketDataProviderFileDownloadInvalidResponse() = default;
~FtpSocketDataProviderFileDownloadInvalidResponse() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_SIZE:
// Use unallocated 599 FTP error code to make sure it falls into the
// generic ERR_FTP_FAILED bucket.
return Verify("SIZE /file\r\n", data, PRE_QUIT,
"599 Evil Response\r\n"
"599 More Evil\r\n");
default:
return FtpSocketDataProviderFileDownload::OnWrite(data);
}
}
private:
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadInvalidResponse);
};
class FtpSocketDataProviderEvilEpsv : public FtpSocketDataProviderFileDownload {
public:
FtpSocketDataProviderEvilEpsv(const char* epsv_response,
State expected_state)
: epsv_response_(epsv_response),
epsv_response_length_(std::strlen(epsv_response)),
expected_state_(expected_state) {}
FtpSocketDataProviderEvilEpsv(const char* epsv_response,
size_t epsv_response_length,
State expected_state)
: epsv_response_(epsv_response),
epsv_response_length_(epsv_response_length),
expected_state_(expected_state) {}
~FtpSocketDataProviderEvilEpsv() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_RETR_EPSV:
return Verify("EPSV\r\n", data, expected_state_,
epsv_response_, epsv_response_length_);
default:
return FtpSocketDataProviderFileDownload::OnWrite(data);
}
}
private:
const char* const epsv_response_;
const size_t epsv_response_length_;
const State expected_state_;
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilEpsv);
};
class FtpSocketDataProviderEvilPasv
: public FtpSocketDataProviderFileDownloadWithPasvFallback {
public:
FtpSocketDataProviderEvilPasv(const char* pasv_response, State expected_state)
: pasv_response_(pasv_response),
expected_state_(expected_state) {
}
~FtpSocketDataProviderEvilPasv() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_RETR_PASV:
return Verify("PASV\r\n", data, expected_state_, pasv_response_);
default:
return FtpSocketDataProviderFileDownloadWithPasvFallback::OnWrite(data);
}
}
private:
const char* const pasv_response_;
const State expected_state_;
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilPasv);
};
class FtpSocketDataProviderEvilSize : public FtpSocketDataProviderFileDownload {
public:
FtpSocketDataProviderEvilSize(const char* size_response, State expected_state)
: size_response_(size_response),
expected_state_(expected_state) {
}
~FtpSocketDataProviderEvilSize() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_SIZE:
return Verify("SIZE /file\r\n", data, expected_state_, size_response_);
default:
return FtpSocketDataProviderFileDownload::OnWrite(data);
}
}
private:
const char* const size_response_;
const State expected_state_;
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilSize);
};
class FtpSocketDataProviderEvilLogin
: public FtpSocketDataProviderFileDownload {
public:
FtpSocketDataProviderEvilLogin(const char* expected_user,
const char* expected_password)
: expected_user_(expected_user),
expected_password_(expected_password) {
}
~FtpSocketDataProviderEvilLogin() override = default;
MockWriteResult OnWrite(const std::string& data) override {
if (InjectFault())
return MockWriteResult(ASYNC, data.length());
switch (state()) {
case PRE_USER:
return Verify(std::string("USER ") + expected_user_ + "\r\n", data,
PRE_PASSWD, "331 Password needed\r\n");
case PRE_PASSWD:
return Verify(std::string("PASS ") + expected_password_ + "\r\n", data,
PRE_SYST, "230 Welcome\r\n");
default:
return FtpSocketDataProviderFileDownload::OnWrite(data);
}
}
private:
const char* const expected_user_;
const char* const expected_password_;
DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilLogin);
};
class FtpNetworkTransactionTest : public PlatformTest,
public ::testing::WithParamInterface<int>,
public WithTaskEnvironment {
public:
FtpNetworkTransactionTest() : host_resolver_(new MockHostResolver) {
SetUpTransaction();
scoped_refptr<RuleBasedHostResolverProc> rules(
new RuleBasedHostResolverProc(nullptr));
if (GetFamily() == AF_INET) {
rules->AddIPLiteralRule("*", "127.0.0.1", "127.0.0.1");
} else if (GetFamily() == AF_INET6) {
rules->AddIPLiteralRule("*", "::1", "::1");
} else {
NOTREACHED();
}
host_resolver_->set_rules(rules.get());
}
~FtpNetworkTransactionTest() override = default;
// Sets up an FtpNetworkTransaction and MocketClientSocketFactory, replacing
// the default one. Only needs to be called if a test runs multiple
// transactions.
void SetUpTransaction() {
mock_socket_factory_ = std::make_unique<MockClientSocketFactory>();
transaction_ = std::make_unique<FtpNetworkTransaction>(
host_resolver_.get(), mock_socket_factory_.get());
}
protected:
// Accessor to make code refactoring-friendly, e.g. when we change the way
// parameters are passed (like more parameters).
int GetFamily() {
return GetParam();
}
FtpRequestInfo GetRequestInfo(const std::string& url) {
FtpRequestInfo info;
info.url = GURL(url);
return info;
}
void ExecuteTransaction(FtpSocketDataProvider* ctrl_socket,
const char* request,
int expected_result) {
// Expect EPSV usage for non-IPv4 control connections.
ctrl_socket->set_use_epsv((GetFamily() != AF_INET));
mock_socket_factory_->AddSocketDataProvider(ctrl_socket);
std::string mock_data("mock-data");
MockRead data_reads[] = {
// Usually FTP servers close the data connection after the entire data has
// been received.
MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
MockRead(mock_data.c_str()),
};
std::unique_ptr<StaticSocketDataProvider> data_socket =
std::make_unique<StaticSocketDataProvider>(data_reads,
base::span<MockWrite>());
mock_socket_factory_->AddSocketDataProvider(data_socket.get());
FtpRequestInfo request_info = GetRequestInfo(request);
EXPECT_EQ(LOAD_STATE_IDLE, transaction_->GetLoadState());
ASSERT_EQ(
ERR_IO_PENDING,
transaction_->Start(&request_info, callback_.callback(),
NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS));
EXPECT_NE(LOAD_STATE_IDLE, transaction_->GetLoadState());
ASSERT_EQ(expected_result, callback_.WaitForResult());
if (expected_result == OK) {
scoped_refptr<IOBuffer> io_buffer =
base::MakeRefCounted<IOBuffer>(kBufferSize);
memset(io_buffer->data(), 0, kBufferSize);
ASSERT_EQ(ERR_IO_PENDING, transaction_->Read(io_buffer.get(), kBufferSize,
callback_.callback()));
ASSERT_EQ(static_cast<int>(mock_data.length()),
callback_.WaitForResult());
EXPECT_EQ(mock_data, std::string(io_buffer->data(), mock_data.length()));
// Do another Read to detect that the data socket is now closed.
int rv = transaction_->Read(io_buffer.get(), kBufferSize,
callback_.callback());
if (rv == ERR_IO_PENDING) {
EXPECT_EQ(0, callback_.WaitForResult());
} else {
EXPECT_EQ(0, rv);
}
}
EXPECT_EQ(FtpSocketDataProvider::QUIT, ctrl_socket->state());
EXPECT_EQ(LOAD_STATE_IDLE, transaction_->GetLoadState());
}
void TransactionFailHelper(FtpSocketDataProvider* ctrl_socket,
const char* request,
FtpSocketDataProvider::State state,
FtpSocketDataProvider::State next_state,
const char* response,
int expected_result) {
ctrl_socket->InjectFailure(state, next_state, response);
ExecuteTransaction(ctrl_socket, request, expected_result);
}
std::unique_ptr<MockHostResolver> host_resolver_;
std::unique_ptr<MockClientSocketFactory> mock_socket_factory_;
std::unique_ptr<FtpNetworkTransaction> transaction_;
TestCompletionCallback callback_;
};
TEST_P(FtpNetworkTransactionTest, FailedLookup) {
FtpRequestInfo request_info = GetRequestInfo("ftp://badhost");
scoped_refptr<RuleBasedHostResolverProc> rules(
new RuleBasedHostResolverProc(nullptr));
rules->AddSimulatedFailure("badhost");
host_resolver_->set_rules(rules.get());
EXPECT_EQ(LOAD_STATE_IDLE, transaction_->GetLoadState());
ASSERT_EQ(
ERR_IO_PENDING,
transaction_->Start(&request_info, callback_.callback(),
NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS));
ASSERT_THAT(callback_.WaitForResult(), IsError(ERR_NAME_NOT_RESOLVED));
EXPECT_EQ(LOAD_STATE_IDLE, transaction_->GetLoadState());
}
// Check that when determining the host, the square brackets decorating IPv6
// literals in URLs are stripped.
TEST_P(FtpNetworkTransactionTest, StripBracketsFromIPv6Literals) {
// This test only makes sense for IPv6 connections.
if (GetFamily() != AF_INET6)
return;
host_resolver_->rules()->AddSimulatedFailure("[::1]");
// We start a transaction that is expected to fail with ERR_INVALID_RESPONSE.
// The important part of this test is to make sure that we don't fail with
// ERR_NAME_NOT_RESOLVED, since that would mean the decorated hostname
// was used.
FtpSocketDataProviderEvilSize ctrl_socket(
"213 99999999999999999999999999999999\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://[::1]/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransaction) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
EXPECT_TRUE(transaction_->GetResponseInfo()->is_directory_listing);
EXPECT_EQ(-1, transaction_->GetResponseInfo()->expected_content_size);
EXPECT_EQ(
(GetFamily() == AF_INET) ? "127.0.0.1" : "::1",
transaction_->GetResponseInfo()->remote_endpoint.ToStringWithoutPort());
EXPECT_EQ(21, transaction_->GetResponseInfo()->remote_endpoint.port());
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionWithPasvFallback) {
FtpSocketDataProviderDirectoryListingWithPasvFallback ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
EXPECT_TRUE(transaction_->GetResponseInfo()->is_directory_listing);
EXPECT_EQ(-1, transaction_->GetResponseInfo()->expected_content_size);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionWithTypecode) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host/;type=d", OK);
EXPECT_TRUE(transaction_->GetResponseInfo()->is_directory_listing);
EXPECT_EQ(-1, transaction_->GetResponseInfo()->expected_content_size);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionMultilineWelcome) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
ctrl_socket.set_multiline_welcome(true);
ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionShortReads2) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
ctrl_socket.set_short_read_limit(2);
ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionShortReads5) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
ctrl_socket.set_short_read_limit(5);
ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionMultilineWelcomeShort) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
// The client will not consume all three 230 lines. That's good, we want to
// test that scenario.
ctrl_socket.set_allow_unconsumed_reads(true);
ctrl_socket.set_multiline_welcome(true);
ctrl_socket.set_short_read_limit(5);
ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
}
// Regression test for http://crbug.com/60555.
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionZeroSize) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_SIZE,
FtpSocketDataProvider::PRE_CWD, "213 0\r\n");
ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionVMS) {
FtpSocketDataProviderVMSDirectoryListing ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host/dir", OK);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionVMSRootDirectory) {
FtpSocketDataProviderVMSDirectoryListingRootDirectory ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionTransferStarting) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_LIST,
FtpSocketDataProvider::PRE_QUIT,
"125-Data connection already open.\r\n"
"125 Transfer starting.\r\n"
"226 Transfer complete.\r\n");
ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransaction) {
FtpSocketDataProviderFileDownload ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
// We pass an artificial value of 18 as a response to the SIZE command.
EXPECT_EQ(18, transaction_->GetResponseInfo()->expected_content_size);
EXPECT_EQ(
(GetFamily() == AF_INET) ? "127.0.0.1" : "::1",
transaction_->GetResponseInfo()->remote_endpoint.ToStringWithoutPort());
EXPECT_EQ(21, transaction_->GetResponseInfo()->remote_endpoint.port());
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionWithPasvFallback) {
FtpSocketDataProviderFileDownloadWithPasvFallback ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
// We pass an artificial value of 18 as a response to the SIZE command.
EXPECT_EQ(18, transaction_->GetResponseInfo()->expected_content_size);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionWithTypecodeA) {
FtpSocketDataProviderFileDownloadWithFileTypecode ctrl_socket;
ctrl_socket.set_data_type('A');
ExecuteTransaction(&ctrl_socket, "ftp://host/file;type=a", OK);
// We pass an artificial value of 18 as a response to the SIZE command.
EXPECT_EQ(18, transaction_->GetResponseInfo()->expected_content_size);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionWithTypecodeI) {
FtpSocketDataProviderFileDownloadWithFileTypecode ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host/file;type=i", OK);
// We pass an artificial value of 18 as a response to the SIZE command.
EXPECT_EQ(18, transaction_->GetResponseInfo()->expected_content_size);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionMultilineWelcome) {
FtpSocketDataProviderFileDownload ctrl_socket;
ctrl_socket.set_multiline_welcome(true);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionShortReads2) {
FtpSocketDataProviderFileDownload ctrl_socket;
ctrl_socket.set_short_read_limit(2);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionShortReads5) {
FtpSocketDataProviderFileDownload ctrl_socket;
ctrl_socket.set_short_read_limit(5);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionZeroSize) {
FtpSocketDataProviderFileDownloadZeroSize ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionCWD451) {
FtpSocketDataProviderFileDownloadCWD451 ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionVMS) {
FtpSocketDataProviderVMSFileDownload ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionTransferStarting) {
FtpSocketDataProviderFileDownload ctrl_socket;
ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_RETR,
FtpSocketDataProvider::PRE_QUIT,
"125-Data connection already open.\r\n"
"125 Transfer starting.\r\n"
"226 Transfer complete.\r\n");
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionInvalidResponse) {
FtpSocketDataProviderFileDownloadInvalidResponse ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvReallyBadFormat) {
FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafePort1) {
FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,1,0,22)\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafePort2) {
// Still unsafe. 1 * 256 + 2 = 258, which is < 1024.
FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,1,1,2)\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafePort3) {
// Still unsafe. 3 * 256 + 4 = 772, which is < 1024.
FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,1,3,4)\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafePort4) {
// Unsafe. 8 * 256 + 1 = 2049, which is used by nfs.
FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,1,8,1)\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvInvalidPort1) {
// Unsafe. 8 * 256 + 1 = 2049, which is used by nfs.
FtpSocketDataProviderEvilPasv ctrl_socket(
"227 Portscan (127,0,0,1,256,100)\r\n", FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvInvalidPort2) {
// Unsafe. 8 * 256 + 1 = 2049, which is used by nfs.
FtpSocketDataProviderEvilPasv ctrl_socket(
"227 Portscan (127,0,0,1,100,256)\r\n", FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvInvalidPort3) {
// Unsafe. 8 * 256 + 1 = 2049, which is used by nfs.
FtpSocketDataProviderEvilPasv ctrl_socket(
"227 Portscan (127,0,0,1,-100,100)\r\n", FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvInvalidPort4) {
// Unsafe. 8 * 256 + 1 = 2049, which is used by nfs.
FtpSocketDataProviderEvilPasv ctrl_socket(
"227 Portscan (127,0,0,1,100,-100)\r\n", FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafeHost) {
FtpSocketDataProviderEvilPasv ctrl_socket(
"227 Portscan (10,1,2,3,123,123)\r\n", FtpSocketDataProvider::PRE_RETR);
ctrl_socket.set_use_epsv(GetFamily() != AF_INET);
std::string mock_data("mock-data");
MockRead data_reads[] = {
MockRead(mock_data.c_str()),
};
StaticSocketDataProvider data_socket1;
StaticSocketDataProvider data_socket2(data_reads, base::span<MockWrite>());
mock_socket_factory_->AddSocketDataProvider(&ctrl_socket);
mock_socket_factory_->AddSocketDataProvider(&data_socket1);
mock_socket_factory_->AddSocketDataProvider(&data_socket2);
FtpRequestInfo request_info = GetRequestInfo("ftp://host/file");
// Start the transaction.
ASSERT_EQ(
ERR_IO_PENDING,
transaction_->Start(&request_info, callback_.callback(),
NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS));
ASSERT_THAT(callback_.WaitForResult(), IsOk());
// The transaction fires the callback when we can start reading data. That
// means that the data socket should be open.
MockTCPClientSocket* data_socket =
static_cast<MockTCPClientSocket*>(transaction_->data_socket_.get());
ASSERT_TRUE(data_socket);
ASSERT_TRUE(data_socket->IsConnected());
// Even if the PASV response specified some other address, we connect
// to the address we used for control connection (which could be 127.0.0.1
// or ::1 depending on whether we use IPv6).
for (auto it = data_socket->addresses().begin();
it != data_socket->addresses().end(); ++it) {
EXPECT_NE("10.1.2.3", it->ToStringWithoutPort());
}
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat1) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||22)\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat2) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (||\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat3) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat4) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (||||)\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat5) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
// Breaking the string in the next line prevents MSVC warning C4125.
const char response[] = "227 Portscan (\0\0\031" "773\0)\r\n";
FtpSocketDataProviderEvilEpsv ctrl_socket(response, sizeof(response)-1,
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort1) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||22|)\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort2) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||258|)\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort3) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||772|)\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort4) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||2049|)\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvInvalidPort) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||4294973296|)\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvWeirdSep) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan ($$$31744$)\r\n",
FtpSocketDataProvider::PRE_RETR);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
}
TEST_P(FtpNetworkTransactionTest,
DownloadTransactionEvilEpsvWeirdSepUnsafePort) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan ($$$317$)\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvIllegalHost) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|2|::1|31744|)\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilLoginBadUsername) {
FtpSocketDataProviderEvilLogin ctrl_socket("hello%0Aworld", "test");
ExecuteTransaction(&ctrl_socket, "ftp://hello%0Aworld:test@host/file", OK);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilLoginBadPassword) {
FtpSocketDataProviderEvilLogin ctrl_socket("test", "hello%0Dworld");
ExecuteTransaction(&ctrl_socket, "ftp://test:hello%0Dworld@host/file", OK);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionSpaceInLogin) {
FtpSocketDataProviderEvilLogin ctrl_socket("hello world", "test");
ExecuteTransaction(&ctrl_socket, "ftp://hello%20world:test@host/file", OK);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionSpaceInPassword) {
FtpSocketDataProviderEvilLogin ctrl_socket("test", "hello world");
ExecuteTransaction(&ctrl_socket, "ftp://test:hello%20world@host/file", OK);
}
TEST_P(FtpNetworkTransactionTest, FailOnInvalidUrls) {
const char* const kBadUrls[]{
// Make sure FtpNetworkTransaction doesn't request paths like
// "/foo/../bar". Doing so wouldn't be a security issue, client side, but
// just doesn't seem like a good idea.
"ftp://host/foo%2f..%2fbar%5c",
// LF
"ftp://host/foo%10.txt",
// CR
"ftp://host/foo%13.txt",
"ftp://host/foo%00.txt",
};
for (const char* bad_url : kBadUrls) {
SCOPED_TRACE(bad_url);
SetUpTransaction();
FtpRequestInfo request_info = GetRequestInfo(bad_url);
ASSERT_EQ(
ERR_INVALID_URL,
transaction_->Start(&request_info, callback_.callback(),
NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS));
}
}
TEST_P(FtpNetworkTransactionTest, EvilRestartUser) {
FtpSocketDataProvider ctrl_socket1;
ctrl_socket1.InjectFailure(FtpSocketDataProvider::PRE_PASSWD,
FtpSocketDataProvider::PRE_QUIT,
"530 Login authentication failed\r\n");
mock_socket_factory_->AddSocketDataProvider(&ctrl_socket1);
FtpRequestInfo request_info = GetRequestInfo("ftp://host/file");
ASSERT_EQ(
ERR_IO_PENDING,
transaction_->Start(&request_info, callback_.callback(),
NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS));
ASSERT_THAT(callback_.WaitForResult(), IsError(ERR_FTP_FAILED));
MockRead ctrl_reads[] = {
MockRead("220 host TestFTPd\r\n"),
MockRead("221 Goodbye!\r\n"),
MockRead(SYNCHRONOUS, OK),
};
MockWrite ctrl_writes[] = {
MockWrite("QUIT\r\n"),
};
StaticSocketDataProvider ctrl_socket2(ctrl_reads, ctrl_writes);
mock_socket_factory_->AddSocketDataProvider(&ctrl_socket2);
ASSERT_EQ(ERR_IO_PENDING,
transaction_->RestartWithAuth(
AuthCredentials(base::ASCIIToUTF16("foo\nownz0red"),
base::ASCIIToUTF16("innocent")),
callback_.callback()));
EXPECT_THAT(callback_.WaitForResult(), IsError(ERR_MALFORMED_IDENTITY));
}
TEST_P(FtpNetworkTransactionTest, EvilRestartPassword) {
FtpSocketDataProvider ctrl_socket1;
ctrl_socket1.InjectFailure(FtpSocketDataProvider::PRE_PASSWD,
FtpSocketDataProvider::PRE_QUIT,
"530 Login authentication failed\r\n");
mock_socket_factory_->AddSocketDataProvider(&ctrl_socket1);
FtpRequestInfo request_info = GetRequestInfo("ftp://host/file");
ASSERT_EQ(
ERR_IO_PENDING,
transaction_->Start(&request_info, callback_.callback(),
NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS));
ASSERT_THAT(callback_.WaitForResult(), IsError(ERR_FTP_FAILED));
MockRead ctrl_reads[] = {
MockRead("220 host TestFTPd\r\n"),
MockRead("331 User okay, send password\r\n"),
MockRead("221 Goodbye!\r\n"),
MockRead(SYNCHRONOUS, OK),
};
MockWrite ctrl_writes[] = {
MockWrite("USER innocent\r\n"),
MockWrite("QUIT\r\n"),
};
StaticSocketDataProvider ctrl_socket2(ctrl_reads, ctrl_writes);
mock_socket_factory_->AddSocketDataProvider(&ctrl_socket2);
ASSERT_EQ(ERR_IO_PENDING,
transaction_->RestartWithAuth(
AuthCredentials(base::ASCIIToUTF16("innocent"),
base::ASCIIToUTF16("foo\nownz0red")),
callback_.callback()));
EXPECT_THAT(callback_.WaitForResult(), IsError(ERR_MALFORMED_IDENTITY));
}
TEST_P(FtpNetworkTransactionTest, Escaping) {
const struct TestCase {
const char* url;
const char* expected_path;
} kTestCases[] = {
{"ftp://host/%20%21%22%23%24%25%79%80%81", "/ !\"#$%y\200\201"},
// This is no allowed to be unescaped by UnescapeURLComponent, since it's
// a lock icon. But it has no special meaning or security concern in the
// context of making FTP requests.
{"ftp://host/%F0%9F%94%92", "/\xF0\x9F\x94\x92"},
// Invalid UTF-8 character, which again has no special meaning over FTP.
{"ftp://host/%81", "/\x81"},
};
for (const auto& test_case : kTestCases) {
SCOPED_TRACE(test_case.url);
SetUpTransaction();
FtpSocketDataProviderFileDownload ctrl_socket;
ctrl_socket.set_file_path(test_case.expected_path);
ExecuteTransaction(&ctrl_socket, test_case.url, OK);
}
}
// Test for http://crbug.com/23794.
TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilSize) {
// Try to overflow int64_t in the response.
FtpSocketDataProviderEvilSize ctrl_socket(
"213 99999999999999999999999999999999\r\n",
FtpSocketDataProvider::PRE_QUIT);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
// Test for http://crbug.com/36360.
TEST_P(FtpNetworkTransactionTest, DownloadTransactionBigSize) {
// Pass a valid, but large file size. The transaction should not fail.
FtpSocketDataProviderEvilSize ctrl_socket(
"213 3204427776\r\n",
FtpSocketDataProvider::PRE_CWD);
ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
EXPECT_EQ(3204427776LL,
transaction_->GetResponseInfo()->expected_content_size);
}
// Regression test for http://crbug.com/25023.
TEST_P(FtpNetworkTransactionTest, CloseConnection) {
FtpSocketDataProvider ctrl_socket;
ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_USER,
FtpSocketDataProvider::PRE_QUIT, "");
ExecuteTransaction(&ctrl_socket, "ftp://host", ERR_EMPTY_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailUser) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
// Use unallocated 599 FTP error code to make sure it falls into the generic
// ERR_FTP_FAILED bucket.
TransactionFailHelper(&ctrl_socket,
"ftp://host",
FtpSocketDataProvider::PRE_USER,
FtpSocketDataProvider::PRE_QUIT,
"599 fail\r\n",
ERR_FTP_FAILED);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailPass) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
TransactionFailHelper(&ctrl_socket,
"ftp://host",
FtpSocketDataProvider::PRE_PASSWD,
FtpSocketDataProvider::PRE_QUIT,
"530 Login authentication failed\r\n",
ERR_FTP_FAILED);
}
// Regression test for http://crbug.com/38707.
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailPass503) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
TransactionFailHelper(&ctrl_socket,
"ftp://host",
FtpSocketDataProvider::PRE_PASSWD,
FtpSocketDataProvider::PRE_QUIT,
"503 Bad sequence of commands\r\n",
ERR_FTP_BAD_COMMAND_SEQUENCE);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailSyst) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
// Use unallocated 599 FTP error code to make sure it falls into the generic
// ERR_FTP_FAILED bucket.
TransactionFailHelper(&ctrl_socket,
"ftp://host",
FtpSocketDataProvider::PRE_SYST,
FtpSocketDataProvider::PRE_PWD,
"599 fail\r\n",
OK);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailPwd) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
// Use unallocated 599 FTP error code to make sure it falls into the generic
// ERR_FTP_FAILED bucket.
TransactionFailHelper(&ctrl_socket,
"ftp://host",
FtpSocketDataProvider::PRE_PWD,
FtpSocketDataProvider::PRE_QUIT,
"599 fail\r\n",
ERR_FTP_FAILED);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailType) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
// Use unallocated 599 FTP error code to make sure it falls into the generic
// ERR_FTP_FAILED bucket.
TransactionFailHelper(&ctrl_socket,
"ftp://host",
FtpSocketDataProvider::PRE_TYPE,
FtpSocketDataProvider::PRE_QUIT,
"599 fail\r\n",
ERR_FTP_FAILED);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailEpsv) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderDirectoryListing ctrl_socket;
// Use unallocated 599 FTP error code to make sure it falls into the generic
// ERR_FTP_FAILED bucket.
TransactionFailHelper(
&ctrl_socket, "ftp://host", FtpSocketDataProvider::PRE_LIST_EPSV,
FtpSocketDataProvider::PRE_NOPASV, "599 fail\r\n", ERR_FTP_FAILED);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailCwd) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
// Use unallocated 599 FTP error code to make sure it falls into the generic
// ERR_FTP_FAILED bucket.
TransactionFailHelper(&ctrl_socket,
"ftp://host",
FtpSocketDataProvider::PRE_CWD,
FtpSocketDataProvider::PRE_QUIT,
"599 fail\r\n",
ERR_FTP_FAILED);
}
TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailList) {
FtpSocketDataProviderVMSDirectoryListing ctrl_socket;
// Use unallocated 599 FTP error code to make sure it falls into the generic
// ERR_FTP_FAILED bucket.
TransactionFailHelper(&ctrl_socket,
"ftp://host/dir",
FtpSocketDataProvider::PRE_LIST,
FtpSocketDataProvider::PRE_QUIT,
"599 fail\r\n",
ERR_FTP_FAILED);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailUser) {
FtpSocketDataProviderFileDownload ctrl_socket;
// Use unallocated 599 FTP error code to make sure it falls into the generic
// ERR_FTP_FAILED bucket.
TransactionFailHelper(&ctrl_socket,
"ftp://host/file",
FtpSocketDataProvider::PRE_USER,
FtpSocketDataProvider::PRE_QUIT,
"599 fail\r\n",
ERR_FTP_FAILED);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailPass) {
FtpSocketDataProviderFileDownload ctrl_socket;
TransactionFailHelper(&ctrl_socket,
"ftp://host/file",
FtpSocketDataProvider::PRE_PASSWD,
FtpSocketDataProvider::PRE_QUIT,
"530 Login authentication failed\r\n",
ERR_FTP_FAILED);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailSyst) {
FtpSocketDataProviderFileDownload ctrl_socket;
// Use unallocated 599 FTP error code to make sure it falls into the generic
// ERR_FTP_FAILED bucket.
TransactionFailHelper(&ctrl_socket,
"ftp://host/file",
FtpSocketDataProvider::PRE_SYST,
FtpSocketDataProvider::PRE_PWD,
"599 fail\r\n",
OK);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailPwd) {
FtpSocketDataProviderFileDownload ctrl_socket;
// Use unallocated 599 FTP error code to make sure it falls into the generic
// ERR_FTP_FAILED bucket.
TransactionFailHelper(&ctrl_socket,
"ftp://host/file",
FtpSocketDataProvider::PRE_PWD,
FtpSocketDataProvider::PRE_QUIT,
"599 fail\r\n",
ERR_FTP_FAILED);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailType) {
FtpSocketDataProviderFileDownload ctrl_socket;
// Use unallocated 599 FTP error code to make sure it falls into the generic
// ERR_FTP_FAILED bucket.
TransactionFailHelper(&ctrl_socket,
"ftp://host/file",
FtpSocketDataProvider::PRE_TYPE,
FtpSocketDataProvider::PRE_QUIT,
"599 fail\r\n",
ERR_FTP_FAILED);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailEpsv) {
// This test makes no sense for IPv4 connections (we don't use EPSV there).
if (GetFamily() == AF_INET)
return;
FtpSocketDataProviderFileDownload ctrl_socket;
// Use unallocated 599 FTP error code to make sure it falls into the generic
// ERR_FTP_FAILED bucket.
TransactionFailHelper(
&ctrl_socket, "ftp://host/file", FtpSocketDataProvider::PRE_RETR_EPSV,
FtpSocketDataProvider::PRE_NOPASV, "599 fail\r\n", ERR_FTP_FAILED);
}
TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailRetr) {
FtpSocketDataProviderFileDownload ctrl_socket;
// Use unallocated 599 FTP error code to make sure it falls into the generic
// ERR_FTP_FAILED bucket.
TransactionFailHelper(&ctrl_socket,
"ftp://host/file",
FtpSocketDataProvider::PRE_RETR,
FtpSocketDataProvider::PRE_QUIT,
"599 fail\r\n",
ERR_FTP_FAILED);
}
TEST_P(FtpNetworkTransactionTest, FileNotFound) {
FtpSocketDataProviderFileNotFound ctrl_socket;
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_FTP_FAILED);
}
// Test for http://crbug.com/38845.
TEST_P(FtpNetworkTransactionTest, ZeroLengthDirInPWD) {
FtpSocketDataProviderFileDownload ctrl_socket;
TransactionFailHelper(&ctrl_socket,
"ftp://host/file",
FtpSocketDataProvider::PRE_PWD,
FtpSocketDataProvider::PRE_TYPE,
"257 \"\"\r\n",
OK);
}
TEST_P(FtpNetworkTransactionTest, UnexpectedInitiatedResponseForDirectory) {
// The states for a directory listing where an initiated response will cause
// an error. Includes all commands sent on the directory listing path, except
// CWD, SIZE, LIST, and QUIT commands.
FtpSocketDataProvider::State kFailingStates[] = {
FtpSocketDataProvider::PRE_USER, FtpSocketDataProvider::PRE_PASSWD,
FtpSocketDataProvider::PRE_SYST, FtpSocketDataProvider::PRE_PWD,
FtpSocketDataProvider::PRE_TYPE,
GetFamily() != AF_INET ? FtpSocketDataProvider::PRE_LIST_EPSV
: FtpSocketDataProvider::PRE_LIST_PASV,
FtpSocketDataProvider::PRE_CWD,
};
for (FtpSocketDataProvider::State state : kFailingStates) {
SetUpTransaction();
FtpSocketDataProviderDirectoryListing ctrl_socket;
ctrl_socket.InjectFailure(state, FtpSocketDataProvider::PRE_QUIT,
"157 Foo\r\n");
ExecuteTransaction(&ctrl_socket, "ftp://host/", ERR_INVALID_RESPONSE);
}
}
TEST_P(FtpNetworkTransactionTest, UnexpectedInitiatedResponseForFile) {
// The states for a download where an initiated response will cause an error.
// Includes all commands sent on the file download path, except CWD, SIZE, and
// QUIT commands.
const FtpSocketDataProvider::State kFailingStates[] = {
FtpSocketDataProvider::PRE_USER, FtpSocketDataProvider::PRE_PASSWD,
FtpSocketDataProvider::PRE_SYST, FtpSocketDataProvider::PRE_PWD,
FtpSocketDataProvider::PRE_TYPE,
GetFamily() != AF_INET ? FtpSocketDataProvider::PRE_RETR_EPSV
: FtpSocketDataProvider::PRE_RETR_PASV,
FtpSocketDataProvider::PRE_CWD};
for (FtpSocketDataProvider::State state : kFailingStates) {
LOG(ERROR) << "??: " << state;
SetUpTransaction();
FtpSocketDataProviderFileDownload ctrl_socket;
ctrl_socket.InjectFailure(state, FtpSocketDataProvider::PRE_QUIT,
"157 Foo\r\n");
ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
}
}
// Make sure that receiving extra unexpected responses correctly results in
// sending a QUIT message, without triggering a DCHECK.
TEST_P(FtpNetworkTransactionTest, ExtraResponses) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_TYPE,
FtpSocketDataProvider::PRE_QUIT,
"157 Foo\r\n"
"157 Bar\r\n"
"157 Trombones\r\n");
ExecuteTransaction(&ctrl_socket, "ftp://host/", ERR_INVALID_RESPONSE);
}
// Make sure that receiving extra unexpected responses to a QUIT message
// correctly results in ending the transaction with an error, without triggering
// a DCHECK.
TEST_P(FtpNetworkTransactionTest, ExtraQuitResponses) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_QUIT,
FtpSocketDataProvider::QUIT,
"221 Foo\r\n"
"221 Bar\r\n"
"221 Trombones\r\n");
ExecuteTransaction(&ctrl_socket, "ftp://host/", ERR_INVALID_RESPONSE);
}
// Test case for https://crbug.com/633841 - similar to the ExtraQuitResponses
// test case, but with an empty response.
TEST_P(FtpNetworkTransactionTest, EmptyQuitResponse) {
FtpSocketDataProviderDirectoryListing ctrl_socket;
ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_QUIT,
FtpSocketDataProvider::QUIT, "");
ExecuteTransaction(&ctrl_socket, "ftp://host/", OK);
}
TEST_P(FtpNetworkTransactionTest, InvalidRemoteDirectory) {
FtpSocketDataProviderFileDownload ctrl_socket;
TransactionFailHelper(
&ctrl_socket, "ftp://host/file", FtpSocketDataProvider::PRE_PWD,
FtpSocketDataProvider::PRE_QUIT,
"257 \"foo\rbar\" is your current location\r\n", ERR_INVALID_RESPONSE);
}
TEST_P(FtpNetworkTransactionTest, InvalidRemoteDirectory2) {
FtpSocketDataProviderFileDownload ctrl_socket;
TransactionFailHelper(
&ctrl_socket, "ftp://host/file", FtpSocketDataProvider::PRE_PWD,
FtpSocketDataProvider::PRE_QUIT,
"257 \"foo\nbar\" is your current location\r\n", ERR_INVALID_RESPONSE);
}
INSTANTIATE_TEST_SUITE_P(Ftp,
FtpNetworkTransactionTest,
::testing::Values(AF_INET, AF_INET6));
} // namespace net