diff options
author | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-28 20:30:09 +0000 |
---|---|---|
committer | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-28 20:30:09 +0000 |
commit | c2972193bd0d0c2354d0862444b7ce4662d52d77 (patch) | |
tree | a6cbb8ce07493d2dd6aa9062f44a741e5082739a | |
parent | 1b2bb88355feef67a2b67968f0c4a91d16799e38 (diff) | |
download | chromium_src-c2972193bd0d0c2354d0862444b7ce4662d52d77.zip chromium_src-c2972193bd0d0c2354d0862444b7ce4662d52d77.tar.gz chromium_src-c2972193bd0d0c2354d0862444b7ce4662d52d77.tar.bz2 |
Rework FTP control response parsing code and fix socket Write misuse.
It also fixes other minor issues in the code, and makes ftp.vim.org work!
TEST=Visit ftp.vim.org on Linux with Chromium compiled in Debug mode. You should see directory listing after a short while.
BUG=http://crbug.com/15792
Review URL: http://codereview.chromium.org/149368
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@21881 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/ftp/ftp_ctrl_response_buffer.cc | 107 | ||||
-rw-r--r-- | net/ftp/ftp_ctrl_response_buffer.h | 101 | ||||
-rw-r--r-- | net/ftp/ftp_ctrl_response_buffer_unittest.cc | 152 | ||||
-rw-r--r-- | net/ftp/ftp_network_transaction.cc | 297 | ||||
-rw-r--r-- | net/ftp/ftp_network_transaction.h | 69 | ||||
-rw-r--r-- | net/ftp/ftp_network_transaction_unittest.cc | 12 | ||||
-rw-r--r-- | net/net.gyp | 3 |
7 files changed, 524 insertions, 217 deletions
diff --git a/net/ftp/ftp_ctrl_response_buffer.cc b/net/ftp/ftp_ctrl_response_buffer.cc new file mode 100644 index 0000000..b60ae81 --- /dev/null +++ b/net/ftp/ftp_ctrl_response_buffer.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2009 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_ctrl_response_buffer.h" + +#include "base/logging.h" +#include "base/string_util.h" +#include "net/base/net_errors.h" + +namespace net { + +// static +const int FtpCtrlResponse::kInvalidStatusCode = -1; + +int FtpCtrlResponseBuffer::ConsumeData(const char* data, int data_length) { + buffer_.append(std::string(data, data_length)); + ExtractFullLinesFromBuffer(); + + while (!lines_.empty()) { + ParsedLine line = lines_.front(); + lines_.pop(); + + if (line_buf_.empty()) { + if (!line.is_complete) + return ERR_INVALID_RESPONSE; + + if (line.is_multiline) { + line_buf_ = line.status_text; + response_buf_.status_code = line.status_code; + } else { + response_buf_.status_code = line.status_code; + response_buf_.lines.push_back(line.status_text); + responses_.push(response_buf_); + + // Prepare to handle following lines. + response_buf_ = FtpCtrlResponse(); + line_buf_.clear(); + } + } else { + if (!line.is_complete || line.status_code != response_buf_.status_code) { + line_buf_.append(line.raw_text); + continue; + } + + response_buf_.lines.push_back(line_buf_); + + line_buf_ = line.status_text; + DCHECK_EQ(line.status_code, response_buf_.status_code); + + if (!line.is_multiline) { + response_buf_.lines.push_back(line_buf_); + responses_.push(response_buf_); + + // Prepare to handle following lines. + response_buf_ = FtpCtrlResponse(); + line_buf_.clear(); + } + } + } + + return OK; +} + +// static +FtpCtrlResponseBuffer::ParsedLine FtpCtrlResponseBuffer::ParseLine( + const std::string& line) { + ParsedLine result; + + if (line.length() >= 3) { + if (StringToInt(line.substr(0, 3), &result.status_code)) + result.has_status_code = (100 <= result.status_code && + result.status_code <= 599); + if (result.has_status_code && line.length() >= 4 && line[3] == ' ') { + result.is_complete = true; + } else if (result.has_status_code && line.length() >= 4 && line[3] == '-') { + result.is_complete = true; + result.is_multiline = true; + } + } + + if (result.is_complete) { + result.status_text = line.substr(4); + } else { + result.status_text = line; + } + + result.raw_text = line; + + return result; +} + +void FtpCtrlResponseBuffer::ExtractFullLinesFromBuffer() { + std::string line_buf; + int cut_pos = 0; + for (size_t i = 0; i < buffer_.length(); i++) { + line_buf.push_back(buffer_[i]); + if (i >= 1 && buffer_[i - 1] == '\r' && buffer_[i] == '\n') { + lines_.push(ParseLine(line_buf.substr(0, line_buf.length() - 2))); + cut_pos = i + 1; + line_buf.clear(); + } + } + buffer_.erase(0, cut_pos); +} + +} // namespace net diff --git a/net/ftp/ftp_ctrl_response_buffer.h b/net/ftp/ftp_ctrl_response_buffer.h new file mode 100644 index 0000000..33f04b9 --- /dev/null +++ b/net/ftp/ftp_ctrl_response_buffer.h @@ -0,0 +1,101 @@ +// Copyright (c) 2009 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. + +#ifndef NET_FTP_FTP_CTRL_RESPONSE_BUFFER_H_ +#define NET_FTP_FTP_CTRL_RESPONSE_BUFFER_H_ + +#include <queue> +#include <string> +#include <vector> + +#include "base/basictypes.h" + +namespace net { + +struct FtpCtrlResponse { + static const int kInvalidStatusCode; + + FtpCtrlResponse() : status_code(kInvalidStatusCode) { + } + + int status_code; // Three-digit status code. + std::vector<std::string> lines; // Response lines, without CRLFs. +}; + +class FtpCtrlResponseBuffer { + public: + FtpCtrlResponseBuffer() {} + + // Called when data is received from the control socket. Returns error code. + int ConsumeData(const char* data, int data_length); + + bool ResponseAvailable() const { + return !responses_.empty(); + } + + // Returns the next response. It is an error to call this function + // unless ResponseAvailable returns true. + FtpCtrlResponse PopResponse() { + FtpCtrlResponse result = responses_.front(); + responses_.pop(); + return result; + } + + private: + struct ParsedLine { + ParsedLine() + : has_status_code(false), + is_multiline(false), + is_complete(false), + status_code(FtpCtrlResponse::kInvalidStatusCode) { + } + + // Indicates that this line begins with a valid 3-digit status code. + bool has_status_code; + + // Indicates that this line has the dash (-) after the code, which + // means a multiline response. + bool is_multiline; + + // Indicates that this line could be parsed as a complete and valid + // response line, without taking into account preceding lines (which + // may change its meaning into a continuation of the previous line). + bool is_complete; + + // Part of response parsed as status code. + int status_code; + + // Part of response parsed as status text. + std::string status_text; + + // Text before parsing, without ending CRLF. + std::string raw_text; + }; + + static ParsedLine ParseLine(const std::string& line); + + void ExtractFullLinesFromBuffer(); + + // We keep not-yet-parsed data in a string buffer. + std::string buffer_; + + std::queue<ParsedLine> lines_; + + // When parsing a multiline response, we don't know beforehand if a line + // will have a continuation. So always store last line of multiline response + // so we can append the continuation to it. + std::string line_buf_; + + // Keep the response data while we add all lines to it. + FtpCtrlResponse response_buf_; + + // As we read full responses (possibly multiline), we add them to the queue. + std::queue<FtpCtrlResponse> responses_; + + DISALLOW_COPY_AND_ASSIGN(FtpCtrlResponseBuffer); +}; + +} // namespace net + +#endif // NET_FTP_FTP_CTRL_RESPONSE_BUFFER_H_ diff --git a/net/ftp/ftp_ctrl_response_buffer_unittest.cc b/net/ftp/ftp_ctrl_response_buffer_unittest.cc new file mode 100644 index 0000000..c2aeee2 --- /dev/null +++ b/net/ftp/ftp_ctrl_response_buffer_unittest.cc @@ -0,0 +1,152 @@ +// Copyright (c) 2009 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_ctrl_response_buffer.h" + +#include <string.h> + +#include "net/base/net_errors.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class FtpCtrlResponseBufferTest : public testing::Test { + protected: + int PushDataToBuffer(const char* data) { + return buffer_.ConsumeData(data, strlen(data)); + } + + net::FtpCtrlResponseBuffer buffer_; +}; + +TEST_F(FtpCtrlResponseBufferTest, Basic) { + EXPECT_FALSE(buffer_.ResponseAvailable()); + + EXPECT_EQ(net::OK, PushDataToBuffer("200 Status Text\r\n")); + EXPECT_TRUE(buffer_.ResponseAvailable()); + + net::FtpCtrlResponse response = buffer_.PopResponse(); + EXPECT_FALSE(buffer_.ResponseAvailable()); + EXPECT_EQ(200, response.status_code); + ASSERT_EQ(1U, response.lines.size()); + EXPECT_EQ("Status Text", response.lines[0]); +} + +TEST_F(FtpCtrlResponseBufferTest, Chunks) { + EXPECT_EQ(net::OK, PushDataToBuffer("20")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + EXPECT_EQ(net::OK, PushDataToBuffer("0 Status")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + EXPECT_EQ(net::OK, PushDataToBuffer(" Text")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + EXPECT_EQ(net::OK, PushDataToBuffer("\r")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + EXPECT_EQ(net::OK, PushDataToBuffer("\n")); + EXPECT_TRUE(buffer_.ResponseAvailable()); + + net::FtpCtrlResponse response = buffer_.PopResponse(); + EXPECT_FALSE(buffer_.ResponseAvailable()); + EXPECT_EQ(200, response.status_code); + ASSERT_EQ(1U, response.lines.size()); + EXPECT_EQ("Status Text", response.lines[0]); +} + +TEST_F(FtpCtrlResponseBufferTest, Continuation) { + EXPECT_EQ(net::OK, PushDataToBuffer("230-FirstLine\r\n")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + + EXPECT_EQ(net::OK, PushDataToBuffer("230-SecondLine\r\n")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + + EXPECT_EQ(net::OK, PushDataToBuffer("230 LastLine\r\n")); + EXPECT_TRUE(buffer_.ResponseAvailable()); + + net::FtpCtrlResponse response = buffer_.PopResponse(); + EXPECT_FALSE(buffer_.ResponseAvailable()); + EXPECT_EQ(230, response.status_code); + ASSERT_EQ(3U, response.lines.size()); + EXPECT_EQ("FirstLine", response.lines[0]); + EXPECT_EQ("SecondLine", response.lines[1]); + EXPECT_EQ("LastLine", response.lines[2]); +} + +TEST_F(FtpCtrlResponseBufferTest, MultilineContinuation) { + EXPECT_EQ(net::OK, PushDataToBuffer("230-FirstLine\r\n")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + + EXPECT_EQ(net::OK, PushDataToBuffer("Continued\r\n")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + + EXPECT_EQ(net::OK, PushDataToBuffer("230-SecondLine\r\n")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + + EXPECT_EQ(net::OK, PushDataToBuffer("215 Continued\r\n")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + + EXPECT_EQ(net::OK, PushDataToBuffer("230 LastLine\r\n")); + EXPECT_TRUE(buffer_.ResponseAvailable()); + + net::FtpCtrlResponse response = buffer_.PopResponse(); + EXPECT_FALSE(buffer_.ResponseAvailable()); + EXPECT_EQ(230, response.status_code); + ASSERT_EQ(3U, response.lines.size()); + EXPECT_EQ("FirstLineContinued", response.lines[0]); + EXPECT_EQ("SecondLine215 Continued", response.lines[1]); + EXPECT_EQ("LastLine", response.lines[2]); +} + +TEST_F(FtpCtrlResponseBufferTest, SimilarContinuation) { + EXPECT_EQ(net::OK, PushDataToBuffer("230-FirstLine\r\n")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + + // Notice the space at the start of the line. It should be recognized + // as a continuation, and not the last line. + EXPECT_EQ(net::OK, PushDataToBuffer(" 230 Continued\r\n")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + + EXPECT_EQ(net::OK, PushDataToBuffer("230 TrueLastLine\r\n")); + EXPECT_TRUE(buffer_.ResponseAvailable()); + + net::FtpCtrlResponse response = buffer_.PopResponse(); + EXPECT_FALSE(buffer_.ResponseAvailable()); + EXPECT_EQ(230, response.status_code); + ASSERT_EQ(2U, response.lines.size()); + EXPECT_EQ("FirstLine 230 Continued", response.lines[0]); + EXPECT_EQ("TrueLastLine", response.lines[1]); +} + +// The nesting of multi-line responses is not allowed. +TEST_F(FtpCtrlResponseBufferTest, NoNesting) { + EXPECT_EQ(net::OK, PushDataToBuffer("230-FirstLine\r\n")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + + EXPECT_EQ(net::OK, PushDataToBuffer("300-Continuation\r\n")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + + EXPECT_EQ(net::OK, PushDataToBuffer("300 Still continuation\r\n")); + EXPECT_FALSE(buffer_.ResponseAvailable()); + + EXPECT_EQ(net::OK, PushDataToBuffer("230 Real End\r\n")); + ASSERT_TRUE(buffer_.ResponseAvailable()); + + net::FtpCtrlResponse response = buffer_.PopResponse(); + EXPECT_FALSE(buffer_.ResponseAvailable()); + EXPECT_EQ(230, response.status_code); + ASSERT_EQ(2U, response.lines.size()); + EXPECT_EQ("FirstLine300-Continuation300 Still continuation", + response.lines[0]); + EXPECT_EQ("Real End", response.lines[1]); +} + +TEST_F(FtpCtrlResponseBufferTest, NonNumericResponse) { + EXPECT_EQ(net::ERR_INVALID_RESPONSE, PushDataToBuffer("Non-numeric\r\n")); + EXPECT_FALSE(buffer_.ResponseAvailable()); +} + +TEST_F(FtpCtrlResponseBufferTest, OutOfRangeResponse) { + EXPECT_EQ(net::ERR_INVALID_RESPONSE, PushDataToBuffer("777 OK?\r\n")); + EXPECT_FALSE(buffer_.ResponseAvailable()); +} + +} // namespace diff --git a/net/ftp/ftp_network_transaction.cc b/net/ftp/ftp_network_transaction.cc index 980e0ab..e783590 100644 --- a/net/ftp/ftp_network_transaction.cc +++ b/net/ftp/ftp_network_transaction.cc @@ -34,10 +34,7 @@ FtpNetworkTransaction::FtpNetworkTransaction( session_(session), request_(NULL), resolver_(session->host_resolver()), - response_message_buf_(new IOBufferWithSize(kCtrlBufLen)), - response_message_buf_len_(0), - read_ctrl_buf_(new ReusedIOBuffer(response_message_buf_, - response_message_buf_->size())), + read_ctrl_buf_(new IOBuffer(kCtrlBufLen)), read_data_buf_len_(0), file_data_len_(0), last_error_(OK), @@ -119,163 +116,80 @@ uint64 FtpNetworkTransaction::GetUploadProgress() const { return 0; } -int FtpNetworkTransaction::ParseCtrlResponse(int* cut_pos) { - enum { - CODE, // Three-digit status code. - TEXT, // The text after code, not including the space after code. - ENDLINE, // We expect a CRLF at end of each line. - } scan_state = CODE; - - *cut_pos = 0; // Index of first unparsed character. - - // Store parsed code and text for current line. - int status_code = 0; - std::string status_text; - - const char* data = response_message_buf_->data(); - for (int i = 0; i < response_message_buf_len_; i++) { - switch (scan_state) { - case CODE: - if (data[i] == ' ' || data[i] == '\t') { - if (status_code < 100 || status_code > 599) - return ERR_INVALID_RESPONSE; - scan_state = TEXT; - break; - } - if (data[i] < '0' || data[i] > '9') - return ERR_INVALID_RESPONSE; - status_code = 10 * status_code + (data[i] - '0'); - break; - case TEXT: - if (data[i] == '\r') { - scan_state = ENDLINE; - break; - } - status_text.push_back(data[i]); - break; - case ENDLINE: - if (data[i] != '\n') - return ERR_INVALID_RESPONSE; - ctrl_responses_.push(ResponseLine(status_code, status_text)); - *cut_pos = i + 1; - - // Prepare to handle the next line. - scan_state = CODE; - status_code = 0; - status_text = ""; - break; - default: - NOTREACHED(); - return ERR_UNEXPECTED; - } - } - - return OK; -} - // Used to prepare and send FTP commad. int FtpNetworkTransaction::SendFtpCommand(const std::string& command, Command cmd) { - command_sent_ = cmd; - DLOG(INFO) << " >> " << command; - const char* buf = command.c_str(); - int buf_len = command.size(); + DCHECK(!write_command_buf_); DCHECK(!write_buf_); - write_buf_ = new IOBuffer(buf_len + 2); - memcpy(write_buf_->data(), buf, buf_len); - memcpy(write_buf_->data() + buf_len, kCRLF, 2); - buf_len += 2; + DLOG(INFO) << " >> " << command; - return ctrl_socket_->Write(write_buf_, buf_len, &io_callback_); + command_sent_ = cmd; + write_buf_written_ = 0; + write_command_buf_ = new IOBufferWithSize(command.length() + 2); + write_buf_ = new ReusedIOBuffer(write_command_buf_, + write_command_buf_->size()); + memcpy(write_command_buf_->data(), command.data(), command.length()); + memcpy(write_command_buf_->data() + command.length(), kCRLF, 2); + + next_state_ = STATE_CTRL_WRITE_COMMAND; + return OK; } -int FtpNetworkTransaction::ProcessCtrlResponses() { - int rv = OK; - if (command_sent_ == COMMAND_NONE) { - while (!ctrl_responses_.empty()) { - ResponseLine line = ctrl_responses_.front(); - ctrl_responses_.pop(); - if (GetErrorClass(line.code) != ERROR_CLASS_OK) - return Stop(ERR_FAILED); - } - next_state_ = STATE_CTRL_WRITE_USER; - return rv; - } - - // Eat multiple 230 lines after PASS. - if (command_sent_ == COMMAND_PASS) { - while (ctrl_responses_.size() > 1) { - if (ctrl_responses_.front().code != 230) - break; - ctrl_responses_.pop(); - } - } - - // Make sure there are no 230's when we want to process SYST response. - if (command_sent_ == COMMAND_SYST) { - while (!ctrl_responses_.empty()) { - if (ctrl_responses_.front().code != 230) - break; - ctrl_responses_.pop(); - } - if (ctrl_responses_.empty()) { - // Read more from control socket. - next_state_ = STATE_CTRL_READ; - return rv; - } - } - - if (ctrl_responses_.size() != 1) - return Stop(ERR_INVALID_RESPONSE); +int FtpNetworkTransaction::ProcessCtrlResponse() { + FtpCtrlResponse response = ctrl_response_buffer_.PopResponse(); - ResponseLine response_line = ctrl_responses_.front(); - ctrl_responses_.pop(); + // We always expect only one response, even if it's multiline. + DCHECK(!ctrl_response_buffer_.ResponseAvailable()); + int rv = OK; switch (command_sent_) { + case COMMAND_NONE: + // TODO(phajdan.jr): Check for errors in the welcome message. + next_state_ = STATE_CTRL_WRITE_USER; + break; case COMMAND_USER: - rv = ProcessResponseUSER(response_line); + rv = ProcessResponseUSER(response); break; case COMMAND_PASS: - rv = ProcessResponsePASS(response_line); + rv = ProcessResponsePASS(response); break; case COMMAND_ACCT: - rv = ProcessResponseACCT(response_line); + rv = ProcessResponseACCT(response); break; case COMMAND_SYST: - rv = ProcessResponseSYST(response_line); + rv = ProcessResponseSYST(response); break; case COMMAND_PWD: - rv = ProcessResponsePWD(response_line); + rv = ProcessResponsePWD(response); break; case COMMAND_TYPE: - rv = ProcessResponseTYPE(response_line); + rv = ProcessResponseTYPE(response); break; case COMMAND_PASV: - rv = ProcessResponsePASV(response_line); + rv = ProcessResponsePASV(response); break; case COMMAND_SIZE: - rv = ProcessResponseSIZE(response_line); + rv = ProcessResponseSIZE(response); break; case COMMAND_RETR: - rv = ProcessResponseRETR(response_line); + rv = ProcessResponseRETR(response); break; case COMMAND_CWD: - rv = ProcessResponseCWD(response_line); + rv = ProcessResponseCWD(response); break; case COMMAND_LIST: - rv = ProcessResponseLIST(response_line); + rv = ProcessResponseLIST(response); break; case COMMAND_MDTM: - rv = ProcessResponseMDTM(response_line); + rv = ProcessResponseMDTM(response); break; case COMMAND_QUIT: - rv = ProcessResponseQUIT(response_line); + rv = ProcessResponseQUIT(response); break; default: DLOG(INFO) << "Missing Command response handling!"; return ERR_FAILED; } - DCHECK(ctrl_responses_.empty()); return rv; } @@ -331,6 +245,13 @@ int FtpNetworkTransaction::DoLoop(int result) { case STATE_CTRL_READ_COMPLETE: rv = DoCtrlReadComplete(rv); break; + case STATE_CTRL_WRITE_COMMAND: + DCHECK(rv == OK); + rv = DoCtrlWriteCommand(); + break; + case STATE_CTRL_WRITE_COMMAND_COMPLETE: + rv = DoCtrlWriteCommandComplete(rv); + break; case STATE_CTRL_WRITE_USER: DCHECK(rv == OK); rv = DoCtrlWriteUSER(); @@ -461,14 +382,15 @@ int FtpNetworkTransaction::DoCtrlConnectComplete(int result) { } int FtpNetworkTransaction::DoCtrlRead() { - if (write_buf_) // Clear the write buffer - write_buf_ = NULL; + // Clear the write buffer. + write_command_buf_ = NULL; + write_buf_ = NULL; next_state_ = STATE_CTRL_READ_COMPLETE; read_ctrl_buf_->data()[0] = 0; return ctrl_socket_->Read( read_ctrl_buf_, - response_message_buf_->size() - response_message_buf_len_, + kCtrlBufLen, &io_callback_); } @@ -476,31 +398,41 @@ int FtpNetworkTransaction::DoCtrlReadComplete(int result) { if (result < 0) return Stop(ERR_FAILED); - response_message_buf_len_ += result; + ctrl_response_buffer_.ConsumeData(read_ctrl_buf_->data(), result); - int cut_pos; - int rv = ParseCtrlResponse(&cut_pos); - - if (rv != OK) - return Stop(rv); + if (!ctrl_response_buffer_.ResponseAvailable()) { + // Read more data from the control socket. + next_state_ = STATE_CTRL_READ; + return OK; + } - if (cut_pos > 0) { - // Parsed at least one response line. - DCHECK_GE(response_message_buf_len_, cut_pos); - memmove(response_message_buf_->data(), - response_message_buf_->data() + cut_pos, - response_message_buf_len_ - cut_pos); - response_message_buf_len_ -= cut_pos; + return ProcessCtrlResponse(); +} - rv = ProcessCtrlResponses(); +int FtpNetworkTransaction::DoCtrlWriteCommand() { + write_buf_->SetOffset(write_buf_written_); + int rv = ctrl_socket_->Write(write_buf_, + write_command_buf_->size() - write_buf_written_, + &io_callback_); + if (rv == ERR_IO_PENDING) { + next_state_ = STATE_CTRL_WRITE_COMMAND_COMPLETE; + return rv; } else { - // Incomplete response line. Read more. - next_state_ = STATE_CTRL_READ; + return DoCtrlWriteCommandComplete(rv); } +} - read_ctrl_buf_->SetOffset(response_message_buf_len_); +int FtpNetworkTransaction::DoCtrlWriteCommandComplete(int result) { + if (result < 0) + return result; - return rv; + write_buf_written_ += result; + if (write_buf_written_ == write_command_buf_->size()) { + next_state_ = STATE_CTRL_READ; + } else { + next_state_ = STATE_CTRL_WRITE_COMMAND; + } + return OK; } // FTP Commands and responses @@ -519,8 +451,9 @@ int FtpNetworkTransaction::DoCtrlWriteUSER() { return SendFtpCommand(command, COMMAND_USER); } -int FtpNetworkTransaction::ProcessResponseUSER(const ResponseLine& response) { - switch (GetErrorClass(response.code)) { +int FtpNetworkTransaction::ProcessResponseUSER( + const FtpCtrlResponse& response) { + switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_OK: next_state_ = STATE_CTRL_WRITE_SYST; break; @@ -528,7 +461,7 @@ int FtpNetworkTransaction::ProcessResponseUSER(const ResponseLine& response) { next_state_ = STATE_CTRL_WRITE_PASS; break; case ERROR_CLASS_ERROR_RETRY: - if (response.code == 421) + if (response.status_code == 421) return Stop(ERR_FAILED); break; case ERROR_CLASS_ERROR: @@ -553,8 +486,9 @@ int FtpNetworkTransaction::DoCtrlWritePASS() { return SendFtpCommand(command, COMMAND_PASS); } -int FtpNetworkTransaction::ProcessResponsePASS(const ResponseLine& response) { - switch (GetErrorClass(response.code)) { +int FtpNetworkTransaction::ProcessResponsePASS( + const FtpCtrlResponse& response) { + switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_OK: next_state_ = STATE_CTRL_WRITE_SYST; break; @@ -562,12 +496,12 @@ int FtpNetworkTransaction::ProcessResponsePASS(const ResponseLine& response) { next_state_ = STATE_CTRL_WRITE_ACCT; break; case ERROR_CLASS_ERROR_RETRY: - if (response.code == 421) { + if (response.status_code == 421) { // TODO(ibrar): Retry here. } return Stop(ERR_FAILED); case ERROR_CLASS_ERROR: - if (response.code == 503) { + if (response.status_code == 503) { next_state_ = STATE_CTRL_WRITE_USER; } else { // TODO(ibrar): Retry here. @@ -587,8 +521,9 @@ int FtpNetworkTransaction::DoCtrlWriteSYST() { return SendFtpCommand(command, COMMAND_SYST); } -int FtpNetworkTransaction::ProcessResponseSYST(const ResponseLine& response) { - switch (GetErrorClass(response.code)) { +int FtpNetworkTransaction::ProcessResponseSYST( + const FtpCtrlResponse& response) { + switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_INITIATED: return Stop(ERR_FAILED); case ERROR_CLASS_OK: @@ -616,8 +551,8 @@ int FtpNetworkTransaction::DoCtrlWritePWD() { return SendFtpCommand(command, COMMAND_PWD); } -int FtpNetworkTransaction::ProcessResponsePWD(const ResponseLine& response) { - switch (GetErrorClass(response.code)) { +int FtpNetworkTransaction::ProcessResponsePWD(const FtpCtrlResponse& response) { + switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_INITIATED: return Stop(ERR_FAILED); case ERROR_CLASS_OK: @@ -642,8 +577,9 @@ int FtpNetworkTransaction::DoCtrlWriteTYPE() { return SendFtpCommand(command, COMMAND_TYPE); } -int FtpNetworkTransaction::ProcessResponseTYPE(const ResponseLine& response) { - switch (GetErrorClass(response.code)) { +int FtpNetworkTransaction::ProcessResponseTYPE( + const FtpCtrlResponse& response) { + switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_INITIATED: return Stop(ERR_FAILED); case ERROR_CLASS_OK: @@ -668,8 +604,9 @@ int FtpNetworkTransaction::DoCtrlWriteACCT() { return SendFtpCommand(command, COMMAND_ACCT); } -int FtpNetworkTransaction::ProcessResponseACCT(const ResponseLine& response) { - switch (GetErrorClass(response.code)) { +int FtpNetworkTransaction::ProcessResponseACCT( + const FtpCtrlResponse& response) { + switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_INITIATED: return Stop(ERR_FAILED); case ERROR_CLASS_OK: @@ -697,20 +634,23 @@ int FtpNetworkTransaction::DoCtrlWritePASV() { // There are two way we can receive IP address and port. // (127,0,0,1,23,21) IP address and port encapsulate in (). // 127,0,0,1,23,21 IP address and port without (). -int FtpNetworkTransaction::ProcessResponsePASV(const ResponseLine& response) { - switch (GetErrorClass(response.code)) { +int FtpNetworkTransaction::ProcessResponsePASV( + const FtpCtrlResponse& response) { + switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_INITIATED: return Stop(ERR_FAILED); case ERROR_CLASS_OK: const char* ptr; int i0, i1, i2, i3, p0, p1; - ptr = response.text.c_str(); // Try with bracket. + if (response.lines.size() != 1) + return Stop(ERR_INVALID_RESPONSE); + ptr = response.lines[0].c_str(); // Try with bracket. while (*ptr && *ptr != '(') ++ptr; if (*ptr) { ++ptr; } else { - ptr = response.text.c_str(); // Try without bracket. + ptr = response.lines[0].c_str(); // Try without bracket. while (*ptr && *ptr != ',') ++ptr; while (*ptr && *ptr != ' ') @@ -748,12 +688,15 @@ int FtpNetworkTransaction::DoCtrlWriteSIZE() { return SendFtpCommand(command, COMMAND_SIZE); } -int FtpNetworkTransaction::ProcessResponseSIZE(const ResponseLine& response) { - switch (GetErrorClass(response.code)) { +int FtpNetworkTransaction::ProcessResponseSIZE( + const FtpCtrlResponse& response) { + switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_INITIATED: break; case ERROR_CLASS_OK: - if (!StringToInt(response.text, &file_data_len_)) + if (response.lines.size() != 1) + return Stop(ERR_INVALID_RESPONSE); + if (!StringToInt(response.lines[0], &file_data_len_)) return Stop(ERR_INVALID_RESPONSE); if (file_data_len_ < 0) return Stop(ERR_INVALID_RESPONSE); @@ -784,8 +727,9 @@ int FtpNetworkTransaction::DoCtrlWriteRETR() { return SendFtpCommand(command, COMMAND_RETR); } -int FtpNetworkTransaction::ProcessResponseRETR(const ResponseLine& response) { - switch (GetErrorClass(response.code)) { +int FtpNetworkTransaction::ProcessResponseRETR( + const FtpCtrlResponse& response) { + switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_INITIATED: break; case ERROR_CLASS_OK: @@ -795,7 +739,8 @@ int FtpNetworkTransaction::ProcessResponseRETR(const ResponseLine& response) { next_state_ = STATE_CTRL_WRITE_PASV; break; case ERROR_CLASS_ERROR_RETRY: - if (response.code == 421 || response.code == 425 || response.code == 426) + if (response.status_code == 421 || response.status_code == 425 || + response.status_code == 426) return Stop(ERR_FAILED); return ERR_FAILED; // TODO(ibrar): Retry here. case ERROR_CLASS_ERROR: @@ -823,8 +768,9 @@ int FtpNetworkTransaction::DoCtrlWriteMDTM() { return SendFtpCommand(command, COMMAND_MDTM); } -int FtpNetworkTransaction::ProcessResponseMDTM(const ResponseLine& response) { - switch (GetErrorClass(response.code)) { +int FtpNetworkTransaction::ProcessResponseMDTM( + const FtpCtrlResponse& response) { + switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_INITIATED: return Stop(ERR_FAILED); case ERROR_CLASS_OK: @@ -857,8 +803,8 @@ int FtpNetworkTransaction::DoCtrlWriteCWD() { return SendFtpCommand(command, COMMAND_CWD); } -int FtpNetworkTransaction::ProcessResponseCWD(const ResponseLine& response) { - switch (GetErrorClass(response.code)) { +int FtpNetworkTransaction::ProcessResponseCWD(const FtpCtrlResponse& response) { + switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_INITIATED: return Stop(ERR_FAILED); case ERROR_CLASS_OK: @@ -883,10 +829,10 @@ int FtpNetworkTransaction::DoCtrlWriteLIST() { return SendFtpCommand(command, COMMAND_LIST); } -int FtpNetworkTransaction::ProcessResponseLIST(const ResponseLine& response) { - switch (GetErrorClass(response.code)) { +int FtpNetworkTransaction::ProcessResponseLIST( + const FtpCtrlResponse& response) { + switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_INITIATED: - response_message_buf_len_ = 0; // Clear the response buffer. next_state_ = STATE_CTRL_READ; break; case ERROR_CLASS_OK: @@ -912,7 +858,8 @@ int FtpNetworkTransaction::DoCtrlWriteQUIT() { return SendFtpCommand(command, COMMAND_QUIT); } -int FtpNetworkTransaction::ProcessResponseQUIT(const ResponseLine& response) { +int FtpNetworkTransaction::ProcessResponseQUIT( + const FtpCtrlResponse& response) { ctrl_socket_->Disconnect(); return last_error_; } diff --git a/net/ftp/ftp_network_transaction.h b/net/ftp/ftp_network_transaction.h index 90c53e4..5ca098c 100644 --- a/net/ftp/ftp_network_transaction.h +++ b/net/ftp/ftp_network_transaction.h @@ -8,11 +8,13 @@ #include <string> #include <queue> #include <utility> +#include <vector> #include "base/ref_counted.h" #include "base/scoped_ptr.h" #include "net/base/address_list.h" #include "net/base/host_resolver.h" +#include "net/ftp/ftp_ctrl_response_buffer.h" #include "net/ftp/ftp_response_info.h" #include "net/ftp/ftp_transaction.h" @@ -72,24 +74,12 @@ class FtpNetworkTransaction : public FtpTransaction { // the requested action did not take place. }; - struct ResponseLine { - ResponseLine(int code, const std::string& text) : code(code), text(text) { - } - - int code; // Three-digit status code. - std::string text; // Text after the code, without ending CRLF. - }; - void DoCallback(int result); void OnIOComplete(int result); // Executes correct ProcessResponse + command_name function based on last // issued command. Returns error code. - int ProcessCtrlResponses(); - - // Parses as much as possible from response_message_buf_. Puts index of the - // first unparsed character in cut_pos. Returns error code. - int ParseCtrlResponse(int* cut_pos); + int ProcessCtrlResponse(); int SendFtpCommand(const std::string& command, Command cmd); @@ -113,32 +103,34 @@ class FtpNetworkTransaction : public FtpTransaction { int DoCtrlConnectComplete(int result); int DoCtrlRead(); int DoCtrlReadComplete(int result); + int DoCtrlWriteCommand(); + int DoCtrlWriteCommandComplete(int result); int DoCtrlWriteUSER(); - int ProcessResponseUSER(const ResponseLine& response); + int ProcessResponseUSER(const FtpCtrlResponse& response); int DoCtrlWritePASS(); - int ProcessResponsePASS(const ResponseLine& response); + int ProcessResponsePASS(const FtpCtrlResponse& response); int DoCtrlWriteACCT(); - int ProcessResponseACCT(const ResponseLine& response); + int ProcessResponseACCT(const FtpCtrlResponse& response); int DoCtrlWriteSYST(); - int ProcessResponseSYST(const ResponseLine& response); + int ProcessResponseSYST(const FtpCtrlResponse& response); int DoCtrlWritePWD(); - int ProcessResponsePWD(const ResponseLine& response); + int ProcessResponsePWD(const FtpCtrlResponse& response); int DoCtrlWriteTYPE(); - int ProcessResponseTYPE(const ResponseLine& response); + int ProcessResponseTYPE(const FtpCtrlResponse& response); int DoCtrlWritePASV(); - int ProcessResponsePASV(const ResponseLine& response); + int ProcessResponsePASV(const FtpCtrlResponse& response); int DoCtrlWriteRETR(); - int ProcessResponseRETR(const ResponseLine& response); + int ProcessResponseRETR(const FtpCtrlResponse& response); int DoCtrlWriteSIZE(); - int ProcessResponseSIZE(const ResponseLine& response); + int ProcessResponseSIZE(const FtpCtrlResponse& response); int DoCtrlWriteCWD(); - int ProcessResponseCWD(const ResponseLine& response); + int ProcessResponseCWD(const FtpCtrlResponse& response); int DoCtrlWriteLIST(); - int ProcessResponseLIST(const ResponseLine& response); + int ProcessResponseLIST(const FtpCtrlResponse& response); int DoCtrlWriteMDTM(); - int ProcessResponseMDTM(const ResponseLine& response); + int ProcessResponseMDTM(const FtpCtrlResponse& response); int DoCtrlWriteQUIT(); - int ProcessResponseQUIT(const ResponseLine& response); + int ProcessResponseQUIT(const FtpCtrlResponse& response); int DoDataResolveHost(); int DoDataResolveHostComplete(int result); @@ -161,22 +153,25 @@ class FtpNetworkTransaction : public FtpTransaction { SingleRequestHostResolver resolver_; AddressList addresses_; - // As we read full response lines, we parse them and add to the queue. - std::queue<ResponseLine> ctrl_responses_; + // User buffer passed to the Read method for control socket. + scoped_refptr<IOBuffer> read_ctrl_buf_; - // Buffer holding not-yet-parsed control socket responses. - scoped_refptr<IOBufferWithSize> response_message_buf_; - int response_message_buf_len_; - - // User buffer passed to the Read method. It actually writes to the - // response_message_buf_ at correct offset. - scoped_refptr<ReusedIOBuffer> read_ctrl_buf_; + FtpCtrlResponseBuffer ctrl_response_buffer_; scoped_refptr<IOBuffer> read_data_buf_; int read_data_buf_len_; int file_data_len_; - scoped_refptr<IOBuffer> write_buf_; + // Buffer holding the command line to be written to the control socket. + scoped_refptr<IOBufferWithSize> write_command_buf_; + + // Buffer passed to the Write method of control socket. It actually writes + // to the write_command_buf_ at correct offset. + scoped_refptr<ReusedIOBuffer> write_buf_; + + // Number of bytes from write_command_buf_ that we've already sent to the + // server. + int write_buf_written_; int last_error_; @@ -201,6 +196,8 @@ class FtpNetworkTransaction : public FtpTransaction { STATE_CTRL_CONNECT_COMPLETE, STATE_CTRL_READ, STATE_CTRL_READ_COMPLETE, + STATE_CTRL_WRITE_COMMAND, + STATE_CTRL_WRITE_COMMAND_COMPLETE, STATE_CTRL_WRITE_USER, STATE_CTRL_WRITE_PASS, STATE_CTRL_WRITE_ACCT, diff --git a/net/ftp/ftp_network_transaction_unittest.cc b/net/ftp/ftp_network_transaction_unittest.cc index d549b2f..7726416 100644 --- a/net/ftp/ftp_network_transaction_unittest.cc +++ b/net/ftp/ftp_network_transaction_unittest.cc @@ -51,7 +51,7 @@ class FtpMockControlSocket : public DynamicMockSocket { virtual MockWriteResult OnWrite(const std::string& data) { if (InjectFault()) - return MockWriteResult(true, OK); + return MockWriteResult(true, data.length()); switch (state()) { case PRE_USER: return Verify("USER anonymous\r\n", data, PRE_PASSWD, @@ -59,7 +59,7 @@ class FtpMockControlSocket : public DynamicMockSocket { case PRE_PASSWD: { const char* response_one = "230 Welcome\r\n"; - const char* response_multi = "230 One\r\n230 Two\r\n230 Three\r\n"; + 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); } @@ -128,7 +128,7 @@ class FtpMockControlSocket : public DynamicMockSocket { if (expected == data) { state_ = next_state; SimulateRead(next_read); - return MockWriteResult(true, OK); + return MockWriteResult(true, data.length()); } return MockWriteResult(true, ERR_UNEXPECTED); } @@ -152,7 +152,7 @@ class FtpMockControlSocketDirectoryListing : public FtpMockControlSocket { virtual MockWriteResult OnWrite(const std::string& data) { if (InjectFault()) - return MockWriteResult(true, OK); + return MockWriteResult(true, data.length()); switch (state()) { case PRE_SIZE: return Verify("SIZE /\r\n", data, PRE_MDTM, @@ -188,7 +188,7 @@ class FtpMockControlSocketFileDownload : public FtpMockControlSocket { virtual MockWriteResult OnWrite(const std::string& data) { if (InjectFault()) - return MockWriteResult(true, OK); + return MockWriteResult(true, data.length()); switch (state()) { case PRE_SIZE: return Verify("SIZE /file\r\n", data, PRE_MDTM, @@ -216,7 +216,7 @@ class FtpMockControlSocketFileDownloadRetrFail virtual MockWriteResult OnWrite(const std::string& data) { if (InjectFault()) - return MockWriteResult(true, OK); + return MockWriteResult(true, data.length()); switch (state()) { case PRE_PASV2: return Verify("PASV\r\n", data, PRE_CWD, diff --git a/net/net.gyp b/net/net.gyp index f52caaf..fb01d8b 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -191,6 +191,8 @@ 'disk_cache/trace.h', 'ftp/ftp_auth_cache.cc', 'ftp/ftp_auth_cache.h', + 'ftp/ftp_ctrl_response_buffer.cc', + 'ftp/ftp_ctrl_response_buffer.h', 'ftp/ftp_directory_parser.cc', 'ftp/ftp_directory_parser.h', 'ftp/ftp_network_layer.cc', @@ -474,6 +476,7 @@ 'disk_cache/mapped_file_unittest.cc', 'disk_cache/storage_block_unittest.cc', 'ftp/ftp_auth_cache_unittest.cc', + 'ftp/ftp_ctrl_response_buffer_unittest.cc', 'ftp/ftp_network_transaction_unittest.cc', 'http/des_unittest.cc', 'http/http_auth_cache_unittest.cc', |