summaryrefslogtreecommitdiff
path: root/chromium/net/ftp
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/net/ftp
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/net/ftp')
-rw-r--r--chromium/net/ftp/OWNERS1
-rw-r--r--chromium/net/ftp/ftp_auth_cache.cc62
-rw-r--r--chromium/net/ftp/ftp_auth_cache.h61
-rw-r--r--chromium/net/ftp/ftp_auth_cache_unittest.cc159
-rw-r--r--chromium/net/ftp/ftp_ctrl_response_buffer.cc152
-rw-r--r--chromium/net/ftp/ftp_ctrl_response_buffer.h101
-rw-r--r--chromium/net/ftp/ftp_ctrl_response_buffer_unittest.cc175
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser.cc145
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser.h46
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_ls.cc233
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_ls.h29
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_ls_unittest.cc218
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_netware.cc94
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_netware.h29
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_netware_unittest.cc77
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_os2.cc77
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_os2.h24
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_os2_unittest.cc116
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_unittest.cc166
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_unittest.h76
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_vms.cc293
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_vms.h24
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_vms_unittest.cc168
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_windows.cc72
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_windows.h24
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_windows_unittest.cc126
-rw-r--r--chromium/net/ftp/ftp_network_layer.cc44
-rw-r--r--chromium/net/ftp/ftp_network_layer.h38
-rw-r--r--chromium/net/ftp/ftp_network_session.cc15
-rw-r--r--chromium/net/ftp/ftp_network_session.h33
-rw-r--r--chromium/net/ftp/ftp_network_transaction.cc1400
-rw-r--r--chromium/net/ftp/ftp_network_transaction.h262
-rw-r--r--chromium/net/ftp/ftp_network_transaction_unittest.cc1602
-rw-r--r--chromium/net/ftp/ftp_request_info.h20
-rw-r--r--chromium/net/ftp/ftp_response_info.cc17
-rw-r--r--chromium/net/ftp/ftp_response_info.h43
-rw-r--r--chromium/net/ftp/ftp_server_type_histograms.cc33
-rw-r--r--chromium/net/ftp/ftp_server_type_histograms.h32
-rw-r--r--chromium/net/ftp/ftp_transaction.h80
-rw-r--r--chromium/net/ftp/ftp_transaction_factory.h29
-rw-r--r--chromium/net/ftp/ftp_util.cc376
-rw-r--r--chromium/net/ftp/ftp_util.h58
-rw-r--r--chromium/net/ftp/ftp_util_unittest.cc253
43 files changed, 7083 insertions, 0 deletions
diff --git a/chromium/net/ftp/OWNERS b/chromium/net/ftp/OWNERS
new file mode 100644
index 00000000000..92ecc889fd3
--- /dev/null
+++ b/chromium/net/ftp/OWNERS
@@ -0,0 +1 @@
+phajdan.jr@chromium.org
diff --git a/chromium/net/ftp/ftp_auth_cache.cc b/chromium/net/ftp/ftp_auth_cache.cc
new file mode 100644
index 00000000000..370cee57379
--- /dev/null
+++ b/chromium/net/ftp/ftp_auth_cache.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 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_auth_cache.h"
+
+#include "base/logging.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// static
+const size_t FtpAuthCache::kMaxEntries = 10;
+
+FtpAuthCache::Entry::Entry(const GURL& origin,
+ const AuthCredentials& credentials)
+ : origin(origin),
+ credentials(credentials) {
+}
+
+FtpAuthCache::Entry::~Entry() {}
+
+FtpAuthCache::FtpAuthCache() {}
+
+FtpAuthCache::~FtpAuthCache() {}
+
+FtpAuthCache::Entry* FtpAuthCache::Lookup(const GURL& origin) {
+ for (EntryList::iterator it = entries_.begin(); it != entries_.end(); ++it) {
+ if (it->origin == origin)
+ return &(*it);
+ }
+ return NULL;
+}
+
+void FtpAuthCache::Add(const GURL& origin, const AuthCredentials& credentials) {
+ DCHECK(origin.SchemeIs("ftp"));
+ DCHECK_EQ(origin.GetOrigin(), origin);
+
+ Entry* entry = Lookup(origin);
+ if (entry) {
+ entry->credentials = credentials;
+ } else {
+ entries_.push_front(Entry(origin, credentials));
+
+ // Prevent unbound memory growth of the cache.
+ if (entries_.size() > kMaxEntries)
+ entries_.pop_back();
+ }
+}
+
+void FtpAuthCache::Remove(const GURL& origin,
+ const AuthCredentials& credentials) {
+ for (EntryList::iterator it = entries_.begin(); it != entries_.end(); ++it) {
+ if (it->origin == origin && it->credentials.Equals(credentials)) {
+ entries_.erase(it);
+ DCHECK(!Lookup(origin));
+ return;
+ }
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_auth_cache.h b/chromium/net/ftp/ftp_auth_cache.h
new file mode 100644
index 00000000000..526b3588782
--- /dev/null
+++ b/chromium/net/ftp/ftp_auth_cache.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2011 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_AUTH_CACHE_H_
+#define NET_FTP_FTP_AUTH_CACHE_H_
+
+#include <list>
+
+#include "net/base/auth.h"
+#include "net/base/net_export.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// The FtpAuthCache class is a simple cache structure to store authentication
+// information for ftp. Provides lookup, insertion, and deletion of entries.
+// The parameter for doing lookups, insertions, and deletions is a GURL of the
+// server's address (not a full URL with path, since FTP auth isn't per path).
+// For example:
+// GURL("ftp://myserver") -- OK (implied port of 21)
+// GURL("ftp://myserver:21") -- OK
+// GURL("ftp://myserver/PATH") -- WRONG, paths not allowed
+class NET_EXPORT_PRIVATE FtpAuthCache {
+ public:
+ // Maximum number of entries we allow in the cache.
+ static const size_t kMaxEntries;
+
+ struct Entry {
+ Entry(const GURL& origin, const AuthCredentials& credentials);
+ ~Entry();
+
+ GURL origin;
+ AuthCredentials credentials;
+ };
+
+ FtpAuthCache();
+ ~FtpAuthCache();
+
+ // Return Entry corresponding to given |origin| or NULL if not found.
+ Entry* Lookup(const GURL& origin);
+
+ // Add an entry for |origin| to the cache using |credentials|. If there is
+ // already an entry for |origin|, it will be overwritten.
+ void Add(const GURL& origin, const AuthCredentials& credentials);
+
+ // Remove the entry for |origin| from the cache, if one exists and matches
+ // |credentials|.
+ void Remove(const GURL& origin, const AuthCredentials& credentials);
+
+ private:
+ typedef std::list<Entry> EntryList;
+
+ // Internal representation of cache, an STL list. This makes lookups O(n),
+ // but we expect n to be very low.
+ EntryList entries_;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_AUTH_CACHE_H_
diff --git a/chromium/net/ftp/ftp_auth_cache_unittest.cc b/chromium/net/ftp/ftp_auth_cache_unittest.cc
new file mode 100644
index 00000000000..153d08be49f
--- /dev/null
+++ b/chromium/net/ftp/ftp_auth_cache_unittest.cc
@@ -0,0 +1,159 @@
+// Copyright (c) 2011 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_auth_cache.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/auth.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using net::FtpAuthCache;
+
+namespace {
+
+const base::string16 kBogus(ASCIIToUTF16("bogus"));
+const base::string16 kOthername(ASCIIToUTF16("othername"));
+const base::string16 kOtherword(ASCIIToUTF16("otherword"));
+const base::string16 kPassword(ASCIIToUTF16("password"));
+const base::string16 kPassword1(ASCIIToUTF16("password1"));
+const base::string16 kPassword2(ASCIIToUTF16("password2"));
+const base::string16 kPassword3(ASCIIToUTF16("password3"));
+const base::string16 kUsername(ASCIIToUTF16("username"));
+const base::string16 kUsername1(ASCIIToUTF16("username1"));
+const base::string16 kUsername2(ASCIIToUTF16("username2"));
+const base::string16 kUsername3(ASCIIToUTF16("username3"));
+
+} // namespace
+
+TEST(FtpAuthCacheTest, LookupAddRemove) {
+ FtpAuthCache cache;
+
+ GURL origin1("ftp://foo1");
+ GURL origin2("ftp://foo2");
+
+ // Lookup non-existent entry.
+ EXPECT_TRUE(cache.Lookup(origin1) == NULL);
+
+ // Add entry for origin1.
+ cache.Add(origin1, net::AuthCredentials(kUsername1, kPassword1));
+ FtpAuthCache::Entry* entry1 = cache.Lookup(origin1);
+ ASSERT_TRUE(entry1);
+ EXPECT_EQ(origin1, entry1->origin);
+ EXPECT_EQ(kUsername1, entry1->credentials.username());
+ EXPECT_EQ(kPassword1, entry1->credentials.password());
+
+ // Add an entry for origin2.
+ cache.Add(origin2, net::AuthCredentials(kUsername2, kPassword2));
+ FtpAuthCache::Entry* entry2 = cache.Lookup(origin2);
+ ASSERT_TRUE(entry2);
+ EXPECT_EQ(origin2, entry2->origin);
+ EXPECT_EQ(kUsername2, entry2->credentials.username());
+ EXPECT_EQ(kPassword2, entry2->credentials.password());
+
+ // The original entry1 should still be there.
+ EXPECT_EQ(entry1, cache.Lookup(origin1));
+
+ // Overwrite the entry for origin1.
+ cache.Add(origin1, net::AuthCredentials(kUsername3, kPassword3));
+ FtpAuthCache::Entry* entry3 = cache.Lookup(origin1);
+ ASSERT_TRUE(entry3);
+ EXPECT_EQ(origin1, entry3->origin);
+ EXPECT_EQ(kUsername3, entry3->credentials.username());
+ EXPECT_EQ(kPassword3, entry3->credentials.password());
+
+ // Remove entry of origin1.
+ cache.Remove(origin1, net::AuthCredentials(kUsername3, kPassword3));
+ EXPECT_TRUE(cache.Lookup(origin1) == NULL);
+
+ // Remove non-existent entry.
+ cache.Remove(origin1, net::AuthCredentials(kUsername3, kPassword3));
+ EXPECT_TRUE(cache.Lookup(origin1) == NULL);
+}
+
+// Check that if the origin differs only by port number, it is considered
+// a separate origin.
+TEST(FtpAuthCacheTest, LookupWithPort) {
+ FtpAuthCache cache;
+
+ GURL origin1("ftp://foo:80");
+ GURL origin2("ftp://foo:21");
+
+ cache.Add(origin1, net::AuthCredentials(kUsername, kPassword));
+ cache.Add(origin2, net::AuthCredentials(kUsername, kPassword));
+
+ EXPECT_NE(cache.Lookup(origin1), cache.Lookup(origin2));
+}
+
+TEST(FtpAuthCacheTest, NormalizedKey) {
+ // GURL is automatically canonicalized. Hence the following variations in
+ // url format should all map to the same entry (case insensitive host,
+ // default port of 21).
+
+ FtpAuthCache cache;
+
+ // Add.
+ cache.Add(GURL("ftp://HoSt:21"), net::AuthCredentials(kUsername, kPassword));
+
+ // Lookup.
+ FtpAuthCache::Entry* entry1 = cache.Lookup(GURL("ftp://HoSt:21"));
+ ASSERT_TRUE(entry1);
+ EXPECT_EQ(entry1, cache.Lookup(GURL("ftp://host:21")));
+ EXPECT_EQ(entry1, cache.Lookup(GURL("ftp://host")));
+
+ // Overwrite.
+ cache.Add(GURL("ftp://host"), net::AuthCredentials(kOthername, kOtherword));
+ FtpAuthCache::Entry* entry2 = cache.Lookup(GURL("ftp://HoSt:21"));
+ ASSERT_TRUE(entry2);
+ EXPECT_EQ(GURL("ftp://host"), entry2->origin);
+ EXPECT_EQ(kOthername, entry2->credentials.username());
+ EXPECT_EQ(kOtherword, entry2->credentials.password());
+
+ // Remove
+ cache.Remove(GURL("ftp://HOsT"),
+ net::AuthCredentials(kOthername, kOtherword));
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host")) == NULL);
+}
+
+TEST(FtpAuthCacheTest, OnlyRemoveMatching) {
+ FtpAuthCache cache;
+
+ cache.Add(GURL("ftp://host"), net::AuthCredentials(kUsername, kPassword));
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host")));
+
+ // Auth data doesn't match, shouldn't remove.
+ cache.Remove(GURL("ftp://host"), net::AuthCredentials(kBogus, kBogus));
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host")));
+
+ // Auth data matches, should remove.
+ cache.Remove(GURL("ftp://host"), net::AuthCredentials(kUsername, kPassword));
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host")) == NULL);
+}
+
+TEST(FtpAuthCacheTest, EvictOldEntries) {
+ FtpAuthCache cache;
+
+ for (size_t i = 0; i < FtpAuthCache::kMaxEntries; i++) {
+ cache.Add(GURL("ftp://host" + base::IntToString(i)),
+ net::AuthCredentials(kUsername, kPassword));
+ }
+
+ // No entries should be evicted before reaching the limit.
+ for (size_t i = 0; i < FtpAuthCache::kMaxEntries; i++) {
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host" + base::IntToString(i))));
+ }
+
+ // Adding one entry should cause eviction of the first entry.
+ cache.Add(GURL("ftp://last_host"),
+ net::AuthCredentials(kUsername, kPassword));
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host0")) == NULL);
+
+ // Remaining entries should not get evicted.
+ for (size_t i = 1; i < FtpAuthCache::kMaxEntries; i++) {
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host" + base::IntToString(i))));
+ }
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://last_host")));
+}
diff --git a/chromium/net/ftp/ftp_ctrl_response_buffer.cc b/chromium/net/ftp/ftp_ctrl_response_buffer.cc
new file mode 100644
index 00000000000..f7b14678095
--- /dev/null
+++ b/chromium/net/ftp/ftp_ctrl_response_buffer.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2011 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/bind.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+// static
+const int FtpCtrlResponse::kInvalidStatusCode = -1;
+
+FtpCtrlResponse::FtpCtrlResponse() : status_code(kInvalidStatusCode) {}
+
+FtpCtrlResponse::~FtpCtrlResponse() {}
+
+FtpCtrlResponseBuffer::FtpCtrlResponseBuffer(const BoundNetLog& net_log)
+ : multiline_(false),
+ net_log_(net_log) {
+}
+
+FtpCtrlResponseBuffer::~FtpCtrlResponseBuffer() {}
+
+int FtpCtrlResponseBuffer::ConsumeData(const char* data, int data_length) {
+ buffer_.append(data, data_length);
+ ExtractFullLinesFromBuffer();
+
+ while (!lines_.empty()) {
+ ParsedLine line = lines_.front();
+ lines_.pop();
+
+ if (multiline_) {
+ 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();
+ multiline_ = false;
+ }
+ } else {
+ if (!line.is_complete)
+ return ERR_INVALID_RESPONSE;
+
+ response_buf_.status_code = line.status_code;
+ if (line.is_multiline) {
+ line_buf_ = line.status_text;
+ multiline_ = true;
+ } else {
+ response_buf_.lines.push_back(line.status_text);
+ responses_.push(response_buf_);
+
+ // Prepare to handle following lines.
+ response_buf_ = FtpCtrlResponse();
+ line_buf_.clear();
+ }
+ }
+ }
+
+ return OK;
+}
+
+namespace {
+
+base::Value* NetLogFtpCtrlResponseCallback(const FtpCtrlResponse* response,
+ NetLog::LogLevel log_level) {
+ base::ListValue* lines = new base::ListValue();
+ lines->AppendStrings(response->lines);
+
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("status_code", response->status_code);
+ dict->Set("lines", lines);
+ return dict;
+}
+
+} // namespace
+
+FtpCtrlResponse FtpCtrlResponseBuffer::PopResponse() {
+ FtpCtrlResponse result = responses_.front();
+ responses_.pop();
+
+ net_log_.AddEvent(NetLog::TYPE_FTP_CONTROL_RESPONSE,
+ base::Bind(&NetLogFtpCtrlResponseCallback, &result));
+
+ return result;
+}
+
+FtpCtrlResponseBuffer::ParsedLine::ParsedLine()
+ : has_status_code(false),
+ is_multiline(false),
+ is_complete(false),
+ status_code(FtpCtrlResponse::kInvalidStatusCode) {
+}
+
+// static
+FtpCtrlResponseBuffer::ParsedLine FtpCtrlResponseBuffer::ParseLine(
+ const std::string& line) {
+ ParsedLine result;
+
+ if (line.length() >= 3) {
+ if (base::StringToInt(base::StringPiece(line.begin(), line.begin() + 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() {
+ int cut_pos = 0;
+ for (size_t i = 0; i < buffer_.length(); i++) {
+ if (i >= 1 && buffer_[i - 1] == '\r' && buffer_[i] == '\n') {
+ lines_.push(ParseLine(buffer_.substr(cut_pos, i - cut_pos - 1)));
+ cut_pos = i + 1;
+ }
+ }
+ buffer_.erase(0, cut_pos);
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_ctrl_response_buffer.h b/chromium/net/ftp/ftp_ctrl_response_buffer.h
new file mode 100644
index 00000000000..fa5c03115b3
--- /dev/null
+++ b/chromium/net/ftp/ftp_ctrl_response_buffer.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2011 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"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+
+namespace net {
+
+struct NET_EXPORT_PRIVATE FtpCtrlResponse {
+ static const int kInvalidStatusCode;
+
+ FtpCtrlResponse();
+ ~FtpCtrlResponse();
+
+ int status_code; // Three-digit status code.
+ std::vector<std::string> lines; // Response lines, without CRLFs.
+};
+
+class NET_EXPORT_PRIVATE FtpCtrlResponseBuffer {
+ public:
+ FtpCtrlResponseBuffer(const BoundNetLog& net_log);
+ ~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();
+
+ private:
+ struct ParsedLine {
+ ParsedLine();
+
+ // 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 terminating 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_;
+
+ // True if we are in the middle of parsing a multi-line response.
+ bool multiline_;
+
+ // 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_;
+
+ BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpCtrlResponseBuffer);
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_CTRL_RESPONSE_BUFFER_H_
diff --git a/chromium/net/ftp/ftp_ctrl_response_buffer_unittest.cc b/chromium/net/ftp/ftp_ctrl_response_buffer_unittest.cc
new file mode 100644
index 00000000000..f74b28724a9
--- /dev/null
+++ b/chromium/net/ftp/ftp_ctrl_response_buffer_unittest.cc
@@ -0,0 +1,175 @@
+// 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 {
+ public:
+ FtpCtrlResponseBufferTest() : buffer_(net::BoundNetLog()) {
+ }
+
+ 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, MultilineContinuationZeroLength) {
+ // For the corner case from bug 29322.
+ EXPECT_EQ(net::OK, PushDataToBuffer("230-\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("example.com\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(2U, response.lines.size());
+ EXPECT_EQ("example.com", response.lines[0]);
+ EXPECT_EQ("LastLine", response.lines[1]);
+}
+
+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/chromium/net/ftp/ftp_directory_listing_parser.cc b/chromium/net/ftp/ftp_directory_listing_parser.cc
new file mode 100644
index 00000000000..03b77bb8205
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser.cc
@@ -0,0 +1,145 @@
+// Copyright (c) 2011 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_directory_listing_parser.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/i18n/icu_encoding_detection.h"
+#include "base/i18n/icu_string_conversions.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/ftp/ftp_directory_listing_parser_ls.h"
+#include "net/ftp/ftp_directory_listing_parser_netware.h"
+#include "net/ftp/ftp_directory_listing_parser_os2.h"
+#include "net/ftp/ftp_directory_listing_parser_vms.h"
+#include "net/ftp/ftp_directory_listing_parser_windows.h"
+#include "net/ftp/ftp_server_type_histograms.h"
+
+namespace net {
+
+namespace {
+
+// Fills in |raw_name| for all |entries| using |encoding|. Returns network
+// error code.
+int FillInRawName(const std::string& encoding,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ for (size_t i = 0; i < entries->size(); i++) {
+ if (!base::UTF16ToCodepage(entries->at(i).name, encoding.c_str(),
+ base::OnStringConversionError::FAIL,
+ &entries->at(i).raw_name)) {
+ return ERR_ENCODING_CONVERSION_FAILED;
+ }
+ }
+
+ return OK;
+}
+
+// Parses |text| as an FTP directory listing. Fills in |entries|
+// and |server_type| and returns network error code.
+int ParseListing(const base::string16& text,
+ const base::string16& newline_separator,
+ const std::string& encoding,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries,
+ FtpServerType* server_type) {
+ std::vector<base::string16> lines;
+ base::SplitStringUsingSubstr(text, newline_separator, &lines);
+
+ struct {
+ base::Callback<bool(void)> callback;
+ FtpServerType server_type;
+ } parsers[] = {
+ {
+ base::Bind(&ParseFtpDirectoryListingLs, lines, current_time, entries),
+ SERVER_LS
+ },
+ {
+ base::Bind(&ParseFtpDirectoryListingWindows, lines, entries),
+ SERVER_WINDOWS
+ },
+ {
+ base::Bind(&ParseFtpDirectoryListingVms, lines, entries),
+ SERVER_VMS
+ },
+ {
+ base::Bind(&ParseFtpDirectoryListingNetware,
+ lines, current_time, entries),
+ SERVER_NETWARE
+ },
+ {
+ base::Bind(&ParseFtpDirectoryListingOS2, lines, entries),
+ SERVER_OS2
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(parsers); i++) {
+ entries->clear();
+ if (parsers[i].callback.Run()) {
+ *server_type = parsers[i].server_type;
+ return FillInRawName(encoding, entries);
+ }
+ }
+
+ entries->clear();
+ return ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT;
+}
+
+// Detects encoding of |text| and parses it as an FTP directory listing.
+// Fills in |entries| and |server_type| and returns network error code.
+int DecodeAndParse(const std::string& text,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries,
+ FtpServerType* server_type) {
+ const char* kNewlineSeparators[] = { "\n", "\r\n" };
+
+ std::vector<std::string> encodings;
+ if (!base::DetectAllEncodings(text, &encodings))
+ return ERR_ENCODING_DETECTION_FAILED;
+
+ // Use first encoding that can be used to decode the text.
+ for (size_t i = 0; i < encodings.size(); i++) {
+ base::string16 converted_text;
+ if (base::CodepageToUTF16(text,
+ encodings[i].c_str(),
+ base::OnStringConversionError::FAIL,
+ &converted_text)) {
+ for (size_t j = 0; j < arraysize(kNewlineSeparators); j++) {
+ int rv = ParseListing(converted_text,
+ ASCIIToUTF16(kNewlineSeparators[j]),
+ encodings[i],
+ current_time,
+ entries,
+ server_type);
+ if (rv == OK)
+ return rv;
+ }
+ }
+ }
+
+ entries->clear();
+ *server_type = SERVER_UNKNOWN;
+ return ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT;
+}
+
+} // namespace
+
+FtpDirectoryListingEntry::FtpDirectoryListingEntry()
+ : type(UNKNOWN),
+ size(-1) {
+}
+
+int ParseFtpDirectoryListing(const std::string& text,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ FtpServerType server_type = SERVER_UNKNOWN;
+ int rv = DecodeAndParse(text, current_time, entries, &server_type);
+ UpdateFtpServerTypeHistograms(server_type);
+ return rv;
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser.h b/chromium/net/ftp/ftp_directory_listing_parser.h
new file mode 100644
index 00000000000..d2dd677bb09
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2011 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_DIRECTORY_LISTING_PARSER_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+struct FtpDirectoryListingEntry {
+ enum Type {
+ UNKNOWN,
+ FILE,
+ DIRECTORY,
+ SYMLINK,
+ };
+
+ FtpDirectoryListingEntry();
+
+ Type type;
+ base::string16 name; // Name (UTF-16-encoded).
+ std::string raw_name; // Name in original character encoding.
+ int64 size; // File size, in bytes. -1 if not applicable.
+
+ // Last modified time, in local time zone.
+ base::Time last_modified;
+};
+
+// Parses an FTP directory listing |text|. On success fills in |entries|.
+// Returns network error code.
+NET_EXPORT int ParseFtpDirectoryListing(
+ const std::string& text,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_H_
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_ls.cc b/chromium/net/ftp/ftp_directory_listing_parser_ls.cc
new file mode 100644
index 00000000000..41e29b60a9b
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_ls.cc
@@ -0,0 +1,233 @@
+// 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_directory_listing_parser_ls.h"
+
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "net/ftp/ftp_util.h"
+
+namespace {
+
+bool TwoColumnDateListingToTime(const base::string16& date,
+ const base::string16& time,
+ base::Time* result) {
+ base::Time::Exploded time_exploded = { 0 };
+
+ // Date should be in format YYYY-MM-DD.
+ std::vector<base::string16> date_parts;
+ base::SplitString(date, '-', &date_parts);
+ if (date_parts.size() != 3)
+ return false;
+ if (!base::StringToInt(date_parts[0], &time_exploded.year))
+ return false;
+ if (!base::StringToInt(date_parts[1], &time_exploded.month))
+ return false;
+ if (!base::StringToInt(date_parts[2], &time_exploded.day_of_month))
+ return false;
+
+ // Time should be in format HH:MM
+ if (time.length() != 5)
+ return false;
+
+ std::vector<base::string16> time_parts;
+ base::SplitString(time, ':', &time_parts);
+ if (time_parts.size() != 2)
+ return false;
+ if (!base::StringToInt(time_parts[0], &time_exploded.hour))
+ return false;
+ if (!base::StringToInt(time_parts[1], &time_exploded.minute))
+ return false;
+ if (!time_exploded.HasValidValues())
+ return false;
+
+ // We don't know the time zone of the server, so just use local time.
+ *result = base::Time::FromLocalExploded(time_exploded);
+ return true;
+}
+
+// Returns the column index of the end of the date listing and detected
+// last modification time.
+bool DetectColumnOffsetSizeAndModificationTime(
+ const std::vector<base::string16>& columns,
+ const base::Time& current_time,
+ size_t* offset,
+ base::string16* size,
+ base::Time* modification_time) {
+ // The column offset can be arbitrarily large if some fields
+ // like owner or group name contain spaces. Try offsets from left to right
+ // and use the first one that matches a date listing.
+ //
+ // Here is how a listing line should look like. A star ("*") indicates
+ // a required field:
+ //
+ // * 1. permission listing
+ // 2. number of links (optional)
+ // * 3. owner name (may contain spaces)
+ // 4. group name (optional, may contain spaces)
+ // * 5. size in bytes
+ // * 6. month
+ // * 7. day of month
+ // * 8. year or time <-- column_offset will be the index of this column
+ // 9. file name (optional, may contain spaces)
+ for (size_t i = 5U; i < columns.size(); i++) {
+ if (net::FtpUtil::LsDateListingToTime(columns[i - 2],
+ columns[i - 1],
+ columns[i],
+ current_time,
+ modification_time)) {
+ *size = columns[i - 3];
+ *offset = i;
+ return true;
+ }
+ }
+
+ // Some FTP listings have swapped the "month" and "day of month" columns
+ // (for example Russian listings). We try to recognize them only after making
+ // sure no column offset works above (this is a more strict way).
+ for (size_t i = 5U; i < columns.size(); i++) {
+ if (net::FtpUtil::LsDateListingToTime(columns[i - 1],
+ columns[i - 2],
+ columns[i],
+ current_time,
+ modification_time)) {
+ *size = columns[i - 3];
+ *offset = i;
+ return true;
+ }
+ }
+
+ // Some FTP listings use a different date format.
+ for (size_t i = 5U; i < columns.size(); i++) {
+ if (TwoColumnDateListingToTime(columns[i - 1],
+ columns[i],
+ modification_time)) {
+ *size = columns[i - 2];
+ *offset = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace
+
+namespace net {
+
+bool ParseFtpDirectoryListingLs(
+ const std::vector<base::string16>& lines,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ // True after we have received a "total n" listing header, where n is an
+ // integer. Only one such header is allowed per listing.
+ bool received_total_line = false;
+
+ for (size_t i = 0; i < lines.size(); i++) {
+ if (lines[i].empty())
+ continue;
+
+ std::vector<base::string16> columns;
+ base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
+
+ // Some FTP servers put a "total n" line at the beginning of the listing
+ // (n is an integer). Allow such a line, but only once, and only if it's
+ // the first non-empty line. Do not match the word exactly, because it may
+ // be in different languages (at least English and German have been seen
+ // in the field).
+ if (columns.size() == 2 && !received_total_line) {
+ received_total_line = true;
+
+ int64 total_number;
+ if (!base::StringToInt64(columns[1], &total_number))
+ return false;
+ if (total_number < 0)
+ return false;
+
+ continue;
+ }
+
+ FtpDirectoryListingEntry entry;
+
+ size_t column_offset;
+ base::string16 size;
+ if (!DetectColumnOffsetSizeAndModificationTime(columns,
+ current_time,
+ &column_offset,
+ &size,
+ &entry.last_modified)) {
+ // Some servers send a message in one of the first few lines.
+ // All those messages have in common is the string ".:",
+ // where "." means the current directory, and ":" separates it
+ // from the rest of the message, which may be empty.
+ if (lines[i].find(ASCIIToUTF16(".:")) != base::string16::npos)
+ continue;
+
+ return false;
+ }
+
+ // Do not check "validity" of the permission listing. It's quirky,
+ // and some servers send garbage here while other parts of the line are OK.
+
+ if (!columns[0].empty() && columns[0][0] == 'l') {
+ entry.type = FtpDirectoryListingEntry::SYMLINK;
+ } else if (!columns[0].empty() && columns[0][0] == 'd') {
+ entry.type = FtpDirectoryListingEntry::DIRECTORY;
+ } else {
+ entry.type = FtpDirectoryListingEntry::FILE;
+ }
+
+ if (!base::StringToInt64(size, &entry.size)) {
+ // Some FTP servers do not separate owning group name from file size,
+ // like "group1234". We still want to display the file name for that
+ // entry, but can't really get the size (What if the group is named
+ // "group1", and the size is in fact 234? We can't distinguish between
+ // that and "group" with size 1234). Use a dummy value for the size.
+ // TODO(phajdan.jr): Use a value that means "unknown" instead of 0 bytes.
+ entry.size = 0;
+ }
+ if (entry.size < 0) {
+ // Some FTP servers have bugs that cause them to display the file size
+ // as negative. They're most likely big files like DVD ISO images.
+ // We still want to display them, so just say the real file size
+ // is unknown.
+ entry.size = -1;
+ }
+ if (entry.type != FtpDirectoryListingEntry::FILE)
+ entry.size = -1;
+
+ if (column_offset == columns.size() - 1) {
+ // If the end of the date listing is the last column, there is no file
+ // name. Some FTP servers send listing entries with empty names.
+ // It's not obvious how to display such an entry, so we ignore them.
+ // We don't want to make the parsing fail at this point though.
+ // Other entries can still be useful.
+ continue;
+ }
+
+ entry.name = FtpUtil::GetStringPartAfterColumns(lines[i],
+ column_offset + 1);
+
+ if (entry.type == FtpDirectoryListingEntry::SYMLINK) {
+ base::string16::size_type pos = entry.name.rfind(ASCIIToUTF16(" -> "));
+
+ // We don't require the " -> " to be present. Some FTP servers don't send
+ // the symlink target, possibly for security reasons.
+ if (pos != base::string16::npos)
+ entry.name = entry.name.substr(0, pos);
+ }
+
+ entries->push_back(entry);
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_ls.h b/chromium/net/ftp/ftp_directory_listing_parser_ls.h
new file mode 100644
index 00000000000..9f7020f8ac9
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_ls.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 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_DIRECTORY_LISTING_PARSER_LS_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_LS_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class Time;
+}
+
+namespace net {
+
+struct FtpDirectoryListingEntry;
+
+// Parses "ls -l" FTP directory listing. Returns true on success.
+NET_EXPORT_PRIVATE bool ParseFtpDirectoryListingLs(
+ const std::vector<base::string16>& lines,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_LS_H_
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_ls_unittest.cc b/chromium/net/ftp/ftp_directory_listing_parser_ls_unittest.cc
new file mode 100644
index 00000000000..a7389443500
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_ls_unittest.cc
@@ -0,0 +1,218 @@
+// Copyright (c) 2011 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_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/ftp/ftp_directory_listing_parser_ls.h"
+
+namespace net {
+
+namespace {
+
+typedef FtpDirectoryListingParserTest FtpDirectoryListingParserLsTest;
+
+TEST_F(FtpDirectoryListingParserLsTest, Good) {
+ const struct SingleLineTestData good_cases[] = {
+ { "-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 README",
+ FtpDirectoryListingEntry::FILE, "README", 528,
+ 2007, 11, 1, 0, 0 },
+ { "drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 directory",
+ FtpDirectoryListingEntry::DIRECTORY, "directory", -1,
+ 1994, 5, 15, 18, 11 },
+ { "lrwxrwxrwx 1 0 0 26 Sep 18 2008 pub -> vol/1/.CLUSTER/var_ftp/pub",
+ FtpDirectoryListingEntry::SYMLINK, "pub", -1,
+ 2008, 9, 18, 0, 0 },
+ { "lrwxrwxrwx 1 0 0 3 Oct 12 13:37 mirror -> pub",
+ FtpDirectoryListingEntry::SYMLINK, "mirror", -1,
+ 1994, 10, 12, 13, 37 },
+ { "drwxrwsr-x 4 501 501 4096 Feb 20 2007 pub",
+ FtpDirectoryListingEntry::DIRECTORY, "pub", -1,
+ 2007, 2, 20, 0, 0 },
+ { "drwxr-xr-x 4 (?) (?) 4096 Apr 8 2007 jigdo",
+ FtpDirectoryListingEntry::DIRECTORY, "jigdo", -1,
+ 2007, 4, 8, 0, 0 },
+ { "drwx-wx-wt 2 root wheel 512 Jul 1 02:15 incoming",
+ FtpDirectoryListingEntry::DIRECTORY, "incoming", -1,
+ 1994, 7, 1, 2, 15 },
+ { "-rw-r--r-- 1 2 3 3447432 May 18 2009 Foo - Manual.pdf",
+ FtpDirectoryListingEntry::FILE, "Foo - Manual.pdf", 3447432,
+ 2009, 5, 18, 0, 0 },
+ { "d-wx-wx-wt+ 4 ftp 989 512 Dec 8 15:54 incoming",
+ FtpDirectoryListingEntry::DIRECTORY, "incoming", -1,
+ 1993, 12, 8, 15, 54 },
+ { "drwxrwxrwx 1 owner group 1024 Sep 13 0:30 audio",
+ FtpDirectoryListingEntry::DIRECTORY, "audio", -1,
+ 1994, 9, 13, 0, 30 },
+ { "lrwxrwxrwx 1 0 0 26 Sep 18 2008 pub",
+ FtpDirectoryListingEntry::SYMLINK, "pub", -1,
+ 2008, 9, 18, 0, 0 },
+ { "-rw-r--r-- 1 ftp ftp -528 Nov 01 2007 README",
+ FtpDirectoryListingEntry::FILE, "README", -1,
+ 2007, 11, 1, 0, 0 },
+
+ // Tests for the wu-ftpd variant:
+ { "drwxr-xr-x 2 sys 512 Mar 27 2009 pub",
+ FtpDirectoryListingEntry::DIRECTORY, "pub", -1,
+ 2009, 3, 27, 0, 0 },
+ { "lrwxrwxrwx 0 0 26 Sep 18 2008 pub -> vol/1/.CLUSTER/var_ftp/pub",
+ FtpDirectoryListingEntry::SYMLINK, "pub", -1,
+ 2008, 9, 18, 0, 0 },
+ { "drwxr-xr-x (?) (?) 4096 Apr 8 2007 jigdo",
+ FtpDirectoryListingEntry::DIRECTORY, "jigdo", -1,
+ 2007, 4, 8, 0, 0 },
+ { "-rw-r--r-- 2 3 3447432 May 18 2009 Foo - Manual.pdf",
+ FtpDirectoryListingEntry::FILE, "Foo - Manual.pdf", 3447432,
+ 2009, 5, 18, 0, 0 },
+
+ // Tests for "ls -l" style listings sent by an OS/2 server (FtpServer):
+ { "-r--r--r-- 1 ftp -A--- 13274 Mar 1 2006 UpTime.exe",
+ FtpDirectoryListingEntry::FILE, "UpTime.exe", 13274,
+ 2006, 3, 1, 0, 0 },
+ { "dr--r--r-- 1 ftp ----- 0 Nov 17 17:08 kernels",
+ FtpDirectoryListingEntry::DIRECTORY, "kernels", -1,
+ 1993, 11, 17, 17, 8 },
+
+ // Tests for "ls -l" style listing sent by Xplain FTP Server.
+ { "drwxr-xr-x folder 0 Jul 17 2006 online",
+ FtpDirectoryListingEntry::DIRECTORY, "online", -1,
+ 2006, 7, 17, 0, 0 },
+
+ // Tests for "ls -l" style listing with owning group name
+ // not separated from file size (http://crbug.com/58963).
+ { "-rw-r--r-- 1 ftpadmin ftpadmin125435904 Apr 9 2008 .pureftpd-upload",
+ FtpDirectoryListingEntry::FILE, ".pureftpd-upload", 0,
+ 2008, 4, 9, 0, 0 },
+
+ // Tests for "ls -l" style listing with number of links
+ // not separated from permission listing (http://crbug.com/70394).
+ { "drwxr-xr-x1732 266 111 90112 Jun 21 2001 .rda_2",
+ FtpDirectoryListingEntry::DIRECTORY, ".rda_2", -1,
+ 2001, 6, 21, 0, 0 },
+
+ // Tests for "ls -l" style listing with group name containing spaces.
+ { "drwxrwxr-x 3 %%%% Domain Users 4096 Dec 9 2009 %%%%%",
+ FtpDirectoryListingEntry::DIRECTORY, "%%%%%", -1,
+ 2009, 12, 9, 0, 0 },
+
+ // Tests for "ls -l" style listing in Russian locale (note the swapped
+ // parts order: the day of month is the first, before month).
+ { "-rwxrwxr-x 1 ftp ftp 123 23 \xd0\xbc\xd0\xb0\xd0\xb9 2011 test",
+ FtpDirectoryListingEntry::FILE, "test", 123,
+ 2011, 5, 23, 0, 0 },
+ { "drwxrwxr-x 1 ftp ftp 4096 19 \xd0\xbe\xd0\xba\xd1\x82 2011 dir",
+ FtpDirectoryListingEntry::DIRECTORY, "dir", -1,
+ 2011, 10, 19, 0, 0 },
+
+ // Plan9 sends entry type "a" for append-only files.
+ { "ar-xr-xr-x 2 none none 512 Apr 26 17:52 plan9",
+ FtpDirectoryListingEntry::FILE, "plan9", 512,
+ 1994, 4, 26, 17, 52 },
+
+ // Hylafax sends a shorter permission listing.
+ { "drwxrwx 2 10 4096 Jul 28 02:41 tmp",
+ FtpDirectoryListingEntry::DIRECTORY, "tmp", -1,
+ 1994, 7, 28, 2, 41 },
+
+ // Completely different date format (YYYY-MM-DD).
+ { "drwxrwxrwx 2 root root 4096 2012-02-07 00:31 notas_servico",
+ FtpDirectoryListingEntry::DIRECTORY, "notas_servico", -1,
+ 2012, 2, 7, 0, 31 },
+ { "-rwxrwxrwx 2 root root 4096 2012-02-07 00:31 notas_servico",
+ FtpDirectoryListingEntry::FILE, "notas_servico", 4096,
+ 2012, 2, 7, 0, 31 },
+
+ // Weird permission bits.
+ { "drwx--l--- 2 0 10 512 Dec 22 1994 swetzel",
+ FtpDirectoryListingEntry::DIRECTORY, "swetzel", -1,
+ 1994, 12, 22, 0, 0 },
+
+ // Garbage in date (but still parseable).
+ { "lrw-rw-rw- 1 user group 542 "
+ "/t11/member/incomingFeb 8 2007 "
+ "Shortcut to incoming.lnk -> /t11/member/incoming",
+ FtpDirectoryListingEntry::SYMLINK, "Shortcut to incoming.lnk", -1,
+ 2007, 2, 8, 0, 0 },
+
+ // Garbage in permissions (with no effect on other bits).
+ // Also test multiple "columns" resulting from the garbage.
+ { "garbage 1 ftp ftp 528 Nov 01 2007 README",
+ FtpDirectoryListingEntry::FILE, "README", 528,
+ 2007, 11, 1, 0, 0 },
+ { "gar bage 1 ftp ftp 528 Nov 01 2007 README",
+ FtpDirectoryListingEntry::FILE, "README", 528,
+ 2007, 11, 1, 0, 0 },
+ { "g a r b a g e 1 ftp ftp 528 Nov 01 2007 README",
+ FtpDirectoryListingEntry::FILE, "README", 528,
+ 2007, 11, 1, 0, 0 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ good_cases[i].input));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingLs(
+ GetSingleLineTestCase(good_cases[i].input),
+ GetMockCurrentTime(),
+ &entries));
+ VerifySingleLineTestCase(good_cases[i], entries);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserLsTest, Ignored) {
+ const char* ignored_cases[] = {
+ "drwxr-xr-x 2 0 0 4096 Mar 18 2007 ", // http://crbug.com/60065
+
+ "ftpd: .: Permission denied",
+ "ftpd-BSD: .: Permission denied",
+ "ls: .: EDC5111I Permission denied.",
+
+ // Tests important for security: verify that after we detect the column
+ // offset we don't try to access invalid memory on malformed input.
+ "drwxr-xr-x 3 ftp ftp 4096 May 15 18:11",
+ "drwxr-xr-x 3 ftp 4096 May 15 18:11",
+ "drwxr-xr-x folder 0 May 15 18:11",
+ };
+ for (size_t i = 0; i < arraysize(ignored_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ ignored_cases[i]));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingLs(
+ GetSingleLineTestCase(ignored_cases[i]),
+ GetMockCurrentTime(),
+ &entries));
+ EXPECT_EQ(0U, entries.size());
+ }
+}
+
+TEST_F(FtpDirectoryListingParserLsTest, Bad) {
+ const char* bad_cases[] = {
+ " foo",
+ "garbage",
+ "-rw-r--r-- ftp ftp",
+ "-rw-r--r-- ftp ftp 528 Foo 01 2007 README",
+ "-rw-r--r-- 1 ftp ftp",
+ "-rw-r--r-- 1 ftp ftp 528 Foo 01 2007 README",
+ "drwxrwxrwx 1 owner group 1024 Sep 13 0:3 audio",
+
+ // Invalid month value (30).
+ "drwxrwxrwx 2 root root 4096 2012-30-07 00:31 notas_servico",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ bad_cases[i]));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_FALSE(ParseFtpDirectoryListingLs(GetSingleLineTestCase(bad_cases[i]),
+ GetMockCurrentTime(),
+ &entries));
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_netware.cc b/chromium/net/ftp/ftp_directory_listing_parser_netware.cc
new file mode 100644
index 00000000000..2306513fa60
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_netware.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2011 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_directory_listing_parser_netware.h"
+
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "net/ftp/ftp_util.h"
+
+namespace {
+
+bool LooksLikeNetwarePermissionsListing(const base::string16& text) {
+ if (text.length() != 10)
+ return false;
+
+ if (text[0] != '[' || text[9] != ']')
+ return false;
+ return (text[1] == 'R' || text[1] == '-') &&
+ (text[2] == 'W' || text[2] == '-') &&
+ (text[3] == 'C' || text[3] == '-') &&
+ (text[4] == 'E' || text[4] == '-') &&
+ (text[5] == 'A' || text[5] == '-') &&
+ (text[6] == 'F' || text[6] == '-') &&
+ (text[7] == 'M' || text[7] == '-') &&
+ (text[8] == 'S' || text[8] == '-');
+}
+
+} // namespace
+
+namespace net {
+
+bool ParseFtpDirectoryListingNetware(
+ const std::vector<base::string16>& lines,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ if (!lines.empty() && !StartsWith(lines[0], ASCIIToUTF16("total "), true))
+ return false;
+
+ for (size_t i = 1U; i < lines.size(); i++) {
+ if (lines[i].empty())
+ continue;
+
+ std::vector<base::string16> columns;
+ base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
+
+ if (columns.size() < 8)
+ return false;
+
+ FtpDirectoryListingEntry entry;
+
+ if (columns[0].length() != 1)
+ return false;
+ if (columns[0][0] == 'd') {
+ entry.type = FtpDirectoryListingEntry::DIRECTORY;
+ } else if (columns[0][0] == '-') {
+ entry.type = FtpDirectoryListingEntry::FILE;
+ } else {
+ return false;
+ }
+
+ // Note: on older Netware systems the permissions listing is in the same
+ // column as the entry type (just there is no space between them). We do not
+ // support the older format here for simplicity.
+ if (!LooksLikeNetwarePermissionsListing(columns[1]))
+ return false;
+
+ if (!base::StringToInt64(columns[3], &entry.size))
+ return false;
+ if (entry.size < 0)
+ return false;
+ if (entry.type != FtpDirectoryListingEntry::FILE)
+ entry.size = -1;
+
+ // Netware uses the same date listing format as Unix "ls -l".
+ if (!FtpUtil::LsDateListingToTime(columns[4], columns[5], columns[6],
+ current_time, &entry.last_modified)) {
+ return false;
+ }
+
+ entry.name = FtpUtil::GetStringPartAfterColumns(lines[i], 7);
+
+ entries->push_back(entry);
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_netware.h b/chromium/net/ftp/ftp_directory_listing_parser_netware.h
new file mode 100644
index 00000000000..a70e21718d7
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_netware.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 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_DIRECTORY_LISTING_PARSER_NETWARE_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_NETWARE_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class Time;
+}
+
+namespace net {
+
+struct FtpDirectoryListingEntry;
+
+// Parses Netware FTP directory listing. Returns true on success.
+NET_EXPORT_PRIVATE bool ParseFtpDirectoryListingNetware(
+ const std::vector<base::string16>& lines,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_NETWARE_H_
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_netware_unittest.cc b/chromium/net/ftp/ftp_directory_listing_parser_netware_unittest.cc
new file mode 100644
index 00000000000..4863713ca86
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_netware_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2011 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_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/ftp/ftp_directory_listing_parser_netware.h"
+
+namespace net {
+
+namespace {
+
+typedef FtpDirectoryListingParserTest FtpDirectoryListingParserNetwareTest;
+
+TEST_F(FtpDirectoryListingParserNetwareTest, Good) {
+ const struct SingleLineTestData good_cases[] = {
+ { "d [RWCEAFMS] ftpadmin 512 Jan 29 2004 pub",
+ FtpDirectoryListingEntry::DIRECTORY, "pub", -1,
+ 2004, 1, 29, 0, 0 },
+ { "- [RW------] ftpadmin 123 Nov 11 18:25 afile",
+ FtpDirectoryListingEntry::FILE, "afile", 123,
+ 1994, 11, 11, 18, 25 },
+ { "d [RWCEAFMS] daniel 512 May 17 2010 NVP anyagok",
+ FtpDirectoryListingEntry::DIRECTORY, "NVP anyagok", -1,
+ 2010, 5, 17, 0, 0 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ good_cases[i].input));
+
+ std::vector<base::string16> lines(
+ GetSingleLineTestCase(good_cases[i].input));
+
+ // The parser requires a "total n" line before accepting regular input.
+ lines.insert(lines.begin(), ASCIIToUTF16("total 1"));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingNetware(lines,
+ GetMockCurrentTime(),
+ &entries));
+ VerifySingleLineTestCase(good_cases[i], entries);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserNetwareTest, Bad) {
+ const char* bad_cases[] = {
+ " foo",
+ "garbage",
+ "d [] ftpadmin 512 Jan 29 2004 pub",
+ "d [XGARBAGE] ftpadmin 512 Jan 29 2004 pub",
+ "d [RWCEAFMS] 512 Jan 29 2004 pub",
+ "d [RWCEAFMS] ftpadmin -1 Jan 29 2004 pub",
+ "l [RW------] ftpadmin 512 Jan 29 2004 pub",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ bad_cases[i]));
+
+ std::vector<base::string16> lines(GetSingleLineTestCase(bad_cases[i]));
+
+ // The parser requires a "total n" line before accepting regular input.
+ lines.insert(lines.begin(), ASCIIToUTF16("total 1"));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_FALSE(ParseFtpDirectoryListingNetware(lines,
+ GetMockCurrentTime(),
+ &entries));
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_os2.cc b/chromium/net/ftp/ftp_directory_listing_parser_os2.cc
new file mode 100644
index 00000000000..9a0cce53c81
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_os2.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2011 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_directory_listing_parser_os2.h"
+
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "net/ftp/ftp_util.h"
+
+namespace net {
+
+bool ParseFtpDirectoryListingOS2(
+ const std::vector<base::string16>& lines,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ for (size_t i = 0; i < lines.size(); i++) {
+ if (lines[i].empty())
+ continue;
+
+ std::vector<base::string16> columns;
+ base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
+
+ // Every line of the listing consists of the following:
+ //
+ // 1. size in bytes (0 for directories)
+ // 2. type (A for files, DIR for directories)
+ // 3. date
+ // 4. time
+ // 5. filename (may be empty or contain spaces)
+ //
+ // For now, make sure we have 1-4, and handle 5 later.
+ if (columns.size() < 4)
+ return false;
+
+ FtpDirectoryListingEntry entry;
+ if (!base::StringToInt64(columns[0], &entry.size))
+ return false;
+ if (EqualsASCII(columns[1], "DIR")) {
+ if (entry.size != 0)
+ return false;
+ entry.type = FtpDirectoryListingEntry::DIRECTORY;
+ entry.size = -1;
+ } else if (EqualsASCII(columns[1], "A")) {
+ entry.type = FtpDirectoryListingEntry::FILE;
+ if (entry.size < 0)
+ return false;
+ } else {
+ return false;
+ }
+
+ if (!FtpUtil::WindowsDateListingToTime(columns[2],
+ columns[3],
+ &entry.last_modified)) {
+ return false;
+ }
+
+ entry.name = FtpUtil::GetStringPartAfterColumns(lines[i], 4);
+ if (entry.name.empty()) {
+ // Some FTP servers send listing entries with empty names.
+ // It's not obvious how to display such an entry, so ignore them.
+ // We don't want to make the parsing fail at this point though.
+ // Other entries can still be useful.
+ continue;
+ }
+
+ entries->push_back(entry);
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_os2.h b/chromium/net/ftp/ftp_directory_listing_parser_os2.h
new file mode 100644
index 00000000000..9cd471fb0b1
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_os2.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 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_DIRECTORY_LISTING_PARSER_OS2_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_OS2_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+struct FtpDirectoryListingEntry;
+
+// Parses OS/2 FTP directory listing. Returns true on success.
+NET_EXPORT_PRIVATE bool ParseFtpDirectoryListingOS2(
+ const std::vector<base::string16>& lines,
+ std::vector<FtpDirectoryListingEntry>* entries);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_OS2_H_
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_os2_unittest.cc b/chromium/net/ftp/ftp_directory_listing_parser_os2_unittest.cc
new file mode 100644
index 00000000000..7302f4c9a9d
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_os2_unittest.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2011 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_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/ftp/ftp_directory_listing_parser_os2.h"
+
+namespace net {
+
+namespace {
+
+typedef FtpDirectoryListingParserTest FtpDirectoryListingParserOS2Test;
+
+TEST_F(FtpDirectoryListingParserOS2Test, Good) {
+ const struct SingleLineTestData good_cases[] = {
+ { "0 DIR 11-02-09 17:32 NT",
+ FtpDirectoryListingEntry::DIRECTORY, "NT", -1,
+ 2009, 11, 2, 17, 32 },
+ { "458 A 01-06-09 14:42 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 2009, 1, 6, 14, 42 },
+ { "1 A 01-06-09 02:42 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 1,
+ 2009, 1, 6, 2, 42 },
+ { "458 A 01-06-01 02:42 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 2001, 1, 6, 2, 42 },
+ { "458 A 01-06-00 02:42 Corner1.txt",
+ FtpDirectoryListingEntry::FILE, "Corner1.txt", 458,
+ 2000, 1, 6, 2, 42 },
+ { "458 A 01-06-99 02:42 Corner2.txt",
+ FtpDirectoryListingEntry::FILE, "Corner2.txt", 458,
+ 1999, 1, 6, 2, 42 },
+ { "458 A 01-06-80 02:42 Corner3.txt",
+ FtpDirectoryListingEntry::FILE, "Corner3.txt", 458,
+ 1980, 1, 6, 2, 42 },
+ { "458 A 01-06-1979 02:42 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 1979, 1, 6, 2, 42 },
+ { "0 DIR 11-02-09 17:32 My Directory",
+ FtpDirectoryListingEntry::DIRECTORY, "My Directory", -1,
+ 2009, 11, 2, 17, 32 },
+ { "0 DIR 12-25-10 00:00 Christmas Midnight",
+ FtpDirectoryListingEntry::DIRECTORY, "Christmas Midnight", -1,
+ 2010, 12, 25, 0, 0 },
+ { "0 DIR 12-25-10 12:00 Christmas Midday",
+ FtpDirectoryListingEntry::DIRECTORY, "Christmas Midday", -1,
+ 2010, 12, 25, 12, 0 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ good_cases[i].input));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingOS2(
+ GetSingleLineTestCase(good_cases[i].input),
+ &entries));
+ VerifySingleLineTestCase(good_cases[i], entries);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserOS2Test, Ignored) {
+ const char* ignored_cases[] = {
+ "1234 A 12-07-10 12:05",
+ "0 DIR 11-02-09 05:32",
+ };
+ for (size_t i = 0; i < arraysize(ignored_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ ignored_cases[i]));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingOS2(
+ GetSingleLineTestCase(ignored_cases[i]),
+ &entries));
+ EXPECT_EQ(0U, entries.size());
+ }
+}
+
+TEST_F(FtpDirectoryListingParserOS2Test, Bad) {
+ const char* bad_cases[] = {
+ "garbage",
+ "0 GARBAGE 11-02-09 05:32",
+ "0 GARBAGE 11-02-09 05:32 NT",
+ "0 DIR 11-FEB-09 05:32",
+ "0 DIR 11-02 05:32",
+ "-1 A 11-02-09 05:32",
+ "0 DIR 11-FEB-09 05:32",
+ "0 DIR 11-02 05:32 NT",
+ "-1 A 11-02-09 05:32 NT",
+ "0 A 99-25-10 12:00",
+ "0 A 12-99-10 12:00",
+ "0 A 12-25-10 99:00",
+ "0 A 12-25-10 12:99",
+ "0 A 99-25-10 12:00 months out of range",
+ "0 A 12-99-10 12:00 days out of range",
+ "0 A 12-25-10 99:00 hours out of range",
+ "0 A 12-25-10 12:99 minutes out of range",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ bad_cases[i]));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_FALSE(ParseFtpDirectoryListingOS2(
+ GetSingleLineTestCase(bad_cases[i]),
+ &entries));
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_unittest.cc b/chromium/net/ftp/ftp_directory_listing_parser_unittest.cc
new file mode 100644
index 00000000000..4eaf1dd97e4
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_unittest.cc
@@ -0,0 +1,166 @@
+// 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_directory_listing_parser.h"
+
+#include "base/file_util.h"
+#include "base/format_macros.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class FtpDirectoryListingParserTest
+ : public testing::TestWithParam<const char*> {
+};
+
+TEST_P(FtpDirectoryListingParserTest, Parse) {
+ base::FilePath test_dir;
+ PathService::Get(base::DIR_SOURCE_ROOT, &test_dir);
+ test_dir = test_dir.AppendASCII("net");
+ test_dir = test_dir.AppendASCII("data");
+ test_dir = test_dir.AppendASCII("ftp");
+
+ base::Time::Exploded mock_current_time_exploded = { 0 };
+ mock_current_time_exploded.year = 1994;
+ mock_current_time_exploded.month = 11;
+ mock_current_time_exploded.day_of_month = 15;
+ mock_current_time_exploded.hour = 12;
+ mock_current_time_exploded.minute = 45;
+ base::Time mock_current_time(
+ base::Time::FromLocalExploded(mock_current_time_exploded));
+
+ SCOPED_TRACE(base::StringPrintf("Test case: %s", GetParam()));
+
+ std::string test_listing;
+ EXPECT_TRUE(file_util::ReadFileToString(test_dir.AppendASCII(GetParam()),
+ &test_listing));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_EQ(OK, ParseFtpDirectoryListing(test_listing,
+ mock_current_time,
+ &entries));
+
+ std::string expected_listing;
+ ASSERT_TRUE(file_util::ReadFileToString(
+ test_dir.AppendASCII(std::string(GetParam()) + ".expected"),
+ &expected_listing));
+
+ std::vector<std::string> lines;
+ base::SplitStringUsingSubstr(expected_listing, "\r\n", &lines);
+
+ // Special case for empty listings.
+ if (lines.size() == 1 && lines[0].empty())
+ lines.clear();
+
+ ASSERT_EQ(9 * entries.size(), lines.size());
+
+ for (size_t i = 0; i < lines.size() / 9; i++) {
+ std::string type(lines[9 * i]);
+ std::string name(lines[9 * i + 1]);
+ int64 size;
+ base::StringToInt64(lines[9 * i + 2], &size);
+
+ SCOPED_TRACE(base::StringPrintf("Filename: %s", name.c_str()));
+
+ int year, month, day_of_month, hour, minute;
+ base::StringToInt(lines[9 * i + 3], &year);
+ base::StringToInt(lines[9 * i + 4], &month);
+ base::StringToInt(lines[9 * i + 5], &day_of_month);
+ base::StringToInt(lines[9 * i + 6], &hour);
+ base::StringToInt(lines[9 * i + 7], &minute);
+
+ const FtpDirectoryListingEntry& entry = entries[i];
+
+ if (type == "d") {
+ EXPECT_EQ(FtpDirectoryListingEntry::DIRECTORY, entry.type);
+ } else if (type == "-") {
+ EXPECT_EQ(FtpDirectoryListingEntry::FILE, entry.type);
+ } else if (type == "l") {
+ EXPECT_EQ(FtpDirectoryListingEntry::SYMLINK, entry.type);
+ } else {
+ ADD_FAILURE() << "invalid gold test data: " << type;
+ }
+
+ EXPECT_EQ(UTF8ToUTF16(name), entry.name);
+ EXPECT_EQ(size, entry.size);
+
+ base::Time::Exploded time_exploded;
+ entry.last_modified.LocalExplode(&time_exploded);
+ EXPECT_EQ(year, time_exploded.year);
+ EXPECT_EQ(month, time_exploded.month);
+ EXPECT_EQ(day_of_month, time_exploded.day_of_month);
+ EXPECT_EQ(hour, time_exploded.hour);
+ EXPECT_EQ(minute, time_exploded.minute);
+ }
+}
+
+const char* kTestFiles[] = {
+ "dir-listing-ls-1",
+ "dir-listing-ls-1-utf8",
+ "dir-listing-ls-2",
+ "dir-listing-ls-3",
+ "dir-listing-ls-4",
+ "dir-listing-ls-5",
+ "dir-listing-ls-6",
+ "dir-listing-ls-7",
+ "dir-listing-ls-8",
+ "dir-listing-ls-9",
+ "dir-listing-ls-10",
+ "dir-listing-ls-11",
+ "dir-listing-ls-12",
+ "dir-listing-ls-13",
+ "dir-listing-ls-14",
+ "dir-listing-ls-15",
+ "dir-listing-ls-16",
+ "dir-listing-ls-17",
+ "dir-listing-ls-18",
+ "dir-listing-ls-19",
+ "dir-listing-ls-20", // TODO(phajdan.jr): should use windows-1251 encoding.
+ "dir-listing-ls-21", // TODO(phajdan.jr): should use windows-1251 encoding.
+ "dir-listing-ls-22", // TODO(phajdan.jr): should use windows-1251 encoding.
+ "dir-listing-ls-23",
+ "dir-listing-ls-24",
+
+ // Tests for Russian listings. The only difference between those
+ // files is character encoding:
+ "dir-listing-ls-25", // UTF-8
+ "dir-listing-ls-26", // KOI8-R
+ "dir-listing-ls-27", // windows-1251
+
+ "dir-listing-ls-28", // Hylafax FTP server
+ "dir-listing-ls-29",
+ "dir-listing-ls-30",
+ "dir-listing-ls-31",
+
+ "dir-listing-netware-1",
+ "dir-listing-netware-2",
+ "dir-listing-netware-3", // Spaces in file names.
+ "dir-listing-os2-1",
+ "dir-listing-vms-1",
+ "dir-listing-vms-2",
+ "dir-listing-vms-3",
+ "dir-listing-vms-4",
+ "dir-listing-vms-5",
+ "dir-listing-vms-6",
+ "dir-listing-vms-7",
+ "dir-listing-vms-8",
+ "dir-listing-windows-1",
+ "dir-listing-windows-2",
+};
+
+INSTANTIATE_TEST_CASE_P(, FtpDirectoryListingParserTest,
+ testing::ValuesIn(kTestFiles));
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_unittest.h b/chromium/net/ftp/ftp_directory_listing_parser_unittest.h
new file mode 100644
index 00000000000..9ac42d867b5
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_unittest.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2011 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_DIRECTORY_LISTING_PARSER_UNITTEST_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_UNITTEST_H_
+
+#include <vector>
+
+#include "base/strings/utf_string_conversions.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+class FtpDirectoryListingParserTest : public testing::Test {
+ public:
+ struct SingleLineTestData {
+ const char* input;
+ FtpDirectoryListingEntry::Type type;
+ const char* filename;
+ int64 size;
+ int year;
+ int month;
+ int day_of_month;
+ int hour;
+ int minute;
+ };
+
+ protected:
+ FtpDirectoryListingParserTest() {}
+
+ std::vector<base::string16> GetSingleLineTestCase(const std::string& text) {
+ std::vector<base::string16> lines;
+ lines.push_back(UTF8ToUTF16(text));
+ return lines;
+ }
+
+ void VerifySingleLineTestCase(
+ const SingleLineTestData& test_case,
+ const std::vector<FtpDirectoryListingEntry>& entries) {
+ ASSERT_FALSE(entries.empty());
+
+ FtpDirectoryListingEntry entry = entries[0];
+ EXPECT_EQ(test_case.type, entry.type);
+ EXPECT_EQ(UTF8ToUTF16(test_case.filename), entry.name);
+ EXPECT_EQ(test_case.size, entry.size);
+
+ base::Time::Exploded time_exploded;
+ entry.last_modified.LocalExplode(&time_exploded);
+
+ // Only test members displayed on the directory listing.
+ EXPECT_EQ(test_case.year, time_exploded.year);
+ EXPECT_EQ(test_case.month, time_exploded.month);
+ EXPECT_EQ(test_case.day_of_month, time_exploded.day_of_month);
+ EXPECT_EQ(test_case.hour, time_exploded.hour);
+ EXPECT_EQ(test_case.minute, time_exploded.minute);
+
+ EXPECT_EQ(1U, entries.size());
+ }
+
+ base::Time GetMockCurrentTime() {
+ base::Time::Exploded mock_current_time_exploded = { 0 };
+ mock_current_time_exploded.year = 1994;
+ mock_current_time_exploded.month = 11;
+ mock_current_time_exploded.day_of_month = 15;
+ mock_current_time_exploded.hour = 12;
+ mock_current_time_exploded.minute = 45;
+ return base::Time::FromLocalExploded(mock_current_time_exploded);
+ }
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_UNITTEST_H_
+
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_vms.cc b/chromium/net/ftp/ftp_directory_listing_parser_vms.cc
new file mode 100644
index 00000000000..4b44d73b5b5
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_vms.cc
@@ -0,0 +1,293 @@
+// 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_directory_listing_parser_vms.h"
+
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "net/ftp/ftp_util.h"
+
+namespace net {
+
+namespace {
+
+// Converts the filename component in listing to the filename we can display.
+// Returns true on success.
+bool ParseVmsFilename(const base::string16& raw_filename,
+ base::string16* parsed_filename,
+ FtpDirectoryListingEntry::Type* type) {
+ // On VMS, the files and directories are versioned. The version number is
+ // separated from the file name by a semicolon. Example: ANNOUNCE.TXT;2.
+ std::vector<base::string16> listing_parts;
+ base::SplitString(raw_filename, ';', &listing_parts);
+ if (listing_parts.size() != 2)
+ return false;
+ int version_number;
+ if (!base::StringToInt(listing_parts[1], &version_number))
+ return false;
+ if (version_number < 0)
+ return false;
+
+ // Even directories have extensions in the listings. Don't display extensions
+ // for directories; it's awkward for non-VMS users. Also, VMS is
+ // case-insensitive, but generally uses uppercase characters. This may look
+ // awkward, so we convert them to lower case.
+ std::vector<base::string16> filename_parts;
+ base::SplitString(listing_parts[0], '.', &filename_parts);
+ if (filename_parts.size() != 2)
+ return false;
+ if (EqualsASCII(filename_parts[1], "DIR")) {
+ *parsed_filename = StringToLowerASCII(filename_parts[0]);
+ *type = FtpDirectoryListingEntry::DIRECTORY;
+ } else {
+ *parsed_filename = StringToLowerASCII(listing_parts[0]);
+ *type = FtpDirectoryListingEntry::FILE;
+ }
+ return true;
+}
+
+bool ParseVmsFilesize(const base::string16& input, int64* size) {
+ if (ContainsOnlyChars(input, ASCIIToUTF16("*"))) {
+ // Response consisting of asterisks means unknown size.
+ *size = -1;
+ return true;
+ }
+
+ // VMS's directory listing gives us file size in blocks. We assume that
+ // the block size is 512 bytes. It doesn't give accurate file size, but is the
+ // best information we have.
+ const int kBlockSize = 512;
+
+ if (base::StringToInt64(input, size)) {
+ if (*size < 0)
+ return false;
+ *size *= kBlockSize;
+ return true;
+ }
+
+ std::vector<base::string16> parts;
+ base::SplitString(input, '/', &parts);
+ if (parts.size() != 2)
+ return false;
+
+ int64 blocks_used, blocks_allocated;
+ if (!base::StringToInt64(parts[0], &blocks_used))
+ return false;
+ if (!base::StringToInt64(parts[1], &blocks_allocated))
+ return false;
+ if (blocks_used > blocks_allocated)
+ return false;
+ if (blocks_used < 0 || blocks_allocated < 0)
+ return false;
+
+ *size = blocks_used * kBlockSize;
+ return true;
+}
+
+bool LooksLikeVmsFileProtectionListingPart(const base::string16& input) {
+ if (input.length() > 4)
+ return false;
+
+ // On VMS there are four different permission bits: Read, Write, Execute,
+ // and Delete. They appear in that order in the permission listing.
+ std::string pattern("RWED");
+ base::string16 match(input);
+ while (!match.empty() && !pattern.empty()) {
+ if (match[0] == pattern[0])
+ match = match.substr(1);
+ pattern = pattern.substr(1);
+ }
+ return match.empty();
+}
+
+bool LooksLikeVmsFileProtectionListing(const base::string16& input) {
+ if (input.length() < 2)
+ return false;
+ if (input[0] != '(' || input[input.length() - 1] != ')')
+ return false;
+
+ // We expect four parts of the file protection listing: for System, Owner,
+ // Group, and World.
+ std::vector<base::string16> parts;
+ base::SplitString(input.substr(1, input.length() - 2), ',', &parts);
+ if (parts.size() != 4)
+ return false;
+
+ return LooksLikeVmsFileProtectionListingPart(parts[0]) &&
+ LooksLikeVmsFileProtectionListingPart(parts[1]) &&
+ LooksLikeVmsFileProtectionListingPart(parts[2]) &&
+ LooksLikeVmsFileProtectionListingPart(parts[3]);
+}
+
+bool LooksLikeVmsUserIdentificationCode(const base::string16& input) {
+ if (input.length() < 2)
+ return false;
+ return input[0] == '[' && input[input.length() - 1] == ']';
+}
+
+bool LooksLikeVMSError(const base::string16& text) {
+ static const char* kPermissionDeniedMessages[] = {
+ "%RMS-E-FNF", // File not found.
+ "%RMS-E-PRV", // Access denied.
+ "%SYSTEM-F-NOPRIV",
+ "privilege",
+ };
+
+ for (size_t i = 0; i < arraysize(kPermissionDeniedMessages); i++) {
+ if (text.find(ASCIIToUTF16(kPermissionDeniedMessages[i])) !=
+ base::string16::npos)
+ return true;
+ }
+
+ return false;
+}
+
+bool VmsDateListingToTime(const std::vector<base::string16>& columns,
+ base::Time* time) {
+ DCHECK_EQ(4U, columns.size());
+
+ base::Time::Exploded time_exploded = { 0 };
+
+ // Date should be in format DD-MMM-YYYY.
+ std::vector<base::string16> date_parts;
+ base::SplitString(columns[2], '-', &date_parts);
+ if (date_parts.size() != 3)
+ return false;
+ if (!base::StringToInt(date_parts[0], &time_exploded.day_of_month))
+ return false;
+ if (!FtpUtil::AbbreviatedMonthToNumber(date_parts[1],
+ &time_exploded.month))
+ return false;
+ if (!base::StringToInt(date_parts[2], &time_exploded.year))
+ return false;
+
+ // Time can be in format HH:MM, HH:MM:SS, or HH:MM:SS.mm. Try to recognize the
+ // last type first. Do not parse the seconds, they will be ignored anyway.
+ base::string16 time_column(columns[3]);
+ if (time_column.length() == 11 && time_column[8] == '.')
+ time_column = time_column.substr(0, 8);
+ if (time_column.length() == 8 && time_column[5] == ':')
+ time_column = time_column.substr(0, 5);
+ if (time_column.length() != 5)
+ return false;
+ std::vector<base::string16> time_parts;
+ base::SplitString(time_column, ':', &time_parts);
+ if (time_parts.size() != 2)
+ return false;
+ if (!base::StringToInt(time_parts[0], &time_exploded.hour))
+ return false;
+ if (!base::StringToInt(time_parts[1], &time_exploded.minute))
+ return false;
+
+ // We don't know the time zone of the server, so just use local time.
+ *time = base::Time::FromLocalExploded(time_exploded);
+ return true;
+}
+
+} // namespace
+
+bool ParseFtpDirectoryListingVms(
+ const std::vector<base::string16>& lines,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ // The first non-empty line is the listing header. It often
+ // starts with "Directory ", but not always. We set a flag after
+ // seing the header.
+ bool seen_header = false;
+
+ // Sometimes the listing doesn't end with a "Total" line, but
+ // it's only okay when it contains some errors (it's needed
+ // to distinguish it from "ls -l" format).
+ bool seen_error = false;
+
+ for (size_t i = 0; i < lines.size(); i++) {
+ if (lines[i].empty())
+ continue;
+
+ if (StartsWith(lines[i], ASCIIToUTF16("Total of "), true)) {
+ // After the "total" line, all following lines must be empty.
+ for (size_t j = i + 1; j < lines.size(); j++)
+ if (!lines[j].empty())
+ return false;
+
+ return true;
+ }
+
+ if (!seen_header) {
+ seen_header = true;
+ continue;
+ }
+
+ if (LooksLikeVMSError(lines[i])) {
+ seen_error = true;
+ continue;
+ }
+
+ std::vector<base::string16> columns;
+ base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
+
+ if (columns.size() == 1) {
+ // There can be no continuation if the current line is the last one.
+ if (i == lines.size() - 1)
+ return false;
+
+ // Skip the next line.
+ i++;
+
+ // This refers to the continuation line.
+ if (LooksLikeVMSError(lines[i])) {
+ seen_error = true;
+ continue;
+ }
+
+ // Join the current and next line and split them into columns.
+ base::SplitString(
+ CollapseWhitespace(lines[i - 1] + ASCIIToUTF16(" ") + lines[i],
+ false),
+ ' ',
+ &columns);
+ }
+
+ FtpDirectoryListingEntry entry;
+ if (!ParseVmsFilename(columns[0], &entry.name, &entry.type))
+ return false;
+
+ // There are different variants of a VMS listing. Some display
+ // the protection listing and user identification code, some do not.
+ if (columns.size() == 6) {
+ if (!LooksLikeVmsFileProtectionListing(columns[5]))
+ return false;
+ if (!LooksLikeVmsUserIdentificationCode(columns[4]))
+ return false;
+
+ // Drop the unneeded data, so that the following code can always expect
+ // just four columns.
+ columns.resize(4);
+ }
+
+ if (columns.size() != 4)
+ return false;
+
+ if (!ParseVmsFilesize(columns[1], &entry.size))
+ return false;
+ if (entry.type != FtpDirectoryListingEntry::FILE)
+ entry.size = -1;
+ if (!VmsDateListingToTime(columns, &entry.last_modified))
+ return false;
+
+ entries->push_back(entry);
+ }
+
+ // The only place where we return true is after receiving the "Total" line,
+ // that should be present in every VMS listing. Alternatively, if the listing
+ // contains error messages, it's OK not to have the "Total" line.
+ return seen_error;
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_vms.h b/chromium/net/ftp/ftp_directory_listing_parser_vms.h
new file mode 100644
index 00000000000..98e6a9275bd
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_vms.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 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_DIRECTORY_LISTING_PARSER_VMS_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_VMS_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+struct FtpDirectoryListingEntry;
+
+// Parses VMS FTP directory listing. Returns true on success.
+NET_EXPORT_PRIVATE bool ParseFtpDirectoryListingVms(
+ const std::vector<base::string16>& lines,
+ std::vector<FtpDirectoryListingEntry>* entries);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_VMS_H_
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_vms_unittest.cc b/chromium/net/ftp/ftp_directory_listing_parser_vms_unittest.cc
new file mode 100644
index 00000000000..3690f7e18c6
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_vms_unittest.cc
@@ -0,0 +1,168 @@
+// Copyright (c) 2011 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_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/ftp/ftp_directory_listing_parser_vms.h"
+
+namespace net {
+
+namespace {
+
+typedef FtpDirectoryListingParserTest FtpDirectoryListingParserVmsTest;
+
+TEST_F(FtpDirectoryListingParserVmsTest, Good) {
+ const struct SingleLineTestData good_cases[] = {
+ { "README.TXT;4 2 18-APR-2000 10:40:39.90",
+ FtpDirectoryListingEntry::FILE, "readme.txt", 1024,
+ 2000, 4, 18, 10, 40 },
+ { ".WELCOME;1 2 13-FEB-2002 23:32:40.47",
+ FtpDirectoryListingEntry::FILE, ".welcome", 1024,
+ 2002, 2, 13, 23, 32 },
+ { "FILE.;1 2 13-FEB-2002 23:32:40.47",
+ FtpDirectoryListingEntry::FILE, "file.", 1024,
+ 2002, 2, 13, 23, 32 },
+ { "EXAMPLE.TXT;1 1 4-NOV-2009 06:02 [JOHNDOE] (RWED,RWED,,)",
+ FtpDirectoryListingEntry::FILE, "example.txt", 512,
+ 2009, 11, 4, 6, 2 },
+ { "ANNOUNCE.TXT;2 1/16 12-MAR-2005 08:44:57 [SYSTEM] (RWED,RWED,RE,RE)",
+ FtpDirectoryListingEntry::FILE, "announce.txt", 512,
+ 2005, 3, 12, 8, 44 },
+ { "TEST.DIR;1 1 4-MAR-1999 22:14:34 [UCX$NOBO,ANONYMOUS] (RWE,RWE,RWE,RWE)",
+ FtpDirectoryListingEntry::DIRECTORY, "test", -1,
+ 1999, 3, 4, 22, 14 },
+ { "ANNOUNCE.TXT;2 1 12-MAR-2005 08:44:57 [X] (,,,)",
+ FtpDirectoryListingEntry::FILE, "announce.txt", 512,
+ 2005, 3, 12, 8, 44 },
+ { "ANNOUNCE.TXT;2 1 12-MAR-2005 08:44:57 [X] (R,RW,RWD,RE)",
+ FtpDirectoryListingEntry::FILE, "announce.txt", 512,
+ 2005, 3, 12, 8, 44 },
+ { "ANNOUNCE.TXT;2 1 12-MAR-2005 08:44:57 [X] (ED,RED,WD,WED)",
+ FtpDirectoryListingEntry::FILE, "announce.txt", 512,
+ 2005, 3, 12, 8, 44 },
+ { "VMS721.ISO;2 ****** 6-MAY-2008 09:29 [ANONY,ANONYMOUS] (RE,RWED,RE,RE)",
+ FtpDirectoryListingEntry::FILE, "vms721.iso", -1,
+ 2008, 5, 6, 9, 29 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ good_cases[i].input));
+
+ std::vector<base::string16> lines(
+ GetSingleLineTestCase(good_cases[i].input));
+
+ // The parser requires a directory header before accepting regular input.
+ lines.insert(lines.begin(),
+ ASCIIToUTF16("Directory ANONYMOUS_ROOT:[000000]"));
+
+ // A valid listing must also have a "Total" line at the end.
+ lines.insert(lines.end(),
+ ASCIIToUTF16("Total of 1 file, 2 blocks."));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingVms(lines,
+ &entries));
+ VerifySingleLineTestCase(good_cases[i], entries);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserVmsTest, Bad) {
+ const char* bad_cases[] = {
+ "garbage",
+
+ // Missing file version number.
+ "README.TXT 2 18-APR-2000 10:40:39",
+
+ // Missing extension.
+ "README;1 2 18-APR-2000 10:40:39",
+
+ // Malformed file size.
+ "README.TXT;1 garbage 18-APR-2000 10:40:39",
+ "README.TXT;1 -2 18-APR-2000 10:40:39",
+
+ // Malformed date.
+ "README.TXT;1 2 APR-2000 10:40:39",
+ "README.TXT;1 2 -18-APR-2000 10:40:39",
+ "README.TXT;1 2 18-APR 10:40:39",
+ "README.TXT;1 2 18-APR-2000 10",
+ "README.TXT;1 2 18-APR-2000 10:40.25",
+ "README.TXT;1 2 18-APR-2000 10:40.25.25",
+
+ // Malformed security information.
+ "X.TXT;2 1 12-MAR-2005 08:44:57 (RWED,RWED,RE,RE)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [SYSTEM]",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 (SYSTEM) (RWED,RWED,RE,RE)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [SYSTEM] [RWED,RWED,RE,RE]",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWED,RE,RE,RE)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWEDRWED,RE,RE)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,DEWR,RE,RE)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWED,Q,RE)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RRWWEEDD,RE,RE)",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i, bad_cases[i]));
+
+ std::vector<base::string16> lines(GetSingleLineTestCase(bad_cases[i]));
+
+ // The parser requires a directory header before accepting regular input.
+ lines.insert(lines.begin(),
+ ASCIIToUTF16("Directory ANONYMOUS_ROOT:[000000]"));
+
+ // A valid listing must also have a "Total" line at the end.
+ lines.insert(lines.end(),
+ ASCIIToUTF16("Total of 1 file, 2 blocks."));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_FALSE(ParseFtpDirectoryListingVms(lines,
+ &entries));
+ }
+}
+
+TEST_F(FtpDirectoryListingParserVmsTest, BadDataAfterFooter) {
+ const char* bad_cases[] = {
+ "garbage",
+ "Total of 1 file, 2 blocks.",
+ "Directory ANYNYMOUS_ROOT:[000000]",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i, bad_cases[i]));
+
+ std::vector<base::string16> lines(
+ GetSingleLineTestCase("README.TXT;4 2 18-APR-2000 10:40:39.90"));
+
+ // The parser requires a directory header before accepting regular input.
+ lines.insert(lines.begin(),
+ ASCIIToUTF16("Directory ANONYMOUS_ROOT:[000000]"));
+
+ // A valid listing must also have a "Total" line at the end.
+ lines.insert(lines.end(),
+ ASCIIToUTF16("Total of 1 file, 2 blocks."));
+
+ {
+ // Make sure the listing is valid before we add data after footer.
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingVms(lines,
+ &entries));
+ }
+
+ {
+ // Insert a line at the end of the listing that should make it invalid.
+ lines.insert(lines.end(),
+ ASCIIToUTF16(bad_cases[i]));
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_FALSE(ParseFtpDirectoryListingVms(lines,
+ &entries));
+ }
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_windows.cc b/chromium/net/ftp/ftp_directory_listing_parser_windows.cc
new file mode 100644
index 00000000000..5ec4a523434
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_windows.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2011 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_directory_listing_parser_windows.h"
+
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "net/ftp/ftp_util.h"
+
+namespace net {
+
+bool ParseFtpDirectoryListingWindows(
+ const std::vector<base::string16>& lines,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ for (size_t i = 0; i < lines.size(); i++) {
+ if (lines[i].empty())
+ continue;
+
+ std::vector<base::string16> columns;
+ base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
+
+ // Every line of the listing consists of the following:
+ //
+ // 1. date
+ // 2. time
+ // 3. size in bytes (or "<DIR>" for directories)
+ // 4. filename (may be empty or contain spaces)
+ //
+ // For now, make sure we have 1-3, and handle 4 later.
+ if (columns.size() < 3)
+ return false;
+
+ FtpDirectoryListingEntry entry;
+ if (EqualsASCII(columns[2], "<DIR>")) {
+ entry.type = FtpDirectoryListingEntry::DIRECTORY;
+ entry.size = -1;
+ } else {
+ entry.type = FtpDirectoryListingEntry::FILE;
+ if (!base::StringToInt64(columns[2], &entry.size))
+ return false;
+ if (entry.size < 0)
+ return false;
+ }
+
+ if (!FtpUtil::WindowsDateListingToTime(columns[0],
+ columns[1],
+ &entry.last_modified)) {
+ return false;
+ }
+
+ entry.name = FtpUtil::GetStringPartAfterColumns(lines[i], 3);
+ if (entry.name.empty()) {
+ // Some FTP servers send listing entries with empty names.
+ // It's not obvious how to display such an entry, so ignore them.
+ // We don't want to make the parsing fail at this point though.
+ // Other entries can still be useful.
+ continue;
+ }
+
+ entries->push_back(entry);
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_windows.h b/chromium/net/ftp/ftp_directory_listing_parser_windows.h
new file mode 100644
index 00000000000..e66f8e5d051
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_windows.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 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_DIRECTORY_LISTING_PARSER_WINDOWS_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_WINDOWS_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+struct FtpDirectoryListingEntry;
+
+// Parses Windows FTP directory listing. Returns true on success.
+NET_EXPORT_PRIVATE bool ParseFtpDirectoryListingWindows(
+ const std::vector<base::string16>& lines,
+ std::vector<FtpDirectoryListingEntry>* entries);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_WINDOWS_H_
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_windows_unittest.cc b/chromium/net/ftp/ftp_directory_listing_parser_windows_unittest.cc
new file mode 100644
index 00000000000..d3d47da1529
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_windows_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2011 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_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/ftp/ftp_directory_listing_parser_windows.h"
+
+namespace net {
+
+namespace {
+
+typedef FtpDirectoryListingParserTest FtpDirectoryListingParserWindowsTest;
+
+TEST_F(FtpDirectoryListingParserWindowsTest, Good) {
+ const struct SingleLineTestData good_cases[] = {
+ { "11-02-09 05:32PM <DIR> NT",
+ FtpDirectoryListingEntry::DIRECTORY, "NT", -1,
+ 2009, 11, 2, 17, 32 },
+ { "01-06-09 02:42PM 458 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 2009, 1, 6, 14, 42 },
+ { "01-06-09 02:42AM 1 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 1,
+ 2009, 1, 6, 2, 42 },
+ { "01-06-01 02:42AM 458 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 2001, 1, 6, 2, 42 },
+ { "01-06-00 02:42AM 458 Corner1.txt",
+ FtpDirectoryListingEntry::FILE, "Corner1.txt", 458,
+ 2000, 1, 6, 2, 42 },
+ { "01-06-99 02:42AM 458 Corner2.txt",
+ FtpDirectoryListingEntry::FILE, "Corner2.txt", 458,
+ 1999, 1, 6, 2, 42 },
+ { "01-06-80 02:42AM 458 Corner3.txt",
+ FtpDirectoryListingEntry::FILE, "Corner3.txt", 458,
+ 1980, 1, 6, 2, 42 },
+#if !defined(OS_LINUX) && !defined(OS_ANDROID)
+ // TODO(phajdan.jr): Re-enable when 2038-year problem is fixed on Linux.
+ { "01-06-79 02:42AM 458 Corner4",
+ FtpDirectoryListingEntry::FILE, "Corner4", 458,
+ 2079, 1, 6, 2, 42 },
+#endif // !defined (OS_LINUX) && !defined(OS_ANDROID)
+ { "01-06-1979 02:42AM 458 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 1979, 1, 6, 2, 42 },
+ { "11-02-09 05:32PM <DIR> My Directory",
+ FtpDirectoryListingEntry::DIRECTORY, "My Directory", -1,
+ 2009, 11, 2, 17, 32 },
+ { "12-25-10 12:00AM <DIR> Christmas Midnight",
+ FtpDirectoryListingEntry::DIRECTORY, "Christmas Midnight", -1,
+ 2010, 12, 25, 0, 0 },
+ { "12-25-10 12:00PM <DIR> Christmas Midday",
+ FtpDirectoryListingEntry::DIRECTORY, "Christmas Midday", -1,
+ 2010, 12, 25, 12, 0 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ good_cases[i].input));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingWindows(
+ GetSingleLineTestCase(good_cases[i].input),
+ &entries));
+ VerifySingleLineTestCase(good_cases[i], entries);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserWindowsTest, Ignored) {
+ const char* ignored_cases[] = {
+ "12-07-10 12:05AM <DIR> ", // http://crbug.com/66097
+ "12-07-10 12:05AM 1234 ",
+ "11-02-09 05:32 <DIR>",
+ "11-02-09 05:32PM <DIR>",
+ };
+ for (size_t i = 0; i < arraysize(ignored_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ ignored_cases[i]));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingWindows(
+ GetSingleLineTestCase(ignored_cases[i]),
+ &entries));
+ EXPECT_EQ(0U, entries.size());
+ }
+}
+
+TEST_F(FtpDirectoryListingParserWindowsTest, Bad) {
+ const char* bad_cases[] = {
+ "garbage",
+ "11-02-09 05:32PM <GARBAGE>",
+ "11-02-09 05:32PM <GARBAGE> NT",
+ "11-FEB-09 05:32PM <DIR>",
+ "11-02 05:32PM <DIR>",
+ "11-02-09 05:32PM -1",
+ "11-FEB-09 05:32PM <DIR> NT",
+ "11-02 05:32PM <DIR> NT",
+ "11-02-09 05:32PM -1 NT",
+ "99-25-10 12:00AM 0",
+ "12-99-10 12:00AM 0",
+ "12-25-10 99:00AM 0",
+ "12-25-10 12:99AM 0",
+ "12-25-10 12:00ZM 0",
+ "99-25-10 12:00AM 0 months out of range",
+ "12-99-10 12:00AM 0 days out of range",
+ "12-25-10 99:00AM 0 hours out of range",
+ "12-25-10 12:99AM 0 minutes out of range",
+ "12-25-10 12:00ZM 0 what does ZM mean",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ bad_cases[i]));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_FALSE(ParseFtpDirectoryListingWindows(
+ GetSingleLineTestCase(bad_cases[i]),
+ &entries));
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_network_layer.cc b/chromium/net/ftp/ftp_network_layer.cc
new file mode 100644
index 00000000000..098fb9fb0a6
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_layer.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2008 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_layer.h"
+
+#include "net/ftp/ftp_network_session.h"
+#include "net/ftp/ftp_network_transaction.h"
+#include "net/socket/client_socket_factory.h"
+
+namespace net {
+
+FtpNetworkLayer::FtpNetworkLayer(HostResolver* host_resolver)
+ : session_(new FtpNetworkSession(host_resolver)),
+ suspended_(false) {
+}
+
+FtpNetworkLayer::~FtpNetworkLayer() {
+}
+
+// static
+FtpTransactionFactory* FtpNetworkLayer::CreateFactory(
+ HostResolver* host_resolver) {
+ return new FtpNetworkLayer(host_resolver);
+}
+
+FtpTransaction* FtpNetworkLayer::CreateTransaction() {
+ if (suspended_)
+ return NULL;
+
+ return new FtpNetworkTransaction(session_.get(),
+ ClientSocketFactory::GetDefaultFactory());
+}
+
+void FtpNetworkLayer::Suspend(bool suspend) {
+ suspended_ = suspend;
+
+ /* TODO(darin): We'll need this code once we have a connection manager.
+ if (suspend)
+ session_->connection_manager()->CloseIdleSockets();
+ */
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_network_layer.h b/chromium/net/ftp/ftp_network_layer.h
new file mode 100644
index 00000000000..6040d029415
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_layer.h
@@ -0,0 +1,38 @@
+// 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.
+
+#ifndef NET_FTP_FTP_NETWORK_LAYER_H_
+#define NET_FTP_FTP_NETWORK_LAYER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+#include "net/ftp/ftp_transaction_factory.h"
+
+namespace net {
+
+class FtpNetworkSession;
+class HostResolver;
+
+class NET_EXPORT FtpNetworkLayer : public FtpTransactionFactory {
+ public:
+ explicit FtpNetworkLayer(HostResolver* host_resolver);
+ virtual ~FtpNetworkLayer();
+
+ static FtpTransactionFactory* CreateFactory(HostResolver* host_resolver);
+
+ // FtpTransactionFactory methods:
+ virtual FtpTransaction* CreateTransaction() OVERRIDE;
+ virtual void Suspend(bool suspend) OVERRIDE;
+
+ private:
+ scoped_refptr<FtpNetworkSession> session_;
+ bool suspended_;
+ DISALLOW_COPY_AND_ASSIGN(FtpNetworkLayer);
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_NETWORK_LAYER_H_
diff --git a/chromium/net/ftp/ftp_network_session.cc b/chromium/net/ftp/ftp_network_session.cc
new file mode 100644
index 00000000000..65dd218a4d4
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_session.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 2010 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_session.h"
+
+namespace net {
+
+FtpNetworkSession::FtpNetworkSession(HostResolver* host_resolver)
+ : host_resolver_(host_resolver) {}
+
+FtpNetworkSession::~FtpNetworkSession() {}
+
+} // namespace net
+
diff --git a/chromium/net/ftp/ftp_network_session.h b/chromium/net/ftp/ftp_network_session.h
new file mode 100644
index 00000000000..e92bb06643f
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_session.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011 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_NETWORK_SESSION_H_
+#define NET_FTP_FTP_NETWORK_SESSION_H_
+
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class HostResolver;
+
+// This class holds session objects used by FtpNetworkTransaction objects.
+class NET_EXPORT_PRIVATE FtpNetworkSession
+ : public base::RefCounted<FtpNetworkSession> {
+ public:
+ explicit FtpNetworkSession(HostResolver* host_resolver);
+
+ HostResolver* host_resolver() { return host_resolver_; }
+
+ private:
+ friend class base::RefCounted<FtpNetworkSession>;
+
+ virtual ~FtpNetworkSession();
+
+ HostResolver* const host_resolver_;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_NETWORK_SESSION_H_
diff --git a/chromium/net/ftp/ftp_network_transaction.cc b/chromium/net/ftp/ftp_network_transaction.cc
new file mode 100644
index 00000000000..f9f7b820168
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_transaction.cc
@@ -0,0 +1,1400 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/ftp/ftp_network_transaction.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "net/base/address_list.h"
+#include "net/base/connection_type_histograms.h"
+#include "net/base/escape.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/ftp/ftp_network_session.h"
+#include "net/ftp/ftp_request_info.h"
+#include "net/ftp/ftp_util.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/stream_socket.h"
+
+const char kCRLF[] = "\r\n";
+
+const int kCtrlBufLen = 1024;
+
+namespace {
+
+// Returns true if |input| can be safely used as a part of FTP command.
+bool IsValidFTPCommandString(const std::string& input) {
+ // RFC 959 only allows ASCII strings, but at least Firefox can send non-ASCII
+ // characters in the command if the request path contains them. To be
+ // compatible, we do the same and allow non-ASCII characters in a command.
+
+ // Protect agains newline injection attack.
+ if (input.find_first_of("\r\n") != std::string::npos)
+ return false;
+
+ return true;
+}
+
+enum ErrorClass {
+ // The requested action was initiated. The client should expect another
+ // reply before issuing the next command.
+ ERROR_CLASS_INITIATED,
+
+ // The requested action has been successfully completed.
+ ERROR_CLASS_OK,
+
+ // The command has been accepted, but to complete the operation, more
+ // information must be sent by the client.
+ ERROR_CLASS_INFO_NEEDED,
+
+ // The command was not accepted and the requested action did not take place.
+ // This condition is temporary, and the client is encouraged to restart the
+ // command sequence.
+ ERROR_CLASS_TRANSIENT_ERROR,
+
+ // The command was not accepted and the requested action did not take place.
+ // This condition is rather permanent, and the client is discouraged from
+ // repeating the exact request.
+ ERROR_CLASS_PERMANENT_ERROR,
+};
+
+// Returns the error class for given response code. Caller should ensure
+// that |response_code| is in range 100-599.
+ErrorClass GetErrorClass(int response_code) {
+ if (response_code >= 100 && response_code <= 199)
+ return ERROR_CLASS_INITIATED;
+
+ if (response_code >= 200 && response_code <= 299)
+ return ERROR_CLASS_OK;
+
+ if (response_code >= 300 && response_code <= 399)
+ return ERROR_CLASS_INFO_NEEDED;
+
+ if (response_code >= 400 && response_code <= 499)
+ return ERROR_CLASS_TRANSIENT_ERROR;
+
+ if (response_code >= 500 && response_code <= 599)
+ return ERROR_CLASS_PERMANENT_ERROR;
+
+ // We should not be called on invalid error codes.
+ NOTREACHED() << response_code;
+ return ERROR_CLASS_PERMANENT_ERROR;
+}
+
+// Returns network error code for received FTP |response_code|.
+int GetNetErrorCodeForFtpResponseCode(int response_code) {
+ switch (response_code) {
+ case 421:
+ return net::ERR_FTP_SERVICE_UNAVAILABLE;
+ case 426:
+ return net::ERR_FTP_TRANSFER_ABORTED;
+ case 450:
+ return net::ERR_FTP_FILE_BUSY;
+ case 500:
+ case 501:
+ return net::ERR_FTP_SYNTAX_ERROR;
+ case 502:
+ case 504:
+ return net::ERR_FTP_COMMAND_NOT_SUPPORTED;
+ case 503:
+ return net::ERR_FTP_BAD_COMMAND_SEQUENCE;
+ default:
+ return net::ERR_FTP_FAILED;
+ }
+}
+
+// From RFC 2428 Section 3:
+// The text returned in response to the EPSV command MUST be:
+// <some text> (<d><d><d><tcp-port><d>)
+// <d> is a delimiter character, ideally to be |
+bool ExtractPortFromEPSVResponse(const net::FtpCtrlResponse& response,
+ int* port) {
+ if (response.lines.size() != 1)
+ return false;
+ const char* ptr = response.lines[0].c_str();
+ while (*ptr && *ptr != '(')
+ ++ptr;
+ if (!*ptr)
+ return false;
+ char sep = *(++ptr);
+ if (!sep || isdigit(sep) || *(++ptr) != sep || *(++ptr) != sep)
+ return false;
+ if (!isdigit(*(++ptr)))
+ return false;
+ *port = *ptr - '0';
+ while (isdigit(*(++ptr))) {
+ *port *= 10;
+ *port += *ptr - '0';
+ }
+ if (*ptr != sep)
+ return false;
+
+ return true;
+}
+
+// There are two way we can receive IP address and port.
+// (127,0,0,1,23,21) IP address and port encapsulated in ().
+// 127,0,0,1,23,21 IP address and port without ().
+//
+// See RFC 959, Section 4.1.2
+bool ExtractPortFromPASVResponse(const net::FtpCtrlResponse& response,
+ int* port) {
+ if (response.lines.size() != 1)
+ return false;
+
+ std::string line(response.lines[0]);
+ if (!IsStringASCII(line))
+ return false;
+ if (line.length() < 2)
+ return false;
+
+ size_t paren_pos = line.find('(');
+ if (paren_pos == std::string::npos) {
+ // Find the first comma and use it to locate the beginning
+ // of the response data.
+ size_t comma_pos = line.find(',');
+ if (comma_pos == std::string::npos)
+ return false;
+
+ size_t space_pos = line.rfind(' ', comma_pos);
+ if (space_pos != std::string::npos)
+ line = line.substr(space_pos + 1);
+ } else {
+ // Remove the parentheses and use the text inside them.
+ size_t closing_paren_pos = line.rfind(')');
+ if (closing_paren_pos == std::string::npos)
+ return false;
+ if (closing_paren_pos <= paren_pos)
+ return false;
+
+ line = line.substr(paren_pos + 1, closing_paren_pos - paren_pos - 1);
+ }
+
+ // Split the line into comma-separated pieces and extract
+ // the last two.
+ std::vector<std::string> pieces;
+ base::SplitString(line, ',', &pieces);
+ if (pieces.size() != 6)
+ return false;
+
+ // Ignore the IP address supplied in the response. We are always going
+ // to connect back to the same server to prevent FTP PASV port scanning.
+ int p0, p1;
+ if (!base::StringToInt(pieces[4], &p0))
+ return false;
+ if (!base::StringToInt(pieces[5], &p1))
+ return false;
+ *port = (p0 << 8) + p1;
+
+ return true;
+}
+
+} // namespace
+
+namespace net {
+
+FtpNetworkTransaction::FtpNetworkTransaction(
+ FtpNetworkSession* session,
+ ClientSocketFactory* socket_factory)
+ : command_sent_(COMMAND_NONE),
+ io_callback_(base::Bind(&FtpNetworkTransaction::OnIOComplete,
+ base::Unretained(this))),
+ session_(session),
+ request_(NULL),
+ resolver_(session->host_resolver()),
+ read_ctrl_buf_(new IOBuffer(kCtrlBufLen)),
+ read_data_buf_len_(0),
+ last_error_(OK),
+ system_type_(SYSTEM_TYPE_UNKNOWN),
+ // Use image (binary) transfer by default. It should always work,
+ // whereas the ascii transfer may damage binary data.
+ data_type_(DATA_TYPE_IMAGE),
+ resource_type_(RESOURCE_TYPE_UNKNOWN),
+ use_epsv_(true),
+ data_connection_port_(0),
+ socket_factory_(socket_factory),
+ next_state_(STATE_NONE),
+ state_after_data_connect_complete_(STATE_CTRL_WRITE_SIZE) {}
+
+FtpNetworkTransaction::~FtpNetworkTransaction() {
+}
+
+int FtpNetworkTransaction::Stop(int error) {
+ if (command_sent_ == COMMAND_QUIT)
+ return error;
+
+ next_state_ = STATE_CTRL_WRITE_QUIT;
+ last_error_ = error;
+ return OK;
+}
+
+int FtpNetworkTransaction::RestartIgnoringLastError(
+ const CompletionCallback& callback) {
+ return ERR_NOT_IMPLEMENTED;
+}
+
+int FtpNetworkTransaction::Start(const FtpRequestInfo* request_info,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) {
+ net_log_ = net_log;
+ request_ = request_info;
+
+ ctrl_response_buffer_.reset(new FtpCtrlResponseBuffer(net_log_));
+
+ if (request_->url.has_username()) {
+ base::string16 username;
+ base::string16 password;
+ GetIdentityFromURL(request_->url, &username, &password);
+ credentials_.Set(username, password);
+ } else {
+ credentials_.Set(ASCIIToUTF16("anonymous"),
+ ASCIIToUTF16("chrome@example.com"));
+ }
+
+ DetectTypecode();
+
+ next_state_ = STATE_CTRL_RESOLVE_HOST;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int FtpNetworkTransaction::RestartWithAuth(const AuthCredentials& credentials,
+ const CompletionCallback& callback) {
+ ResetStateForRestart();
+
+ credentials_ = credentials;
+
+ next_state_ = STATE_CTRL_RESOLVE_HOST;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int FtpNetworkTransaction::Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(buf);
+ DCHECK_GT(buf_len, 0);
+
+ read_data_buf_ = buf;
+ read_data_buf_len_ = buf_len;
+
+ next_state_ = STATE_DATA_READ;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+const FtpResponseInfo* FtpNetworkTransaction::GetResponseInfo() const {
+ return &response_;
+}
+
+LoadState FtpNetworkTransaction::GetLoadState() const {
+ if (next_state_ == STATE_CTRL_RESOLVE_HOST_COMPLETE)
+ return LOAD_STATE_RESOLVING_HOST;
+
+ if (next_state_ == STATE_CTRL_CONNECT_COMPLETE ||
+ next_state_ == STATE_DATA_CONNECT_COMPLETE)
+ return LOAD_STATE_CONNECTING;
+
+ if (next_state_ == STATE_DATA_READ_COMPLETE)
+ return LOAD_STATE_READING_RESPONSE;
+
+ if (command_sent_ == COMMAND_RETR && read_data_buf_.get())
+ return LOAD_STATE_READING_RESPONSE;
+
+ if (command_sent_ == COMMAND_QUIT)
+ return LOAD_STATE_IDLE;
+
+ if (command_sent_ != COMMAND_NONE)
+ return LOAD_STATE_SENDING_REQUEST;
+
+ return LOAD_STATE_IDLE;
+}
+
+uint64 FtpNetworkTransaction::GetUploadProgress() const {
+ return 0;
+}
+
+void FtpNetworkTransaction::ResetStateForRestart() {
+ command_sent_ = COMMAND_NONE;
+ user_callback_.Reset();
+ response_ = FtpResponseInfo();
+ read_ctrl_buf_ = new IOBuffer(kCtrlBufLen);
+ ctrl_response_buffer_.reset(new FtpCtrlResponseBuffer(net_log_));
+ read_data_buf_ = NULL;
+ read_data_buf_len_ = 0;
+ if (write_buf_.get())
+ write_buf_->SetOffset(0);
+ last_error_ = OK;
+ data_connection_port_ = 0;
+ ctrl_socket_.reset();
+ data_socket_.reset();
+ next_state_ = STATE_NONE;
+ state_after_data_connect_complete_ = STATE_CTRL_WRITE_SIZE;
+}
+
+void FtpNetworkTransaction::ResetDataConnectionAfterError(State next_state) {
+ // The server _might_ have reset the data connection
+ // (see RFC 959 3.2. ESTABLISHING DATA CONNECTIONS:
+ // "The server MUST close the data connection under the following
+ // conditions:
+ // ...
+ // 5. An irrecoverable error condition occurs.")
+ //
+ // It is ambiguous what an irrecoverable error condition is,
+ // so we take no chances.
+ state_after_data_connect_complete_ = next_state;
+ next_state_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV;
+}
+
+void FtpNetworkTransaction::DoCallback(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(!user_callback_.is_null());
+
+ // Since Run may result in Read being called, clear callback_ up front.
+ CompletionCallback c = user_callback_;
+ user_callback_.Reset();
+ c.Run(rv);
+}
+
+void FtpNetworkTransaction::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+int FtpNetworkTransaction::ProcessCtrlResponse() {
+ FtpCtrlResponse response = ctrl_response_buffer_->PopResponse();
+
+ int rv = OK;
+ switch (command_sent_) {
+ case COMMAND_NONE:
+ // TODO(phajdan.jr): Check for errors in the welcome message.
+ next_state_ = STATE_CTRL_WRITE_USER;
+ break;
+ case COMMAND_USER:
+ rv = ProcessResponseUSER(response);
+ break;
+ case COMMAND_PASS:
+ rv = ProcessResponsePASS(response);
+ break;
+ case COMMAND_SYST:
+ rv = ProcessResponseSYST(response);
+ break;
+ case COMMAND_PWD:
+ rv = ProcessResponsePWD(response);
+ break;
+ case COMMAND_TYPE:
+ rv = ProcessResponseTYPE(response);
+ break;
+ case COMMAND_EPSV:
+ rv = ProcessResponseEPSV(response);
+ break;
+ case COMMAND_PASV:
+ rv = ProcessResponsePASV(response);
+ break;
+ case COMMAND_SIZE:
+ rv = ProcessResponseSIZE(response);
+ break;
+ case COMMAND_RETR:
+ rv = ProcessResponseRETR(response);
+ break;
+ case COMMAND_CWD:
+ rv = ProcessResponseCWD(response);
+ break;
+ case COMMAND_LIST:
+ rv = ProcessResponseLIST(response);
+ break;
+ case COMMAND_QUIT:
+ rv = ProcessResponseQUIT(response);
+ break;
+ default:
+ LOG(DFATAL) << "Unexpected value of command_sent_: " << command_sent_;
+ return ERR_UNEXPECTED;
+ }
+
+ // We may get multiple responses for some commands,
+ // see http://crbug.com/18036.
+ while (ctrl_response_buffer_->ResponseAvailable() && rv == OK) {
+ response = ctrl_response_buffer_->PopResponse();
+
+ switch (command_sent_) {
+ case COMMAND_RETR:
+ rv = ProcessResponseRETR(response);
+ break;
+ case COMMAND_LIST:
+ rv = ProcessResponseLIST(response);
+ break;
+ default:
+ // Multiple responses for other commands are invalid.
+ return Stop(ERR_INVALID_RESPONSE);
+ }
+ }
+
+ return rv;
+}
+
+// Used to prepare and send FTP command.
+int FtpNetworkTransaction::SendFtpCommand(const std::string& command,
+ const std::string& command_for_log,
+ Command cmd) {
+ // If we send a new command when we still have unprocessed responses
+ // for previous commands, the response receiving code will have no way to know
+ // which responses are for which command.
+ DCHECK(!ctrl_response_buffer_->ResponseAvailable());
+
+ DCHECK(!write_command_buf_.get());
+ DCHECK(!write_buf_.get());
+
+ if (!IsValidFTPCommandString(command)) {
+ // Callers should validate the command themselves and return a more specific
+ // error code.
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+
+ command_sent_ = cmd;
+
+ write_command_buf_ = new IOBufferWithSize(command.length() + 2);
+ write_buf_ = new DrainableIOBuffer(write_command_buf_.get(),
+ write_command_buf_->size());
+ memcpy(write_command_buf_->data(), command.data(), command.length());
+ memcpy(write_command_buf_->data() + command.length(), kCRLF, 2);
+
+ net_log_.AddEvent(NetLog::TYPE_FTP_COMMAND_SENT,
+ NetLog::StringCallback("command", &command_for_log));
+
+ next_state_ = STATE_CTRL_WRITE;
+ return OK;
+}
+
+std::string FtpNetworkTransaction::GetRequestPathForFtpCommand(
+ bool is_directory) const {
+ std::string path(current_remote_directory_);
+ if (request_->url.has_path()) {
+ std::string gurl_path(request_->url.path());
+
+ // Get rid of the typecode, see RFC 1738 section 3.2.2. FTP url-path.
+ std::string::size_type pos = gurl_path.rfind(';');
+ if (pos != std::string::npos)
+ gurl_path.resize(pos);
+
+ path.append(gurl_path);
+ }
+ // Make sure that if the path is expected to be a file, it won't end
+ // with a trailing slash.
+ if (!is_directory && path.length() > 1 && path[path.length() - 1] == '/')
+ path.erase(path.length() - 1);
+ UnescapeRule::Type unescape_rules = UnescapeRule::SPACES |
+ UnescapeRule::URL_SPECIAL_CHARS;
+ // This may unescape to non-ASCII characters, but we allow that. See the
+ // comment for IsValidFTPCommandString.
+ path = net::UnescapeURLComponent(path, unescape_rules);
+
+ if (system_type_ == SYSTEM_TYPE_VMS) {
+ if (is_directory)
+ path = FtpUtil::UnixDirectoryPathToVMS(path);
+ else
+ path = FtpUtil::UnixFilePathToVMS(path);
+ }
+
+ DCHECK(IsValidFTPCommandString(path));
+ return path;
+}
+
+void FtpNetworkTransaction::DetectTypecode() {
+ if (!request_->url.has_path())
+ return;
+ std::string gurl_path(request_->url.path());
+
+ // Extract the typecode, see RFC 1738 section 3.2.2. FTP url-path.
+ std::string::size_type pos = gurl_path.rfind(';');
+ if (pos == std::string::npos)
+ return;
+ std::string typecode_string(gurl_path.substr(pos));
+ if (typecode_string == ";type=a") {
+ data_type_ = DATA_TYPE_ASCII;
+ resource_type_ = RESOURCE_TYPE_FILE;
+ } else if (typecode_string == ";type=i") {
+ data_type_ = DATA_TYPE_IMAGE;
+ resource_type_ = RESOURCE_TYPE_FILE;
+ } else if (typecode_string == ";type=d") {
+ resource_type_ = RESOURCE_TYPE_DIRECTORY;
+ }
+}
+
+int FtpNetworkTransaction::DoLoop(int result) {
+ DCHECK(next_state_ != STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_CTRL_RESOLVE_HOST:
+ DCHECK(rv == OK);
+ rv = DoCtrlResolveHost();
+ break;
+ case STATE_CTRL_RESOLVE_HOST_COMPLETE:
+ rv = DoCtrlResolveHostComplete(rv);
+ break;
+ case STATE_CTRL_CONNECT:
+ DCHECK(rv == OK);
+ rv = DoCtrlConnect();
+ break;
+ case STATE_CTRL_CONNECT_COMPLETE:
+ rv = DoCtrlConnectComplete(rv);
+ break;
+ case STATE_CTRL_READ:
+ DCHECK(rv == OK);
+ rv = DoCtrlRead();
+ break;
+ case STATE_CTRL_READ_COMPLETE:
+ rv = DoCtrlReadComplete(rv);
+ break;
+ case STATE_CTRL_WRITE:
+ DCHECK(rv == OK);
+ rv = DoCtrlWrite();
+ break;
+ case STATE_CTRL_WRITE_COMPLETE:
+ rv = DoCtrlWriteComplete(rv);
+ break;
+ case STATE_CTRL_WRITE_USER:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteUSER();
+ break;
+ case STATE_CTRL_WRITE_PASS:
+ DCHECK(rv == OK);
+ rv = DoCtrlWritePASS();
+ break;
+ case STATE_CTRL_WRITE_SYST:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteSYST();
+ break;
+ case STATE_CTRL_WRITE_PWD:
+ DCHECK(rv == OK);
+ rv = DoCtrlWritePWD();
+ break;
+ case STATE_CTRL_WRITE_TYPE:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteTYPE();
+ break;
+ case STATE_CTRL_WRITE_EPSV:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteEPSV();
+ break;
+ case STATE_CTRL_WRITE_PASV:
+ DCHECK(rv == OK);
+ rv = DoCtrlWritePASV();
+ break;
+ case STATE_CTRL_WRITE_RETR:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteRETR();
+ break;
+ case STATE_CTRL_WRITE_SIZE:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteSIZE();
+ break;
+ case STATE_CTRL_WRITE_CWD:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteCWD();
+ break;
+ case STATE_CTRL_WRITE_LIST:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteLIST();
+ break;
+ case STATE_CTRL_WRITE_QUIT:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteQUIT();
+ break;
+ case STATE_DATA_CONNECT:
+ DCHECK(rv == OK);
+ rv = DoDataConnect();
+ break;
+ case STATE_DATA_CONNECT_COMPLETE:
+ rv = DoDataConnectComplete(rv);
+ break;
+ case STATE_DATA_READ:
+ DCHECK(rv == OK);
+ rv = DoDataRead();
+ break;
+ case STATE_DATA_READ_COMPLETE:
+ rv = DoDataReadComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+ return rv;
+}
+
+int FtpNetworkTransaction::DoCtrlResolveHost() {
+ next_state_ = STATE_CTRL_RESOLVE_HOST_COMPLETE;
+
+ HostResolver::RequestInfo info(HostPortPair::FromURL(request_->url));
+ // No known referrer.
+ return resolver_.Resolve(
+ info, &addresses_,
+ base::Bind(&FtpNetworkTransaction::OnIOComplete, base::Unretained(this)),
+ net_log_);
+}
+
+int FtpNetworkTransaction::DoCtrlResolveHostComplete(int result) {
+ if (result == OK)
+ next_state_ = STATE_CTRL_CONNECT;
+ return result;
+}
+
+int FtpNetworkTransaction::DoCtrlConnect() {
+ next_state_ = STATE_CTRL_CONNECT_COMPLETE;
+ ctrl_socket_ = socket_factory_->CreateTransportClientSocket(
+ addresses_, net_log_.net_log(), net_log_.source());
+ net_log_.AddEvent(
+ NetLog::TYPE_FTP_CONTROL_CONNECTION,
+ ctrl_socket_->NetLog().source().ToEventParametersCallback());
+ return ctrl_socket_->Connect(io_callback_);
+}
+
+int FtpNetworkTransaction::DoCtrlConnectComplete(int result) {
+ if (result == OK) {
+ // Put the peer's IP address and port into the response.
+ IPEndPoint ip_endpoint;
+ result = ctrl_socket_->GetPeerAddress(&ip_endpoint);
+ if (result == OK) {
+ response_.socket_address = HostPortPair::FromIPEndPoint(ip_endpoint);
+ next_state_ = STATE_CTRL_READ;
+
+ if (ip_endpoint.GetFamily() == ADDRESS_FAMILY_IPV4) {
+ // Do not use EPSV for IPv4 connections. Some servers become confused
+ // and we time out while waiting to connect. PASV is perfectly fine for
+ // IPv4. Note that this blacklists IPv4 not to use EPSV instead of
+ // whitelisting IPv6 to use it, to make the code more future-proof:
+ // all future protocols should just use EPSV.
+ use_epsv_ = false;
+ }
+ }
+ }
+ return result;
+}
+
+int FtpNetworkTransaction::DoCtrlRead() {
+ next_state_ = STATE_CTRL_READ_COMPLETE;
+ return ctrl_socket_->Read(read_ctrl_buf_.get(), kCtrlBufLen, io_callback_);
+}
+
+int FtpNetworkTransaction::DoCtrlReadComplete(int result) {
+ if (result == 0) {
+ // Some servers (for example Pure-FTPd) apparently close the control
+ // connection when anonymous login is not permitted. For more details
+ // see http://crbug.com/25023.
+ if (command_sent_ == COMMAND_USER &&
+ credentials_.username() == ASCIIToUTF16("anonymous")) {
+ response_.needs_auth = true;
+ }
+ return Stop(ERR_EMPTY_RESPONSE);
+ }
+ if (result < 0)
+ return Stop(result);
+
+ ctrl_response_buffer_->ConsumeData(read_ctrl_buf_->data(), result);
+
+ if (!ctrl_response_buffer_->ResponseAvailable()) {
+ // Read more data from the control socket.
+ next_state_ = STATE_CTRL_READ;
+ return OK;
+ }
+
+ return ProcessCtrlResponse();
+}
+
+int FtpNetworkTransaction::DoCtrlWrite() {
+ next_state_ = STATE_CTRL_WRITE_COMPLETE;
+
+ return ctrl_socket_->Write(
+ write_buf_.get(), write_buf_->BytesRemaining(), io_callback_);
+}
+
+int FtpNetworkTransaction::DoCtrlWriteComplete(int result) {
+ if (result < 0)
+ return result;
+
+ write_buf_->DidConsume(result);
+ if (write_buf_->BytesRemaining() == 0) {
+ // Clear the write buffer.
+ write_buf_ = NULL;
+ write_command_buf_ = NULL;
+
+ next_state_ = STATE_CTRL_READ;
+ } else {
+ next_state_ = STATE_CTRL_WRITE;
+ }
+ return OK;
+}
+
+// FTP Commands and responses
+
+// USER Command.
+int FtpNetworkTransaction::DoCtrlWriteUSER() {
+ std::string command = "USER " + UTF16ToUTF8(credentials_.username());
+
+ if (!IsValidFTPCommandString(command))
+ return Stop(ERR_MALFORMED_IDENTITY);
+
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, "USER ***", COMMAND_USER);
+}
+
+int FtpNetworkTransaction::ProcessResponseUSER(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_OK:
+ next_state_ = STATE_CTRL_WRITE_SYST;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ next_state_ = STATE_CTRL_WRITE_PASS;
+ break;
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ case ERROR_CLASS_PERMANENT_ERROR:
+ response_.needs_auth = true;
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// PASS command.
+int FtpNetworkTransaction::DoCtrlWritePASS() {
+ std::string command = "PASS " + UTF16ToUTF8(credentials_.password());
+
+ if (!IsValidFTPCommandString(command))
+ return Stop(ERR_MALFORMED_IDENTITY);
+
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, "PASS ***", COMMAND_PASS);
+}
+
+int FtpNetworkTransaction::ProcessResponsePASS(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_OK:
+ next_state_ = STATE_CTRL_WRITE_SYST;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ case ERROR_CLASS_PERMANENT_ERROR:
+ response_.needs_auth = true;
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// SYST command.
+int FtpNetworkTransaction::DoCtrlWriteSYST() {
+ std::string command = "SYST";
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_SYST);
+}
+
+int FtpNetworkTransaction::ProcessResponseSYST(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_OK: {
+ // All important info should be on the first line.
+ std::string line = response.lines[0];
+ // The response should be ASCII, which allows us to do case-insensitive
+ // comparisons easily. If it is not ASCII, we leave the system type
+ // as unknown.
+ if (IsStringASCII(line)) {
+ line = StringToLowerASCII(line);
+
+ // Remove all whitespace, to correctly handle cases like fancy "V M S"
+ // response instead of "VMS".
+ RemoveChars(line, kWhitespaceASCII, &line);
+
+ // The "magic" strings we test for below have been gathered by an
+ // empirical study. VMS needs to come first because some VMS systems
+ // also respond with "UNIX emulation", which is not perfect. It is much
+ // more reliable to talk to these servers in their native language.
+ if (line.find("vms") != std::string::npos) {
+ system_type_ = SYSTEM_TYPE_VMS;
+ } else if (line.find("l8") != std::string::npos ||
+ line.find("unix") != std::string::npos ||
+ line.find("bsd") != std::string::npos) {
+ system_type_ = SYSTEM_TYPE_UNIX;
+ } else if (line.find("win32") != std::string::npos ||
+ line.find("windows") != std::string::npos) {
+ system_type_ = SYSTEM_TYPE_WINDOWS;
+ } else if (line.find("os/2") != std::string::npos) {
+ system_type_ = SYSTEM_TYPE_OS2;
+ }
+ }
+ next_state_ = STATE_CTRL_WRITE_PWD;
+ break;
+ }
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ // Server does not recognize the SYST command so proceed.
+ next_state_ = STATE_CTRL_WRITE_PWD;
+ break;
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// PWD command.
+int FtpNetworkTransaction::DoCtrlWritePWD() {
+ std::string command = "PWD";
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_PWD);
+}
+
+int FtpNetworkTransaction::ProcessResponsePWD(const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_OK: {
+ // The info we look for should be on the first line.
+ std::string line = response.lines[0];
+ if (line.empty())
+ return Stop(ERR_INVALID_RESPONSE);
+ std::string::size_type quote_pos = line.find('"');
+ if (quote_pos != std::string::npos) {
+ line = line.substr(quote_pos + 1);
+ quote_pos = line.find('"');
+ if (quote_pos == std::string::npos)
+ return Stop(ERR_INVALID_RESPONSE);
+ line = line.substr(0, quote_pos);
+ }
+ if (system_type_ == SYSTEM_TYPE_VMS)
+ line = FtpUtil::VMSPathToUnix(line);
+ if (line.length() && line[line.length() - 1] == '/')
+ line.erase(line.length() - 1);
+ current_remote_directory_ = line;
+ next_state_ = STATE_CTRL_WRITE_TYPE;
+ break;
+ }
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// TYPE command.
+int FtpNetworkTransaction::DoCtrlWriteTYPE() {
+ std::string command = "TYPE ";
+ if (data_type_ == DATA_TYPE_ASCII) {
+ command += "A";
+ } else if (data_type_ == DATA_TYPE_IMAGE) {
+ command += "I";
+ } else {
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_TYPE);
+}
+
+int FtpNetworkTransaction::ProcessResponseTYPE(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_OK:
+ next_state_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// EPSV command
+int FtpNetworkTransaction::DoCtrlWriteEPSV() {
+ const std::string command = "EPSV";
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_EPSV);
+}
+
+int FtpNetworkTransaction::ProcessResponseEPSV(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_OK:
+ if (!ExtractPortFromEPSVResponse( response, &data_connection_port_))
+ return Stop(ERR_INVALID_RESPONSE);
+ if (data_connection_port_ < 1024 ||
+ !IsPortAllowedByFtp(data_connection_port_))
+ return Stop(ERR_UNSAFE_PORT);
+ next_state_ = STATE_DATA_CONNECT;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ case ERROR_CLASS_PERMANENT_ERROR:
+ use_epsv_ = false;
+ next_state_ = STATE_CTRL_WRITE_PASV;
+ return OK;
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// PASV command
+int FtpNetworkTransaction::DoCtrlWritePASV() {
+ std::string command = "PASV";
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_PASV);
+}
+
+int FtpNetworkTransaction::ProcessResponsePASV(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_OK:
+ if (!ExtractPortFromPASVResponse(response, &data_connection_port_))
+ return Stop(ERR_INVALID_RESPONSE);
+ if (data_connection_port_ < 1024 ||
+ !IsPortAllowedByFtp(data_connection_port_))
+ return Stop(ERR_UNSAFE_PORT);
+ next_state_ = STATE_DATA_CONNECT;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// RETR command
+int FtpNetworkTransaction::DoCtrlWriteRETR() {
+ std::string command = "RETR " + GetRequestPathForFtpCommand(false);
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_RETR);
+}
+
+int FtpNetworkTransaction::ProcessResponseRETR(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ // We want the client to start reading the response at this point.
+ // It got here either through Start or RestartWithAuth. We want that
+ // method to complete. Not setting next state here will make DoLoop exit
+ // and in turn make Start/RestartWithAuth complete.
+ resource_type_ = RESOURCE_TYPE_FILE;
+ break;
+ case ERROR_CLASS_OK:
+ resource_type_ = RESOURCE_TYPE_FILE;
+ next_state_ = STATE_CTRL_WRITE_QUIT;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ // Code 550 means "Failed to open file". Other codes are unrelated,
+ // like "Not logged in" etc.
+ if (response.status_code != 550 || resource_type_ == RESOURCE_TYPE_FILE)
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+
+ // It's possible that RETR failed because the path is a directory.
+ resource_type_ = RESOURCE_TYPE_DIRECTORY;
+
+ // We're going to try CWD next, but first send a PASV one more time,
+ // because some FTP servers, including FileZilla, require that.
+ // See http://crbug.com/25316.
+ next_state_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV;
+ break;
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+
+ // We should be sure about our resource type now. Otherwise we risk
+ // an infinite loop (RETR can later send CWD, and CWD can later send RETR).
+ DCHECK_NE(RESOURCE_TYPE_UNKNOWN, resource_type_);
+
+ return OK;
+}
+
+// SIZE command
+int FtpNetworkTransaction::DoCtrlWriteSIZE() {
+ std::string command = "SIZE " + GetRequestPathForFtpCommand(false);
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_SIZE);
+}
+
+int FtpNetworkTransaction::ProcessResponseSIZE(
+ const FtpCtrlResponse& response) {
+ State state_after_size;
+ if (resource_type_ == RESOURCE_TYPE_FILE)
+ state_after_size = STATE_CTRL_WRITE_RETR;
+ else
+ state_after_size = STATE_CTRL_WRITE_CWD;
+
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ next_state_ = state_after_size;
+ break;
+ case ERROR_CLASS_OK:
+ if (response.lines.size() != 1)
+ return Stop(ERR_INVALID_RESPONSE);
+ int64 size;
+ if (!base::StringToInt64(response.lines[0], &size))
+ return Stop(ERR_INVALID_RESPONSE);
+ if (size < 0)
+ return Stop(ERR_INVALID_RESPONSE);
+
+ // A successful response to SIZE does not mean the resource is a file.
+ // Some FTP servers (for example, the qnx one) send a SIZE even for
+ // directories.
+ response_.expected_content_size = size;
+
+ next_state_ = state_after_size;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ next_state_ = state_after_size;
+ break;
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ ResetDataConnectionAfterError(state_after_size);
+ break;
+ case ERROR_CLASS_PERMANENT_ERROR:
+ // It's possible that SIZE failed because the path is a directory.
+ if (resource_type_ == RESOURCE_TYPE_UNKNOWN &&
+ response.status_code != 550) {
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ }
+
+ ResetDataConnectionAfterError(state_after_size);
+ break;
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+
+ return OK;
+}
+
+// CWD command
+int FtpNetworkTransaction::DoCtrlWriteCWD() {
+ std::string command = "CWD " + GetRequestPathForFtpCommand(true);
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_CWD);
+}
+
+int FtpNetworkTransaction::ProcessResponseCWD(const FtpCtrlResponse& response) {
+ // We should never issue CWD if we know the target resource is a file.
+ DCHECK_NE(RESOURCE_TYPE_FILE, resource_type_);
+
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_OK:
+ next_state_ = STATE_CTRL_WRITE_LIST;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ // Some FTP servers send response 451 (not a valid CWD response according
+ // to RFC 959) instead of 550.
+ if (response.status_code == 451)
+ return ProcessResponseCWDNotADirectory();
+
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ if (response.status_code == 550)
+ return ProcessResponseCWDNotADirectory();
+
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+
+ return OK;
+}
+
+int FtpNetworkTransaction::ProcessResponseCWDNotADirectory() {
+ if (resource_type_ == RESOURCE_TYPE_DIRECTORY) {
+ // We're assuming that the resource is a directory, but the server
+ // says it's not true. The most probable interpretation is that it
+ // doesn't exist (with FTP we can't be sure).
+ return Stop(ERR_FILE_NOT_FOUND);
+ }
+
+ // We are here because SIZE failed and we are not sure what the resource
+ // type is. It could still be file, and SIZE could fail because of
+ // an access error (http://crbug.com/56734). Try RETR just to be sure.
+ resource_type_ = RESOURCE_TYPE_FILE;
+
+ ResetDataConnectionAfterError(STATE_CTRL_WRITE_RETR);
+ return OK;
+}
+
+// LIST command
+int FtpNetworkTransaction::DoCtrlWriteLIST() {
+ // Use the -l option for mod_ftp configured in LISTIsNLST mode: the option
+ // forces LIST output instead of NLST (which would be ambiguous for us
+ // to parse).
+ std::string command("LIST -l");
+ if (system_type_ == SYSTEM_TYPE_VMS)
+ command = "LIST *.*;0";
+
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_LIST);
+}
+
+int FtpNetworkTransaction::ProcessResponseLIST(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ // We want the client to start reading the response at this point.
+ // It got here either through Start or RestartWithAuth. We want that
+ // method to complete. Not setting next state here will make DoLoop exit
+ // and in turn make Start/RestartWithAuth complete.
+ response_.is_directory_listing = true;
+ break;
+ case ERROR_CLASS_OK:
+ response_.is_directory_listing = true;
+ next_state_ = STATE_CTRL_WRITE_QUIT;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// QUIT command
+int FtpNetworkTransaction::DoCtrlWriteQUIT() {
+ std::string command = "QUIT";
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_QUIT);
+}
+
+int FtpNetworkTransaction::ProcessResponseQUIT(
+ const FtpCtrlResponse& response) {
+ ctrl_socket_->Disconnect();
+ return last_error_;
+}
+
+// Data Connection
+
+int FtpNetworkTransaction::DoDataConnect() {
+ next_state_ = STATE_DATA_CONNECT_COMPLETE;
+ IPEndPoint ip_endpoint;
+ AddressList data_address;
+ // Connect to the same host as the control socket to prevent PASV port
+ // scanning attacks.
+ int rv = ctrl_socket_->GetPeerAddress(&ip_endpoint);
+ if (rv != OK)
+ return Stop(rv);
+ data_address = AddressList::CreateFromIPAddress(
+ ip_endpoint.address(), data_connection_port_);
+ data_socket_ = socket_factory_->CreateTransportClientSocket(
+ data_address, net_log_.net_log(), net_log_.source());
+ net_log_.AddEvent(
+ NetLog::TYPE_FTP_DATA_CONNECTION,
+ data_socket_->NetLog().source().ToEventParametersCallback());
+ return data_socket_->Connect(io_callback_);
+}
+
+int FtpNetworkTransaction::DoDataConnectComplete(int result) {
+ if (result != OK && use_epsv_) {
+ // It's possible we hit a broken server, sadly. They can break in different
+ // ways. Some time out, some reset a connection. Fall back to PASV.
+ // TODO(phajdan.jr): remember it for future transactions with this server.
+ // TODO(phajdan.jr): write a test for this code path.
+ use_epsv_ = false;
+ next_state_ = STATE_CTRL_WRITE_PASV;
+ return OK;
+ }
+
+ // Only record the connection error after we've applied all our fallbacks.
+ // We want to capture the final error, one we're not going to recover from.
+ RecordDataConnectionError(result);
+
+ if (result != OK)
+ return Stop(result);
+
+ next_state_ = state_after_data_connect_complete_;
+ return OK;
+}
+
+int FtpNetworkTransaction::DoDataRead() {
+ DCHECK(read_data_buf_.get());
+ DCHECK_GT(read_data_buf_len_, 0);
+
+ if (data_socket_ == NULL || !data_socket_->IsConnected()) {
+ // If we don't destroy the data socket completely, some servers will wait
+ // for us (http://crbug.com/21127). The half-closed TCP connection needs
+ // to be closed on our side too.
+ data_socket_.reset();
+
+ if (ctrl_socket_->IsConnected()) {
+ // Wait for the server's response, we should get it before sending QUIT.
+ next_state_ = STATE_CTRL_READ;
+ return OK;
+ }
+
+ // We are no longer connected to the server, so just finish the transaction.
+ return Stop(OK);
+ }
+
+ next_state_ = STATE_DATA_READ_COMPLETE;
+ read_data_buf_->data()[0] = 0;
+ return data_socket_->Read(
+ read_data_buf_.get(), read_data_buf_len_, io_callback_);
+}
+
+int FtpNetworkTransaction::DoDataReadComplete(int result) {
+ return result;
+}
+
+// We're using a histogram as a group of counters, with one bucket for each
+// enumeration value. We're only interested in the values of the counters.
+// Ignore the shape, average, and standard deviation of the histograms because
+// they are meaningless.
+//
+// We use two histograms. In the first histogram we tally whether the user has
+// seen an error of that type during the session. In the second histogram we
+// tally the total number of times the users sees each errer.
+void FtpNetworkTransaction::RecordDataConnectionError(int result) {
+ // Gather data for http://crbug.com/3073. See how many users have trouble
+ // establishing FTP data connection in passive FTP mode.
+ enum {
+ // Data connection successful.
+ NET_ERROR_OK = 0,
+
+ // Local firewall blocked the connection.
+ NET_ERROR_ACCESS_DENIED = 1,
+
+ // Connection timed out.
+ NET_ERROR_TIMED_OUT = 2,
+
+ // Connection has been estabilished, but then got broken (either reset
+ // or aborted).
+ NET_ERROR_CONNECTION_BROKEN = 3,
+
+ // Connection has been refused.
+ NET_ERROR_CONNECTION_REFUSED = 4,
+
+ // No connection to the internet.
+ NET_ERROR_INTERNET_DISCONNECTED = 5,
+
+ // Could not reach the destination address.
+ NET_ERROR_ADDRESS_UNREACHABLE = 6,
+
+ // A programming error in our network stack.
+ NET_ERROR_UNEXPECTED = 7,
+
+ // Other kind of error.
+ NET_ERROR_OTHER = 20,
+
+ NUM_OF_NET_ERROR_TYPES
+ } type;
+ switch (result) {
+ case OK:
+ type = NET_ERROR_OK;
+ break;
+ case ERR_ACCESS_DENIED:
+ case ERR_NETWORK_ACCESS_DENIED:
+ type = NET_ERROR_ACCESS_DENIED;
+ break;
+ case ERR_TIMED_OUT:
+ type = NET_ERROR_TIMED_OUT;
+ break;
+ case ERR_CONNECTION_ABORTED:
+ case ERR_CONNECTION_RESET:
+ case ERR_CONNECTION_CLOSED:
+ type = NET_ERROR_CONNECTION_BROKEN;
+ break;
+ case ERR_CONNECTION_FAILED:
+ case ERR_CONNECTION_REFUSED:
+ type = NET_ERROR_CONNECTION_REFUSED;
+ break;
+ case ERR_INTERNET_DISCONNECTED:
+ type = NET_ERROR_INTERNET_DISCONNECTED;
+ break;
+ case ERR_ADDRESS_INVALID:
+ case ERR_ADDRESS_UNREACHABLE:
+ type = NET_ERROR_ADDRESS_UNREACHABLE;
+ break;
+ case ERR_UNEXPECTED:
+ type = NET_ERROR_UNEXPECTED;
+ break;
+ default:
+ type = NET_ERROR_OTHER;
+ break;
+ };
+ static bool had_error_type[NUM_OF_NET_ERROR_TYPES];
+
+ DCHECK(type >= 0 && type < NUM_OF_NET_ERROR_TYPES);
+ if (!had_error_type[type]) {
+ had_error_type[type] = true;
+ UMA_HISTOGRAM_ENUMERATION("Net.FtpDataConnectionErrorHappened",
+ type, NUM_OF_NET_ERROR_TYPES);
+ }
+ UMA_HISTOGRAM_ENUMERATION("Net.FtpDataConnectionErrorCount",
+ type, NUM_OF_NET_ERROR_TYPES);
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_network_transaction.h b/chromium/net/ftp/ftp_network_transaction.h
new file mode 100644
index 00000000000..5eb6aae7361
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_transaction.h
@@ -0,0 +1,262 @@
+// 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.
+
+#ifndef NET_FTP_FTP_NETWORK_TRANSACTION_H_
+#define NET_FTP_FTP_NETWORK_TRANSACTION_H_
+
+#include <string>
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/address_list.h"
+#include "net/base/auth.h"
+#include "net/base/net_log.h"
+#include "net/dns/host_resolver.h"
+#include "net/dns/single_request_host_resolver.h"
+#include "net/ftp/ftp_ctrl_response_buffer.h"
+#include "net/ftp/ftp_response_info.h"
+#include "net/ftp/ftp_transaction.h"
+
+namespace net {
+
+class ClientSocketFactory;
+class FtpNetworkSession;
+class StreamSocket;
+
+class NET_EXPORT_PRIVATE FtpNetworkTransaction : public FtpTransaction {
+ public:
+ FtpNetworkTransaction(FtpNetworkSession* session,
+ ClientSocketFactory* socket_factory);
+ virtual ~FtpNetworkTransaction();
+
+ virtual int Stop(int error);
+ virtual int RestartIgnoringLastError(const CompletionCallback& callback);
+
+ // FtpTransaction methods:
+ virtual int Start(const FtpRequestInfo* request_info,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual int RestartWithAuth(const AuthCredentials& credentials,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual const FtpResponseInfo* GetResponseInfo() const OVERRIDE;
+ virtual LoadState GetLoadState() const OVERRIDE;
+ virtual uint64 GetUploadProgress() const OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(FtpNetworkTransactionTest,
+ DownloadTransactionEvilPasvUnsafeHost);
+
+ enum Command {
+ COMMAND_NONE,
+ COMMAND_USER,
+ COMMAND_PASS,
+ COMMAND_SYST,
+ COMMAND_TYPE,
+ COMMAND_EPSV,
+ COMMAND_PASV,
+ COMMAND_PWD,
+ COMMAND_SIZE,
+ COMMAND_RETR,
+ COMMAND_CWD,
+ COMMAND_LIST,
+ COMMAND_QUIT,
+ };
+
+ // Major categories of remote system types, as returned by SYST command.
+ enum SystemType {
+ SYSTEM_TYPE_UNKNOWN,
+ SYSTEM_TYPE_UNIX,
+ SYSTEM_TYPE_WINDOWS,
+ SYSTEM_TYPE_OS2,
+ SYSTEM_TYPE_VMS,
+ };
+
+ // Data representation type, see RFC 959 section 3.1.1. Data Types.
+ // We only support the two most popular data types.
+ enum DataType {
+ DATA_TYPE_ASCII,
+ DATA_TYPE_IMAGE,
+ };
+
+ // In FTP we need to issue different commands depending on whether a resource
+ // is a file or directory. If we don't know that, we're going to autodetect
+ // it.
+ enum ResourceType {
+ RESOURCE_TYPE_UNKNOWN,
+ RESOURCE_TYPE_FILE,
+ RESOURCE_TYPE_DIRECTORY,
+ };
+
+ enum State {
+ // Control connection states:
+ STATE_CTRL_RESOLVE_HOST,
+ STATE_CTRL_RESOLVE_HOST_COMPLETE,
+ STATE_CTRL_CONNECT,
+ STATE_CTRL_CONNECT_COMPLETE,
+ STATE_CTRL_READ,
+ STATE_CTRL_READ_COMPLETE,
+ STATE_CTRL_WRITE,
+ STATE_CTRL_WRITE_COMPLETE,
+ STATE_CTRL_WRITE_USER,
+ STATE_CTRL_WRITE_PASS,
+ STATE_CTRL_WRITE_SYST,
+ STATE_CTRL_WRITE_TYPE,
+ STATE_CTRL_WRITE_EPSV,
+ STATE_CTRL_WRITE_PASV,
+ STATE_CTRL_WRITE_PWD,
+ STATE_CTRL_WRITE_RETR,
+ STATE_CTRL_WRITE_SIZE,
+ STATE_CTRL_WRITE_CWD,
+ STATE_CTRL_WRITE_LIST,
+ STATE_CTRL_WRITE_QUIT,
+ // Data connection states:
+ STATE_DATA_CONNECT,
+ STATE_DATA_CONNECT_COMPLETE,
+ STATE_DATA_READ,
+ STATE_DATA_READ_COMPLETE,
+ STATE_NONE
+ };
+
+ // Resets the members of the transaction so it can be restarted.
+ void ResetStateForRestart();
+
+ // Resets the data connection after an error and switches to |next_state|.
+ void ResetDataConnectionAfterError(State next_state);
+
+ void DoCallback(int result);
+ void OnIOComplete(int result);
+
+ // Executes correct ProcessResponse + command_name function based on last
+ // issued command. Returns error code.
+ int ProcessCtrlResponse();
+
+ int SendFtpCommand(const std::string& command,
+ const std::string& command_for_log,
+ Command cmd);
+
+ // Returns request path suitable to be included in an FTP command. If the path
+ // will be used as a directory, |is_directory| should be true.
+ std::string GetRequestPathForFtpCommand(bool is_directory) const;
+
+ // See if the request URL contains a typecode and make us respect it.
+ void DetectTypecode();
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ // Each of these methods corresponds to a State value. Those with an input
+ // argument receive the result from the previous state. If a method returns
+ // ERR_IO_PENDING, then the result from OnIOComplete will be passed to the
+ // next state method as the result arg.
+ int DoCtrlResolveHost();
+ int DoCtrlResolveHostComplete(int result);
+ int DoCtrlConnect();
+ int DoCtrlConnectComplete(int result);
+ int DoCtrlRead();
+ int DoCtrlReadComplete(int result);
+ int DoCtrlWrite();
+ int DoCtrlWriteComplete(int result);
+ int DoCtrlWriteUSER();
+ int ProcessResponseUSER(const FtpCtrlResponse& response);
+ int DoCtrlWritePASS();
+ int ProcessResponsePASS(const FtpCtrlResponse& response);
+ int DoCtrlWriteSYST();
+ int ProcessResponseSYST(const FtpCtrlResponse& response);
+ int DoCtrlWritePWD();
+ int ProcessResponsePWD(const FtpCtrlResponse& response);
+ int DoCtrlWriteTYPE();
+ int ProcessResponseTYPE(const FtpCtrlResponse& response);
+ int DoCtrlWriteEPSV();
+ int ProcessResponseEPSV(const FtpCtrlResponse& response);
+ int DoCtrlWritePASV();
+ int ProcessResponsePASV(const FtpCtrlResponse& response);
+ int DoCtrlWriteRETR();
+ int ProcessResponseRETR(const FtpCtrlResponse& response);
+ int DoCtrlWriteSIZE();
+ int ProcessResponseSIZE(const FtpCtrlResponse& response);
+ int DoCtrlWriteCWD();
+ int ProcessResponseCWD(const FtpCtrlResponse& response);
+ int ProcessResponseCWDNotADirectory();
+ int DoCtrlWriteLIST();
+ int ProcessResponseLIST(const FtpCtrlResponse& response);
+ int DoCtrlWriteQUIT();
+ int ProcessResponseQUIT(const FtpCtrlResponse& response);
+
+ int DoDataConnect();
+ int DoDataConnectComplete(int result);
+ int DoDataRead();
+ int DoDataReadComplete(int result);
+
+ void RecordDataConnectionError(int result);
+
+ Command command_sent_;
+
+ CompletionCallback io_callback_;
+ CompletionCallback user_callback_;
+
+ scoped_refptr<FtpNetworkSession> session_;
+
+ BoundNetLog net_log_;
+ const FtpRequestInfo* request_;
+ FtpResponseInfo response_;
+
+ // Cancels the outstanding request on destruction.
+ SingleRequestHostResolver resolver_;
+ AddressList addresses_;
+
+ // User buffer passed to the Read method for control socket.
+ scoped_refptr<IOBuffer> read_ctrl_buf_;
+
+ scoped_ptr<FtpCtrlResponseBuffer> ctrl_response_buffer_;
+
+ scoped_refptr<IOBuffer> read_data_buf_;
+ int read_data_buf_len_;
+
+ // 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<DrainableIOBuffer> write_buf_;
+
+ int last_error_;
+
+ SystemType system_type_;
+
+ // Data type to be used for the TYPE command.
+ DataType data_type_;
+
+ // Detected resource type (file or directory).
+ ResourceType resource_type_;
+
+ // Initially we favour EPSV over PASV for transfers but should any
+ // EPSV fail, we fall back to PASV for the duration of connection.
+ bool use_epsv_;
+
+ AuthCredentials credentials_;
+
+ // Current directory on the remote server, as returned by last PWD command,
+ // with any trailing slash removed.
+ std::string current_remote_directory_;
+
+ int data_connection_port_;
+
+ ClientSocketFactory* socket_factory_;
+
+ scoped_ptr<StreamSocket> ctrl_socket_;
+ scoped_ptr<StreamSocket> data_socket_;
+
+ State next_state_;
+
+ // State to switch to after data connection is complete.
+ State state_after_data_connect_complete_;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_NETWORK_TRANSACTION_H_
diff --git a/chromium/net/ftp/ftp_network_transaction_unittest.cc b/chromium/net/ftp/ftp_network_transaction_unittest.cc
new file mode 100644
index 00000000000..a244cc51c5a
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_transaction_unittest.cc
@@ -0,0 +1,1602 @@
+// 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 "build/build_config.h"
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_util.h"
+#include "net/base/test_completion_callback.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/ftp/ftp_network_session.h"
+#include "net/ftp/ftp_request_info.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+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 DynamicSocketDataProvider {
+ public:
+ enum State {
+ NONE,
+ PRE_USER,
+ PRE_PASSWD,
+ PRE_SYST,
+ PRE_PWD,
+ PRE_TYPE,
+ PRE_SIZE,
+ PRE_EPSV,
+ PRE_PASV,
+ PRE_LIST,
+ PRE_RETR,
+ PRE_RETR_EPSV,
+ PRE_RETR_PASV,
+ PRE_CWD_EPSV,
+ PRE_CWD_PASV,
+ PRE_CWD,
+ PRE_QUIT,
+ PRE_NOPASV,
+ QUIT
+ };
+
+ FtpSocketDataProvider()
+ : failure_injection_state_(NONE),
+ multiline_welcome_(false),
+ use_epsv_(true),
+ data_type_('I') {
+ Init();
+ }
+
+ virtual 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:
+ {
+ const char* response_one = "230 Welcome\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);
+ }
+ 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,
+ use_epsv_ ? PRE_EPSV : PRE_PASV,
+ "200 TYPE set successfully\r\n");
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_SIZE,
+ "227 Entering Extended Passive Mode (|||31744|)\r\n");
+ case PRE_CWD_EPSV:
+ return Verify("EPSV\r\n", data, PRE_CWD,
+ "227 Entering Extended Passive Mode (|||31744|)\r\n");
+ case PRE_RETR_EPSV:
+ return Verify("EPSV\r\n", data, PRE_RETR,
+ "227 Entering Extended Passive Mode (|||31744|)\r\n");
+ case PRE_CWD_PASV:
+ return Verify("PASV\r\n", data, PRE_CWD,
+ "227 Entering Passive Mode 127,0,0,1,123,456\r\n");
+ case PRE_RETR_PASV:
+ return Verify("PASV\r\n", data, PRE_RETR,
+ "227 Entering Passive Mode 127,0,0,1,123,456\r\n");
+ case PRE_PASV:
+ return Verify("PASV\r\n", data, PRE_SIZE,
+ "227 Entering Passive Mode 127,0,0,1,123,456\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_;
+ }
+
+ virtual void Reset() OVERRIDE {
+ DynamicSocketDataProvider::Reset();
+ Init();
+ }
+
+ 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; }
+
+ 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));
+ }
+
+
+ private:
+ 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() {
+ }
+
+ virtual 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,
+ use_epsv() ? PRE_CWD_EPSV : PRE_CWD_PASV,
+ "550 I can only retrieve regular files\r\n");
+ case PRE_CWD:
+ return Verify("CWD /\r\n", data, PRE_LIST, "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() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV,
+ "500 no EPSV for you\r\n");
+ case PRE_SIZE:
+ return Verify("SIZE /\r\n", data, PRE_CWD_PASV,
+ "550 I can only retrieve regular files\r\n");
+ default:
+ return FtpSocketDataProviderDirectoryListing::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(
+ FtpSocketDataProviderDirectoryListingWithPasvFallback);
+};
+
+class FtpSocketDataProviderDirectoryListingZeroSize
+ : public FtpSocketDataProviderDirectoryListing {
+ public:
+ FtpSocketDataProviderDirectoryListingZeroSize() {
+ }
+
+ virtual 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, "213 0\r\n");
+ default:
+ return FtpSocketDataProviderDirectoryListing::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderDirectoryListingZeroSize);
+};
+
+class FtpSocketDataProviderVMSDirectoryListing : public FtpSocketDataProvider {
+ public:
+ FtpSocketDataProviderVMSDirectoryListing() {
+ }
+
+ virtual 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_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV, "500 Invalid command\r\n");
+ case PRE_SIZE:
+ return Verify("SIZE ANONYMOUS_ROOT:[000000]dir\r\n", data, PRE_CWD_PASV,
+ "550 I can only retrieve regular files\r\n");
+ case PRE_CWD:
+ return Verify("CWD ANONYMOUS_ROOT:[dir]\r\n", data, PRE_LIST,
+ "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() {
+ }
+
+ virtual 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_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV,
+ "500 EPSV command unknown\r\n");
+ case PRE_SIZE:
+ return Verify("SIZE ANONYMOUS_ROOT\r\n", data, PRE_CWD_PASV,
+ "550 I can only retrieve regular files\r\n");
+ case PRE_CWD:
+ return Verify("CWD ANONYMOUS_ROOT:[000000]\r\n", data, PRE_LIST,
+ "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() {
+ }
+
+ virtual 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_RETR,
+ "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() {
+ }
+
+ virtual 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 18\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");
+ 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(FtpSocketDataProviderFileDownload);
+};
+
+class FtpSocketDataProviderFileNotFound : public FtpSocketDataProvider {
+ public:
+ FtpSocketDataProviderFileNotFound() {
+ }
+
+ virtual 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_CWD_EPSV : PRE_CWD_PASV,
+ "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() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV,
+ "500 No can do\r\n");
+ case PRE_CWD:
+ return Verify("CWD /file\r\n", data, 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() {
+ }
+
+ virtual 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() {
+ }
+
+ virtual 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() {
+ }
+
+ virtual 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_EPSV:
+ return Verify("EPSV\r\n", data, PRE_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, 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 FtpSocketDataProviderEscaping : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderEscaping() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_SIZE:
+ return Verify("SIZE / !\"#$%y\200\201\r\n", data, PRE_CWD,
+ "213 18\r\n");
+ case PRE_CWD:
+ return Verify("CWD / !\"#$%y\200\201\r\n", data,
+ use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
+ "550 Not a directory\r\n");
+ case PRE_RETR:
+ return Verify("RETR / !\"#$%y\200\201\r\n", data, PRE_QUIT,
+ "200 OK\r\n");
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEscaping);
+};
+
+class FtpSocketDataProviderFileDownloadTransferStarting
+ : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderFileDownloadTransferStarting() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_RETR:
+ return Verify("RETR /file\r\n", data, PRE_QUIT,
+ "125-Data connection already open.\r\n"
+ "125 Transfer starting.\r\n"
+ "226 Transfer complete.\r\n");
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadTransferStarting);
+};
+
+class FtpSocketDataProviderDirectoryListingTransferStarting
+ : public FtpSocketDataProviderDirectoryListing {
+ public:
+ FtpSocketDataProviderDirectoryListingTransferStarting() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_LIST:
+ return Verify("LIST -l\r\n", data, PRE_QUIT,
+ "125-Data connection already open.\r\n"
+ "125 Transfer starting.\r\n"
+ "226 Transfer complete.\r\n");
+ default:
+ return FtpSocketDataProviderDirectoryListing::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(
+ FtpSocketDataProviderDirectoryListingTransferStarting);
+};
+
+class FtpSocketDataProviderFileDownloadInvalidResponse
+ : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderFileDownloadInvalidResponse() {
+ }
+
+ virtual 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) {}
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, expected_state_,
+ epsv_response_, epsv_response_length_);
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ const char* 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) {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_PASV:
+ return Verify("PASV\r\n", data, expected_state_, pasv_response_);
+ default:
+ return FtpSocketDataProviderFileDownloadWithPasvFallback::OnWrite(data);
+ }
+ }
+
+ private:
+ const char* 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) {
+ }
+
+ virtual 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* 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) {
+ }
+
+ virtual 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* expected_user_;
+ const char* expected_password_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilLogin);
+};
+
+class FtpSocketDataProviderCloseConnection : public FtpSocketDataProvider {
+ public:
+ FtpSocketDataProviderCloseConnection() {
+ }
+
+ virtual 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_QUIT, "");
+ default:
+ return FtpSocketDataProvider::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderCloseConnection);
+};
+
+class FtpNetworkTransactionTest
+ : public PlatformTest,
+ public ::testing::WithParamInterface<int> {
+ public:
+ FtpNetworkTransactionTest()
+ : host_resolver_(new MockHostResolver),
+ session_(new FtpNetworkSession(host_resolver_.get())),
+ transaction_(session_.get(), &mock_socket_factory_) {
+ scoped_refptr<RuleBasedHostResolverProc> rules(
+ new RuleBasedHostResolverProc(NULL));
+ 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());
+ }
+
+ 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 data_socket,
+ 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()),
+ };
+
+ ScopedVector<StaticSocketDataProvider> data_sockets;
+ data_sockets.reserve(data_socket);
+ for (int i = 0; i < data_socket + 1; i++) {
+ // We only read from one data socket, other ones are dummy.
+ if (i == data_socket) {
+ data_sockets.push_back(new StaticSocketDataProvider(
+ data_reads, arraysize(data_reads), NULL, 0));
+ } else {
+ data_sockets.push_back(new StaticSocketDataProvider);
+ }
+ mock_socket_factory_.AddSocketDataProvider(data_sockets[i]);
+ }
+
+ FtpRequestInfo request_info = GetRequestInfo(request);
+ EXPECT_EQ(LOAD_STATE_IDLE, transaction_.GetLoadState());
+ ASSERT_EQ(ERR_IO_PENDING,
+ transaction_.Start(&request_info, callback_.callback(),
+ BoundNetLog()));
+ EXPECT_NE(LOAD_STATE_IDLE, transaction_.GetLoadState());
+ ASSERT_EQ(expected_result, callback_.WaitForResult());
+ if (expected_result == OK) {
+ scoped_refptr<IOBuffer> io_buffer(new 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, 1, expected_result);
+ }
+
+ scoped_ptr<MockHostResolver> host_resolver_;
+ scoped_refptr<FtpNetworkSession> session_;
+ MockClientSocketFactory mock_socket_factory_;
+ FtpNetworkTransaction transaction_;
+ TestCompletionCallback callback_;
+};
+
+TEST_P(FtpNetworkTransactionTest, FailedLookup) {
+ FtpRequestInfo request_info = GetRequestInfo("ftp://badhost");
+ scoped_refptr<RuleBasedHostResolverProc> rules(
+ new RuleBasedHostResolverProc(NULL));
+ 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(),
+ BoundNetLog()));
+ ASSERT_EQ(ERR_NAME_NOT_RESOLVED, callback_.WaitForResult());
+ 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", 1, ERR_INVALID_RESPONSE);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransaction) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, 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()->socket_address.host());
+ EXPECT_EQ(21, transaction_.GetResponseInfo()->socket_address.port());
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionWithPasvFallback) {
+ FtpSocketDataProviderDirectoryListingWithPasvFallback ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, 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", 1, 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", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionShortReads2) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ ctrl_socket.set_short_read_limit(2);
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionShortReads5) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ ctrl_socket.set_short_read_limit(5);
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, 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.allow_unconsumed_reads(true);
+ ctrl_socket.set_multiline_welcome(true);
+ ctrl_socket.set_short_read_limit(5);
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, OK);
+}
+
+// Regression test for http://crbug.com/60555.
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionZeroSize) {
+ FtpSocketDataProviderDirectoryListingZeroSize ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 0, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionVMS) {
+ FtpSocketDataProviderVMSDirectoryListing ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/dir", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionVMSRootDirectory) {
+ FtpSocketDataProviderVMSDirectoryListingRootDirectory ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionTransferStarting) {
+ FtpSocketDataProviderDirectoryListingTransferStarting ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransaction) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, 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()->socket_address.host());
+ EXPECT_EQ(21, transaction_.GetResponseInfo()->socket_address.port());
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionWithPasvFallback) {
+ FtpSocketDataProviderFileDownloadWithPasvFallback ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, 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", 0, 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", 0, 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", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionShortReads2) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ ctrl_socket.set_short_read_limit(2);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionShortReads5) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ ctrl_socket.set_short_read_limit(5);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionZeroSize) {
+ FtpSocketDataProviderFileDownloadZeroSize ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionCWD451) {
+ FtpSocketDataProviderFileDownloadCWD451 ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionVMS) {
+ FtpSocketDataProviderVMSFileDownload ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionTransferStarting) {
+ FtpSocketDataProviderFileDownloadTransferStarting ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionInvalidResponse) {
+ FtpSocketDataProviderFileDownloadInvalidResponse ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, 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", 1, 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", 1, 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", 1, 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", 1, 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", 1, ERR_UNSAFE_PORT);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafeHost) {
+ FtpSocketDataProviderEvilPasv ctrl_socket(
+ "227 Portscan (10,1,2,3,123,456)\r\n", FtpSocketDataProvider::PRE_SIZE);
+ 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, arraysize(data_reads),
+ NULL, 0);
+ 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(),
+ BoundNetLog()));
+ ASSERT_EQ(OK, callback_.WaitForResult());
+
+ // 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 (AddressList::const_iterator 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", 1, 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", 1, 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", 1, 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", 1, 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;
+
+ const char response[] = "227 Portscan (\0\0\031773\0)\r\n";
+ FtpSocketDataProviderEvilEpsv ctrl_socket(response, sizeof(response)-1,
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, 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", 1, 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", 1, 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", 1, 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", 1, ERR_UNSAFE_PORT);
+}
+
+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_SIZE);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, 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", 1, 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", 1, ERR_INVALID_RESPONSE);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilLoginBadUsername) {
+ FtpSocketDataProviderEvilLogin ctrl_socket("hello%0Aworld", "test");
+ ExecuteTransaction(&ctrl_socket, "ftp://hello%0Aworld:test@host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilLoginBadPassword) {
+ FtpSocketDataProviderEvilLogin ctrl_socket("test", "hello%0Dworld");
+ ExecuteTransaction(&ctrl_socket, "ftp://test:hello%0Dworld@host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionSpaceInLogin) {
+ FtpSocketDataProviderEvilLogin ctrl_socket("hello world", "test");
+ ExecuteTransaction(&ctrl_socket, "ftp://hello%20world:test@host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionSpaceInPassword) {
+ FtpSocketDataProviderEvilLogin ctrl_socket("test", "hello world");
+ ExecuteTransaction(&ctrl_socket, "ftp://test:hello%20world@host/file", 1, OK);
+}
+
+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(),
+ BoundNetLog()));
+ ASSERT_EQ(ERR_FTP_FAILED, callback_.WaitForResult());
+
+ 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, arraysize(ctrl_reads),
+ ctrl_writes, arraysize(ctrl_writes));
+ mock_socket_factory_.AddSocketDataProvider(&ctrl_socket2);
+ ASSERT_EQ(ERR_IO_PENDING,
+ transaction_.RestartWithAuth(
+ AuthCredentials(
+ ASCIIToUTF16("foo\nownz0red"),
+ ASCIIToUTF16("innocent")),
+ callback_.callback()));
+ EXPECT_EQ(ERR_MALFORMED_IDENTITY, callback_.WaitForResult());
+}
+
+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(),
+ BoundNetLog()));
+ ASSERT_EQ(ERR_FTP_FAILED, callback_.WaitForResult());
+
+ 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, arraysize(ctrl_reads),
+ ctrl_writes, arraysize(ctrl_writes));
+ mock_socket_factory_.AddSocketDataProvider(&ctrl_socket2);
+ ASSERT_EQ(ERR_IO_PENDING,
+ transaction_.RestartWithAuth(
+ AuthCredentials(ASCIIToUTF16("innocent"),
+ ASCIIToUTF16("foo\nownz0red")),
+ callback_.callback()));
+ EXPECT_EQ(ERR_MALFORMED_IDENTITY, callback_.WaitForResult());
+}
+
+TEST_P(FtpNetworkTransactionTest, Escaping) {
+ FtpSocketDataProviderEscaping ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/%20%21%22%23%24%25%79%80%81",
+ 1, OK);
+}
+
+// Test for http://crbug.com/23794.
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilSize) {
+ // Try to overflow int64 in the response.
+ FtpSocketDataProviderEvilSize ctrl_socket(
+ "213 99999999999999999999999999999999\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, 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", 1, OK);
+ EXPECT_EQ(3204427776LL,
+ transaction_.GetResponseInfo()->expected_content_size);
+}
+
+// Regression test for http://crbug.com/25023.
+TEST_P(FtpNetworkTransactionTest, CloseConnection) {
+ FtpSocketDataProviderCloseConnection ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, 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_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_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", 2, 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);
+}
+
+INSTANTIATE_TEST_CASE_P(FTP,
+ FtpNetworkTransactionTest,
+ ::testing::Values(AF_INET, AF_INET6));
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_request_info.h b/chromium/net/ftp/ftp_request_info.h
new file mode 100644
index 00000000000..be7702fd3d9
--- /dev/null
+++ b/chromium/net/ftp/ftp_request_info.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2010 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_REQUEST_INFO_H_
+#define NET_FTP_FTP_REQUEST_INFO_H_
+
+#include "url/gurl.h"
+
+namespace net {
+
+class FtpRequestInfo {
+ public:
+ // The requested URL.
+ GURL url;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_REQUEST_INFO_H_
diff --git a/chromium/net/ftp/ftp_response_info.cc b/chromium/net/ftp/ftp_response_info.cc
new file mode 100644
index 00000000000..ddbd1edf3a4
--- /dev/null
+++ b/chromium/net/ftp/ftp_response_info.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2011 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_response_info.h"
+
+namespace net {
+
+FtpResponseInfo::FtpResponseInfo()
+ : needs_auth(false),
+ expected_content_size(-1),
+ is_directory_listing(false) {
+}
+
+FtpResponseInfo::~FtpResponseInfo() {}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_response_info.h b/chromium/net/ftp/ftp_response_info.h
new file mode 100644
index 00000000000..8bae8c19931
--- /dev/null
+++ b/chromium/net/ftp/ftp_response_info.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2010 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_RESPONSE_INFO_H_
+#define NET_FTP_FTP_RESPONSE_INFO_H_
+
+#include "base/time/time.h"
+#include "net/base/host_port_pair.h"
+
+namespace net {
+
+class FtpResponseInfo {
+ public:
+ FtpResponseInfo();
+ ~FtpResponseInfo();
+
+ // True if authentication failed and valid authentication credentials are
+ // needed.
+ bool needs_auth;
+
+ // The time at which the request was made that resulted in this response.
+ // For cached responses, this time could be "far" in the past.
+ base::Time request_time;
+
+ // The time at which the response headers were received. For cached
+ // responses, this time could be "far" in the past.
+ base::Time response_time;
+
+ // Expected content size, in bytes, as reported by SIZE command. Only valid
+ // for file downloads. -1 means unknown size.
+ int64 expected_content_size;
+
+ // True if the response data is of a directory listing.
+ bool is_directory_listing;
+
+ // Remote address of the socket which fetched this resource.
+ HostPortPair socket_address;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_RESPONSE_INFO_H_
diff --git a/chromium/net/ftp/ftp_server_type_histograms.cc b/chromium/net/ftp/ftp_server_type_histograms.cc
new file mode 100644
index 00000000000..84592ed023f
--- /dev/null
+++ b/chromium/net/ftp/ftp_server_type_histograms.cc
@@ -0,0 +1,33 @@
+// 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_server_type_histograms.h"
+
+#include "base/metrics/histogram.h"
+
+namespace net {
+
+// We're using a histogram as a group of counters, with one bucket for each
+// enumeration value. We're only interested in the values of the counters.
+// Ignore the shape, average, and standard deviation of the histograms because
+// they are meaningless.
+//
+// We use two histograms. In the first histogram we tally whether the user has
+// seen an FTP server of a given type during that session. In the second
+// histogram we tally the number of transactions with FTP server of a given type
+// the user has made during that session.
+void UpdateFtpServerTypeHistograms(FtpServerType type) {
+ static bool had_server_type[NUM_OF_SERVER_TYPES];
+ if (type >= 0 && type < NUM_OF_SERVER_TYPES) {
+ if (!had_server_type[type]) {
+ had_server_type[type] = true;
+ UMA_HISTOGRAM_ENUMERATION("Net.HadFtpServerType2",
+ type, NUM_OF_SERVER_TYPES);
+ }
+ }
+ UMA_HISTOGRAM_ENUMERATION("Net.FtpServerTypeCount2",
+ type, NUM_OF_SERVER_TYPES);
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_server_type_histograms.h b/chromium/net/ftp/ftp_server_type_histograms.h
new file mode 100644
index 00000000000..5ae862cd21a
--- /dev/null
+++ b/chromium/net/ftp/ftp_server_type_histograms.h
@@ -0,0 +1,32 @@
+// 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_SERVER_TYPE_HISTOGRAMS_H_
+#define NET_FTP_FTP_SERVER_TYPE_HISTOGRAMS_H_
+
+// The UpdateFtpServerTypeHistograms function collects statistics related
+// to the types of FTP servers that our users are encountering.
+
+namespace net {
+
+enum FtpServerType {
+ // Record cases in which we couldn't parse the server's response. That means
+ // a server type we don't recognize, a security attack (when what we're
+ // connecting to isn't an FTP server), or a broken server.
+ SERVER_UNKNOWN = 0,
+
+ SERVER_LS = 1, // Server using /bin/ls -l listing style.
+ SERVER_WINDOWS = 2, // Server using Windows listing style.
+ SERVER_VMS = 3, // Server using VMS listing style.
+ SERVER_NETWARE = 4, // Server using Netware listing style.
+ SERVER_OS2 = 5, // Server using OS/2 listing style.
+
+ NUM_OF_SERVER_TYPES
+};
+
+void UpdateFtpServerTypeHistograms(FtpServerType type);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_SERVER_TYPE_HISTOGRAMS_H_
diff --git a/chromium/net/ftp/ftp_transaction.h b/chromium/net/ftp/ftp_transaction.h
new file mode 100644
index 00000000000..f0e1b41f26d
--- /dev/null
+++ b/chromium/net/ftp/ftp_transaction.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2011 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_TRANSACTION_H_
+#define NET_FTP_FTP_TRANSACTION_H_
+
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_states.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class AuthCredentials;
+class FtpResponseInfo;
+class FtpRequestInfo;
+class BoundNetLog;
+
+// Represents a single FTP transaction.
+class NET_EXPORT_PRIVATE FtpTransaction {
+ public:
+ // Stops any pending IO and destroys the transaction object.
+ virtual ~FtpTransaction() {}
+
+ // Starts the FTP transaction (i.e., sends the FTP request).
+ //
+ // Returns OK if the transaction could be started synchronously, which means
+ // that the request was served from the cache (only supported for directory
+ // listings). ERR_IO_PENDING is returned to indicate that the
+ // CompletionCallback will be notified once response info is available or if
+ // an IO error occurs. Any other return value indicates that the transaction
+ // could not be started.
+ //
+ // Regardless of the return value, the caller is expected to keep the
+ // request_info object alive until Destroy is called on the transaction.
+ //
+ // NOTE: The transaction is not responsible for deleting the callback object.
+ //
+ // Profiling information for the request is saved to |net_log| if non-NULL.
+ virtual int Start(const FtpRequestInfo* request_info,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) = 0;
+
+ // Restarts the FTP transaction with authentication credentials.
+ virtual int RestartWithAuth(const AuthCredentials& credentials,
+ const CompletionCallback& callback) = 0;
+
+ // Once response info is available for the transaction, response data may be
+ // read by calling this method.
+ //
+ // Response data is copied into the given buffer and the number of bytes
+ // copied is returned. ERR_IO_PENDING is returned if response data is not
+ // yet available. The CompletionCallback is notified when the data copy
+ // completes, and it is passed the number of bytes that were successfully
+ // copied. Or, if a read error occurs, the CompletionCallback is notified of
+ // the error. Any other negative return value indicates that the transaction
+ // could not be read.
+ //
+ // NOTE: The transaction is not responsible for deleting the callback object.
+ //
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) = 0;
+
+ // Returns the response info for this transaction or NULL if the response
+ // info is not available.
+ virtual const FtpResponseInfo* GetResponseInfo() const = 0;
+
+ // Returns the load state for this transaction.
+ virtual LoadState GetLoadState() const = 0;
+
+ // Returns the upload progress in bytes. If there is no upload data,
+ // zero will be returned.
+ virtual uint64 GetUploadProgress() const = 0;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_TRANSACTION_H_
diff --git a/chromium/net/ftp/ftp_transaction_factory.h b/chromium/net/ftp/ftp_transaction_factory.h
new file mode 100644
index 00000000000..db32fbeeac5
--- /dev/null
+++ b/chromium/net/ftp/ftp_transaction_factory.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 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_TRANSACTION_FACTORY_H_
+#define NET_FTP_FTP_TRANSACTION_FACTORY_H_
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+class FtpTransaction;
+
+// An interface to a class that can create FtpTransaction objects.
+class NET_EXPORT FtpTransactionFactory {
+ public:
+ virtual ~FtpTransactionFactory() {}
+
+ // Creates a FtpTransaction object.
+ virtual FtpTransaction* CreateTransaction() = 0;
+
+ // Suspends the creation of new transactions. If |suspend| is false, creation
+ // of new transactions is resumed.
+ virtual void Suspend(bool suspend) = 0;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_TRANSACTION_FACTORY_H_
diff --git a/chromium/net/ftp/ftp_util.cc b/chromium/net/ftp/ftp_util.cc
new file mode 100644
index 00000000000..c5e18c8f79d
--- /dev/null
+++ b/chromium/net/ftp/ftp_util.cc
@@ -0,0 +1,376 @@
+// Copyright (c) 2011 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_util.h"
+
+#include <map>
+#include <vector>
+
+#include "base/i18n/case_conversion.h"
+#include "base/i18n/char_iterator.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "third_party/icu/source/common/unicode/uchar.h"
+#include "third_party/icu/source/i18n/unicode/datefmt.h"
+#include "third_party/icu/source/i18n/unicode/dtfmtsym.h"
+
+using base::StringPiece16;
+
+// For examples of Unix<->VMS path conversions, see the unit test file. On VMS
+// a path looks differently depending on whether it's a file or directory.
+
+namespace net {
+
+// static
+std::string FtpUtil::UnixFilePathToVMS(const std::string& unix_path) {
+ if (unix_path.empty())
+ return std::string();
+
+ base::StringTokenizer tokenizer(unix_path, "/");
+ std::vector<std::string> tokens;
+ while (tokenizer.GetNext())
+ tokens.push_back(tokenizer.token());
+
+ if (unix_path[0] == '/') {
+ // It's an absolute path.
+
+ if (tokens.empty()) {
+ DCHECK_EQ(1U, unix_path.length());
+ return "[]";
+ }
+
+ if (tokens.size() == 1)
+ return unix_path.substr(1); // Drop the leading slash.
+
+ std::string result(tokens[0] + ":[");
+ if (tokens.size() == 2) {
+ // Don't ask why, it just works that way on VMS.
+ result.append("000000");
+ } else {
+ result.append(tokens[1]);
+ for (size_t i = 2; i < tokens.size() - 1; i++)
+ result.append("." + tokens[i]);
+ }
+ result.append("]" + tokens[tokens.size() - 1]);
+ return result;
+ }
+
+ if (tokens.size() == 1)
+ return unix_path;
+
+ std::string result("[");
+ for (size_t i = 0; i < tokens.size() - 1; i++)
+ result.append("." + tokens[i]);
+ result.append("]" + tokens[tokens.size() - 1]);
+ return result;
+}
+
+// static
+std::string FtpUtil::UnixDirectoryPathToVMS(const std::string& unix_path) {
+ if (unix_path.empty())
+ return std::string();
+
+ std::string path(unix_path);
+
+ if (path[path.length() - 1] != '/')
+ path.append("/");
+
+ // Reuse logic from UnixFilePathToVMS by appending a fake file name to the
+ // real path and removing it after conversion.
+ path.append("x");
+ path = UnixFilePathToVMS(path);
+ return path.substr(0, path.length() - 1);
+}
+
+// static
+std::string FtpUtil::VMSPathToUnix(const std::string& vms_path) {
+ if (vms_path.empty())
+ return ".";
+
+ if (vms_path[0] == '/') {
+ // This is not really a VMS path. Most likely the server is emulating UNIX.
+ // Return path as-is.
+ return vms_path;
+ }
+
+ if (vms_path == "[]")
+ return "/";
+
+ std::string result(vms_path);
+ if (vms_path[0] == '[') {
+ // It's a relative path.
+ ReplaceFirstSubstringAfterOffset(&result, 0, "[.", std::string());
+ } else {
+ // It's an absolute path.
+ result.insert(0, "/");
+ ReplaceSubstringsAfterOffset(&result, 0, ":[000000]", "/");
+ ReplaceSubstringsAfterOffset(&result, 0, ":[", "/");
+ }
+ std::replace(result.begin(), result.end(), '.', '/');
+ std::replace(result.begin(), result.end(), ']', '/');
+
+ // Make sure the result doesn't end with a slash.
+ if (result.length() && result[result.length() - 1] == '/')
+ result = result.substr(0, result.length() - 1);
+
+ return result;
+}
+
+namespace {
+
+// Lazy-initialized map of abbreviated month names.
+class AbbreviatedMonthsMap {
+ public:
+ static AbbreviatedMonthsMap* GetInstance() {
+ return Singleton<AbbreviatedMonthsMap>::get();
+ }
+
+ // Converts abbreviated month name |text| to its number (in range 1-12).
+ // On success returns true and puts the number in |number|.
+ bool GetMonthNumber(const base::string16& text, int* number) {
+ // Ignore the case of the month names. The simplest way to handle that
+ // is to make everything lowercase.
+ base::string16 text_lower(base::i18n::ToLower(text));
+
+ if (map_.find(text_lower) == map_.end())
+ return false;
+
+ *number = map_[text_lower];
+ return true;
+ }
+
+ private:
+ friend struct DefaultSingletonTraits<AbbreviatedMonthsMap>;
+
+ // Constructor, initializes the map based on ICU data. It is much faster
+ // to do that just once.
+ AbbreviatedMonthsMap() {
+ int32_t locales_count;
+ const icu::Locale* locales =
+ icu::DateFormat::getAvailableLocales(locales_count);
+
+ for (int32_t locale = 0; locale < locales_count; locale++) {
+ UErrorCode status(U_ZERO_ERROR);
+
+ icu::DateFormatSymbols format_symbols(locales[locale], status);
+
+ // If we cannot get format symbols for some locale, it's not a fatal
+ // error. Just try another one.
+ if (U_FAILURE(status))
+ continue;
+
+ int32_t months_count;
+ const icu::UnicodeString* months =
+ format_symbols.getShortMonths(months_count);
+
+ for (int32_t month = 0; month < months_count; month++) {
+ base::string16 month_name(months[month].getBuffer(),
+ static_cast<size_t>(months[month].length()));
+
+ // Ignore the case of the month names. The simplest way to handle that
+ // is to make everything lowercase.
+ month_name = base::i18n::ToLower(month_name);
+
+ map_[month_name] = month + 1;
+
+ // Sometimes ICU returns longer strings, but in FTP listings a shorter
+ // abbreviation is used (for example for the Russian locale). Make sure
+ // we always have a map entry for a three-letter abbreviation.
+ map_[month_name.substr(0, 3)] = month + 1;
+ }
+ }
+
+ // Fail loudly if the data returned by ICU is obviously incomplete.
+ // This is intended to catch cases like http://crbug.com/177428
+ // much earlier. Note that the issue above turned out to be non-trivial
+ // to reproduce - crash data is much better indicator of a problem
+ // than incomplete bug reports.
+ CHECK_EQ(1, map_[ASCIIToUTF16("jan")]);
+ CHECK_EQ(2, map_[ASCIIToUTF16("feb")]);
+ CHECK_EQ(3, map_[ASCIIToUTF16("mar")]);
+ CHECK_EQ(4, map_[ASCIIToUTF16("apr")]);
+ CHECK_EQ(5, map_[ASCIIToUTF16("may")]);
+ CHECK_EQ(6, map_[ASCIIToUTF16("jun")]);
+ CHECK_EQ(7, map_[ASCIIToUTF16("jul")]);
+ CHECK_EQ(8, map_[ASCIIToUTF16("aug")]);
+ CHECK_EQ(9, map_[ASCIIToUTF16("sep")]);
+ CHECK_EQ(10, map_[ASCIIToUTF16("oct")]);
+ CHECK_EQ(11, map_[ASCIIToUTF16("nov")]);
+ CHECK_EQ(12, map_[ASCIIToUTF16("dec")]);
+ }
+
+ // Maps lowercase month names to numbers in range 1-12.
+ std::map<base::string16, int> map_;
+
+ DISALLOW_COPY_AND_ASSIGN(AbbreviatedMonthsMap);
+};
+
+} // namespace
+
+// static
+bool FtpUtil::AbbreviatedMonthToNumber(const base::string16& text,
+ int* number) {
+ return AbbreviatedMonthsMap::GetInstance()->GetMonthNumber(text, number);
+}
+
+// static
+bool FtpUtil::LsDateListingToTime(const base::string16& month,
+ const base::string16& day,
+ const base::string16& rest,
+ const base::Time& current_time,
+ base::Time* result) {
+ base::Time::Exploded time_exploded = { 0 };
+
+ if (!AbbreviatedMonthToNumber(month, &time_exploded.month)) {
+ // Work around garbage sent by some servers in the same column
+ // as the month. Take just last 3 characters of the string.
+ if (month.length() < 3 ||
+ !AbbreviatedMonthToNumber(month.substr(month.length() - 3),
+ &time_exploded.month)) {
+ return false;
+ }
+ }
+
+ if (!base::StringToInt(day, &time_exploded.day_of_month))
+ return false;
+ if (time_exploded.day_of_month > 31)
+ return false;
+
+ if (!base::StringToInt(rest, &time_exploded.year)) {
+ // Maybe it's time. Does it look like time (HH:MM)?
+ if (rest.length() == 5 && rest[2] == ':') {
+ if (!base::StringToInt(StringPiece16(rest.begin(), rest.begin() + 2),
+ &time_exploded.hour)) {
+ return false;
+ }
+
+ if (!base::StringToInt(StringPiece16(rest.begin() + 3, rest.begin() + 5),
+ &time_exploded.minute)) {
+ return false;
+ }
+ } else if (rest.length() == 4 && rest[1] == ':') {
+ // Sometimes it's just H:MM.
+ if (!base::StringToInt(StringPiece16(rest.begin(), rest.begin() + 1),
+ &time_exploded.hour)) {
+ return false;
+ }
+
+ if (!base::StringToInt(StringPiece16(rest.begin() + 2, rest.begin() + 4),
+ &time_exploded.minute)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ // Guess the year.
+ base::Time::Exploded current_exploded;
+ current_time.LocalExplode(&current_exploded);
+
+ // If it's not possible for the parsed date to be in the current year,
+ // use the previous year.
+ if (time_exploded.month > current_exploded.month ||
+ (time_exploded.month == current_exploded.month &&
+ time_exploded.day_of_month > current_exploded.day_of_month)) {
+ time_exploded.year = current_exploded.year - 1;
+ } else {
+ time_exploded.year = current_exploded.year;
+ }
+ }
+
+ // We don't know the time zone of the listing, so just use local time.
+ *result = base::Time::FromLocalExploded(time_exploded);
+ return true;
+}
+
+// static
+bool FtpUtil::WindowsDateListingToTime(const base::string16& date,
+ const base::string16& time,
+ base::Time* result) {
+ base::Time::Exploded time_exploded = { 0 };
+
+ // Date should be in format MM-DD-YY[YY].
+ std::vector<base::string16> date_parts;
+ base::SplitString(date, '-', &date_parts);
+ if (date_parts.size() != 3)
+ return false;
+ if (!base::StringToInt(date_parts[0], &time_exploded.month))
+ return false;
+ if (!base::StringToInt(date_parts[1], &time_exploded.day_of_month))
+ return false;
+ if (!base::StringToInt(date_parts[2], &time_exploded.year))
+ return false;
+ if (time_exploded.year < 0)
+ return false;
+ // If year has only two digits then assume that 00-79 is 2000-2079,
+ // and 80-99 is 1980-1999.
+ if (time_exploded.year < 80)
+ time_exploded.year += 2000;
+ else if (time_exploded.year < 100)
+ time_exploded.year += 1900;
+
+ // Time should be in format HH:MM[(AM|PM)]
+ if (time.length() < 5)
+ return false;
+
+ std::vector<base::string16> time_parts;
+ base::SplitString(time.substr(0, 5), ':', &time_parts);
+ if (time_parts.size() != 2)
+ return false;
+ if (!base::StringToInt(time_parts[0], &time_exploded.hour))
+ return false;
+ if (!base::StringToInt(time_parts[1], &time_exploded.minute))
+ return false;
+ if (!time_exploded.HasValidValues())
+ return false;
+
+ if (time.length() > 5) {
+ if (time.length() != 7)
+ return false;
+ base::string16 am_or_pm(time.substr(5, 2));
+ if (EqualsASCII(am_or_pm, "PM")) {
+ if (time_exploded.hour < 12)
+ time_exploded.hour += 12;
+ } else if (EqualsASCII(am_or_pm, "AM")) {
+ if (time_exploded.hour == 12)
+ time_exploded.hour = 0;
+ } else {
+ return false;
+ }
+ }
+
+ // We don't know the time zone of the server, so just use local time.
+ *result = base::Time::FromLocalExploded(time_exploded);
+ return true;
+}
+
+// static
+base::string16 FtpUtil::GetStringPartAfterColumns(const base::string16& text,
+ int columns) {
+ base::i18n::UTF16CharIterator iter(&text);
+
+ // TODO(jshin): Is u_isspace the right function to use here?
+ for (int i = 0; i < columns; i++) {
+ // Skip the leading whitespace.
+ while (!iter.end() && u_isspace(iter.get()))
+ iter.Advance();
+
+ // Skip the actual text of i-th column.
+ while (!iter.end() && !u_isspace(iter.get()))
+ iter.Advance();
+ }
+
+ base::string16 result(text.substr(iter.array_pos()));
+ TrimWhitespace(result, TRIM_ALL, &result);
+ return result;
+}
+
+} // namespace
diff --git a/chromium/net/ftp/ftp_util.h b/chromium/net/ftp/ftp_util.h
new file mode 100644
index 00000000000..3e8a899c7d4
--- /dev/null
+++ b/chromium/net/ftp/ftp_util.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2011 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_UTIL_H_
+#define NET_FTP_FTP_UTIL_H_
+
+#include <string>
+
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class Time;
+}
+
+namespace net {
+
+class NET_EXPORT_PRIVATE FtpUtil {
+ public:
+ // Converts Unix file path to VMS path (must be a file, and not a directory).
+ static std::string UnixFilePathToVMS(const std::string& unix_path);
+
+ // Converts Unix directory path to VMS path (must be a directory).
+ static std::string UnixDirectoryPathToVMS(const std::string& unix_path);
+
+ // Converts VMS path to Unix-style path.
+ static std::string VMSPathToUnix(const std::string& vms_path);
+
+ // Converts abbreviated month (like Nov) to its number (in range 1-12).
+ // Note: in some locales abbreviations are more than three letters long,
+ // and this function also handles them correctly.
+ static bool AbbreviatedMonthToNumber(const base::string16& text, int* number);
+
+ // Converts a "ls -l" date listing to time. The listing comes in three
+ // columns. The first one contains month, the second one contains day
+ // of month. The third one is either a time (and then we guess the year based
+ // on |current_time|), or is a year (and then we don't know the time).
+ static bool LsDateListingToTime(const base::string16& month,
+ const base::string16& day,
+ const base::string16& rest,
+ const base::Time& current_time,
+ base::Time* result);
+
+ // Converts a Windows date listing to time. Returns true on success.
+ static bool WindowsDateListingToTime(const base::string16& date,
+ const base::string16& time,
+ base::Time* result);
+
+ // Skips |columns| columns from |text| (whitespace-delimited), and returns the
+ // remaining part, without leading/trailing whitespace.
+ static base::string16 GetStringPartAfterColumns(const base::string16& text,
+ int columns);
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_UTIL_H_
diff --git a/chromium/net/ftp/ftp_util_unittest.cc b/chromium/net/ftp/ftp_util_unittest.cc
new file mode 100644
index 00000000000..2aab7f49f5c
--- /dev/null
+++ b/chromium/net/ftp/ftp_util_unittest.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 2011 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_util.h"
+
+#include "base/basictypes.h"
+#include "base/format_macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(FtpUtilTest, UnixFilePathToVMS) {
+ const struct {
+ const char* input;
+ const char* expected_output;
+ } kTestCases[] = {
+ { "", "" },
+ { "/", "[]" },
+ { "/a", "a" },
+ { "/a/b", "a:[000000]b" },
+ { "/a/b/c", "a:[b]c" },
+ { "/a/b/c/d", "a:[b.c]d" },
+ { "/a/b/c/d/e", "a:[b.c.d]e" },
+ { "a", "a" },
+ { "a/b", "[.a]b" },
+ { "a/b/c", "[.a.b]c" },
+ { "a/b/c/d", "[.a.b.c]d" },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
+ EXPECT_EQ(kTestCases[i].expected_output,
+ net::FtpUtil::UnixFilePathToVMS(kTestCases[i].input))
+ << kTestCases[i].input;
+ }
+}
+
+TEST(FtpUtilTest, UnixDirectoryPathToVMS) {
+ const struct {
+ const char* input;
+ const char* expected_output;
+ } kTestCases[] = {
+ { "", "" },
+ { "/", "" },
+ { "/a", "a:[000000]" },
+ { "/a/", "a:[000000]" },
+ { "/a/b", "a:[b]" },
+ { "/a/b/", "a:[b]" },
+ { "/a/b/c", "a:[b.c]" },
+ { "/a/b/c/", "a:[b.c]" },
+ { "/a/b/c/d", "a:[b.c.d]" },
+ { "/a/b/c/d/", "a:[b.c.d]" },
+ { "/a/b/c/d/e", "a:[b.c.d.e]" },
+ { "/a/b/c/d/e/", "a:[b.c.d.e]" },
+ { "a", "[.a]" },
+ { "a/", "[.a]" },
+ { "a/b", "[.a.b]" },
+ { "a/b/", "[.a.b]" },
+ { "a/b/c", "[.a.b.c]" },
+ { "a/b/c/", "[.a.b.c]" },
+ { "a/b/c/d", "[.a.b.c.d]" },
+ { "a/b/c/d/", "[.a.b.c.d]" },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
+ EXPECT_EQ(kTestCases[i].expected_output,
+ net::FtpUtil::UnixDirectoryPathToVMS(kTestCases[i].input))
+ << kTestCases[i].input;
+ }
+}
+
+TEST(FtpUtilTest, VMSPathToUnix) {
+ const struct {
+ const char* input;
+ const char* expected_output;
+ } kTestCases[] = {
+ { "", "." },
+ { "[]", "/" },
+ { "a", "/a" },
+ { "a:[000000]", "/a" },
+ { "a:[000000]b", "/a/b" },
+ { "a:[b]", "/a/b" },
+ { "a:[b]c", "/a/b/c" },
+ { "a:[b.c]", "/a/b/c" },
+ { "a:[b.c]d", "/a/b/c/d" },
+ { "a:[b.c.d]", "/a/b/c/d" },
+ { "a:[b.c.d]e", "/a/b/c/d/e" },
+ { "a:[b.c.d.e]", "/a/b/c/d/e" },
+ { "[.a]", "a" },
+ { "[.a]b", "a/b" },
+ { "[.a.b]", "a/b" },
+ { "[.a.b]c", "a/b/c" },
+ { "[.a.b.c]", "a/b/c" },
+ { "[.a.b.c]d", "a/b/c/d" },
+ { "[.a.b.c.d]", "a/b/c/d" },
+ { "[.", "" },
+
+ // UNIX emulation:
+ { "/", "/" },
+ { "/a", "/a" },
+ { "/a/b", "/a/b" },
+ { "/a/b/c", "/a/b/c" },
+ { "/a/b/c/d", "/a/b/c/d" },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
+ EXPECT_EQ(kTestCases[i].expected_output,
+ net::FtpUtil::VMSPathToUnix(kTestCases[i].input))
+ << kTestCases[i].input;
+ }
+}
+
+TEST(FtpUtilTest, LsDateListingToTime) {
+ base::Time mock_current_time;
+ ASSERT_TRUE(base::Time::FromString("Tue, 15 Nov 1994 12:45:26 GMT",
+ &mock_current_time));
+
+ const struct {
+ // Input.
+ const char* month;
+ const char* day;
+ const char* rest;
+
+ // Expected output.
+ int expected_year;
+ int expected_month;
+ int expected_day_of_month;
+ int expected_hour;
+ int expected_minute;
+ } kTestCases[] = {
+ { "Nov", "01", "2007", 2007, 11, 1, 0, 0 },
+ { "Jul", "25", "13:37", 1994, 7, 25, 13, 37 },
+
+ // Test date listings in German.
+ { "M\xc3\xa4r", "13", "2009", 2009, 3, 13, 0, 0 },
+ { "Mai", "1", "10:10", 1994, 5, 1, 10, 10 },
+ { "Okt", "14", "21:18", 1994, 10, 14, 21, 18 },
+ { "Dez", "25", "2008", 2008, 12, 25, 0, 0 },
+
+ // Test date listings in Russian.
+ { "\xd1\x8f\xd0\xbd\xd0\xb2", "1", "2011", 2011, 1, 1, 0, 0 },
+ { "\xd1\x84\xd0\xb5\xd0\xb2", "1", "2011", 2011, 2, 1, 0, 0 },
+ { "\xd0\xbc\xd0\xb0\xd1\x80", "1", "2011", 2011, 3, 1, 0, 0 },
+ { "\xd0\xb0\xd0\xbf\xd1\x80", "1", "2011", 2011, 4, 1, 0, 0 },
+ { "\xd0\xbc\xd0\xb0\xd0\xb9", "1", "2011", 2011, 5, 1, 0, 0 },
+ { "\xd0\xb8\xd1\x8e\xd0\xbd", "1", "2011", 2011, 6, 1, 0, 0 },
+ { "\xd0\xb8\xd1\x8e\xd0\xbb", "1", "2011", 2011, 7, 1, 0, 0 },
+ { "\xd0\xb0\xd0\xb2\xd0\xb3", "1", "2011", 2011, 8, 1, 0, 0 },
+ { "\xd1\x81\xd0\xb5\xd0\xbd", "1", "2011", 2011, 9, 1, 0, 0 },
+ { "\xd0\xbe\xd0\xba\xd1\x82", "1", "2011", 2011, 10, 1, 0, 0 },
+ { "\xd0\xbd\xd0\xbe\xd1\x8f", "1", "2011", 2011, 11, 1, 0, 0 },
+ { "\xd0\xb4\xd0\xb5\xd0\xba", "1", "2011", 2011, 12, 1, 0, 0 },
+
+ // Test current year detection.
+ { "Nov", "01", "12:00", 1994, 11, 1, 12, 0 },
+ { "Nov", "15", "12:00", 1994, 11, 15, 12, 0 },
+ { "Nov", "16", "12:00", 1993, 11, 16, 12, 0 },
+ { "Jan", "01", "08:30", 1994, 1, 1, 8, 30 },
+ { "Sep", "02", "09:00", 1994, 9, 2, 9, 0 },
+ { "Dec", "06", "21:00", 1993, 12, 6, 21, 0 },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s %s %s", i,
+ kTestCases[i].month, kTestCases[i].day,
+ kTestCases[i].rest));
+
+ base::Time time;
+ ASSERT_TRUE(net::FtpUtil::LsDateListingToTime(
+ UTF8ToUTF16(kTestCases[i].month), UTF8ToUTF16(kTestCases[i].day),
+ UTF8ToUTF16(kTestCases[i].rest), mock_current_time, &time));
+
+ base::Time::Exploded time_exploded;
+ time.LocalExplode(&time_exploded);
+ EXPECT_EQ(kTestCases[i].expected_year, time_exploded.year);
+ EXPECT_EQ(kTestCases[i].expected_month, time_exploded.month);
+ EXPECT_EQ(kTestCases[i].expected_day_of_month, time_exploded.day_of_month);
+ EXPECT_EQ(kTestCases[i].expected_hour, time_exploded.hour);
+ EXPECT_EQ(kTestCases[i].expected_minute, time_exploded.minute);
+ EXPECT_EQ(0, time_exploded.second);
+ EXPECT_EQ(0, time_exploded.millisecond);
+ }
+}
+
+TEST(FtpUtilTest, WindowsDateListingToTime) {
+ const struct {
+ // Input.
+ const char* date;
+ const char* time;
+
+ // Expected output.
+ int expected_year;
+ int expected_month;
+ int expected_day_of_month;
+ int expected_hour;
+ int expected_minute;
+ } kTestCases[] = {
+ { "11-01-07", "12:42", 2007, 11, 1, 12, 42 },
+ { "11-01-07", "12:42AM", 2007, 11, 1, 0, 42 },
+ { "11-01-07", "12:42PM", 2007, 11, 1, 12, 42 },
+
+ { "11-01-2007", "12:42", 2007, 11, 1, 12, 42 },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s %s", i,
+ kTestCases[i].date, kTestCases[i].time));
+
+ base::Time time;
+ ASSERT_TRUE(net::FtpUtil::WindowsDateListingToTime(
+ UTF8ToUTF16(kTestCases[i].date),
+ UTF8ToUTF16(kTestCases[i].time),
+ &time));
+
+ base::Time::Exploded time_exploded;
+ time.LocalExplode(&time_exploded);
+ EXPECT_EQ(kTestCases[i].expected_year, time_exploded.year);
+ EXPECT_EQ(kTestCases[i].expected_month, time_exploded.month);
+ EXPECT_EQ(kTestCases[i].expected_day_of_month, time_exploded.day_of_month);
+ EXPECT_EQ(kTestCases[i].expected_hour, time_exploded.hour);
+ EXPECT_EQ(kTestCases[i].expected_minute, time_exploded.minute);
+ EXPECT_EQ(0, time_exploded.second);
+ EXPECT_EQ(0, time_exploded.millisecond);
+ }
+}
+
+TEST(FtpUtilTest, GetStringPartAfterColumns) {
+ const struct {
+ const char* text;
+ int column;
+ const char* expected_result;
+ } kTestCases[] = {
+ { "", 0, "" },
+ { "", 1, "" },
+ { "foo abc", 0, "foo abc" },
+ { "foo abc", 1, "abc" },
+ { " foo abc", 0, "foo abc" },
+ { " foo abc", 1, "abc" },
+ { " foo abc", 2, "" },
+ { " foo abc ", 0, "foo abc" },
+ { " foo abc ", 1, "abc" },
+ { " foo abc ", 2, "" },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s %d", i,
+ kTestCases[i].text, kTestCases[i].column));
+
+ EXPECT_EQ(ASCIIToUTF16(kTestCases[i].expected_result),
+ net::FtpUtil::GetStringPartAfterColumns(
+ ASCIIToUTF16(kTestCases[i].text), kTestCases[i].column));
+ }
+}
+
+} // namespace