summaryrefslogtreecommitdiffstats
path: root/net/ftp
diff options
context:
space:
mode:
authorphajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-12-04 09:58:32 +0000
committerphajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-12-04 09:58:32 +0000
commita115b010733df8d53f4f6cc0f66cb90b7a8069dc (patch)
treee2aed2c2980b7061e6b36b1967f19edd28a07678 /net/ftp
parent76ae486cba7db7b2adada0716ccbbb846371363d (diff)
downloadchromium_src-a115b010733df8d53f4f6cc0f66cb90b7a8069dc.zip
chromium_src-a115b010733df8d53f4f6cc0f66cb90b7a8069dc.tar.gz
chromium_src-a115b010733df8d53f4f6cc0f66cb90b7a8069dc.tar.bz2
Split FTP LIST parsing code into individual files for each listing style.
This turns out to be much better code organisation strategy. We're going to have even more parsers. TEST=Covered by net_unittests. BUG=25520 Review URL: http://codereview.chromium.org/465035 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@33807 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/ftp')
-rw-r--r--net/ftp/ftp_directory_listing_buffer.cc10
-rw-r--r--net/ftp/ftp_directory_listing_buffer_unittest.cc2
-rw-r--r--net/ftp/ftp_directory_listing_parser.cc12
-rw-r--r--net/ftp/ftp_directory_listing_parser.h53
-rw-r--r--net/ftp/ftp_directory_listing_parser_ls.cc191
-rw-r--r--net/ftp/ftp_directory_listing_parser_ls.h36
-rw-r--r--net/ftp/ftp_directory_listing_parser_ls_unittest.cc69
-rw-r--r--net/ftp/ftp_directory_listing_parser_unittest.h59
-rw-r--r--net/ftp/ftp_directory_listing_parser_vms.cc295
-rw-r--r--net/ftp/ftp_directory_listing_parser_vms.h62
-rw-r--r--net/ftp/ftp_directory_listing_parser_vms_unittest.cc118
-rw-r--r--net/ftp/ftp_directory_listing_parser_windows.cc109
-rw-r--r--net/ftp/ftp_directory_listing_parser_windows.h33
-rw-r--r--net/ftp/ftp_directory_listing_parser_windows_unittest.cc74
-rw-r--r--net/ftp/ftp_directory_listing_parsers.cc601
-rw-r--r--net/ftp/ftp_directory_listing_parsers.h138
-rw-r--r--net/ftp/ftp_directory_listing_parsers_unittest.cc267
-rw-r--r--net/ftp/ftp_util.cc40
-rw-r--r--net/ftp/ftp_util.h6
19 files changed, 1164 insertions, 1011 deletions
diff --git a/net/ftp/ftp_directory_listing_buffer.cc b/net/ftp/ftp_directory_listing_buffer.cc
index 19483d0..a74b7fe 100644
--- a/net/ftp/ftp_directory_listing_buffer.cc
+++ b/net/ftp/ftp_directory_listing_buffer.cc
@@ -8,7 +8,9 @@
#include "base/stl_util-inl.h"
#include "base/string_util.h"
#include "net/base/net_errors.h"
-#include "net/ftp/ftp_directory_listing_parsers.h"
+#include "net/ftp/ftp_directory_listing_parser_ls.h"
+#include "net/ftp/ftp_directory_listing_parser_vms.h"
+#include "net/ftp/ftp_directory_listing_parser_windows.h"
#include "unicode/ucsdet.h"
namespace {
@@ -41,9 +43,9 @@ namespace net {
FtpDirectoryListingBuffer::FtpDirectoryListingBuffer()
: current_parser_(NULL) {
- parsers_.insert(new FtpLsDirectoryListingParser());
- parsers_.insert(new FtpWindowsDirectoryListingParser());
- parsers_.insert(new FtpVmsDirectoryListingParser());
+ parsers_.insert(new FtpDirectoryListingParserLs());
+ parsers_.insert(new FtpDirectoryListingParserVms());
+ parsers_.insert(new FtpDirectoryListingParserWindows());
}
FtpDirectoryListingBuffer::~FtpDirectoryListingBuffer() {
diff --git a/net/ftp/ftp_directory_listing_buffer_unittest.cc b/net/ftp/ftp_directory_listing_buffer_unittest.cc
index 9aaa16f..d5063c2 100644
--- a/net/ftp/ftp_directory_listing_buffer_unittest.cc
+++ b/net/ftp/ftp_directory_listing_buffer_unittest.cc
@@ -10,7 +10,7 @@
#include "base/string_tokenizer.h"
#include "base/string_util.h"
#include "net/base/net_errors.h"
-#include "net/ftp/ftp_directory_listing_parsers.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
diff --git a/net/ftp/ftp_directory_listing_parser.cc b/net/ftp/ftp_directory_listing_parser.cc
new file mode 100644
index 0000000..2f3eeca
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser.cc
@@ -0,0 +1,12 @@
+// 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_directory_listing_parser.h"
+
+namespace net {
+
+FtpDirectoryListingParser::~FtpDirectoryListingParser() {
+}
+
+} // namespace
diff --git a/net/ftp/ftp_directory_listing_parser.h b/net/ftp/ftp_directory_listing_parser.h
new file mode 100644
index 0000000..7ba895a
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser.h
@@ -0,0 +1,53 @@
+// 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_DIRECTORY_LISTING_PARSER_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_H_
+
+#include "base/basictypes.h"
+#include "base/string16.h"
+#include "base/time.h"
+#include "net/ftp/ftp_server_type_histograms.h"
+
+namespace net {
+
+struct FtpDirectoryListingEntry {
+ enum Type {
+ FILE,
+ DIRECTORY,
+ SYMLINK,
+ };
+
+ Type type;
+ string16 name;
+ int64 size; // File size, in bytes. -1 if not applicable.
+
+ // Last modified time, in local time zone.
+ base::Time last_modified;
+};
+
+class FtpDirectoryListingParser {
+ public:
+ virtual ~FtpDirectoryListingParser();
+
+ virtual FtpServerType GetServerType() const = 0;
+
+ // Adds |line| to the internal parsing buffer. Returns true on success.
+ virtual bool ConsumeLine(const string16& line) = 0;
+
+ // Called after all input has been consumed. Returns true if the parser
+ // recognizes all received data as a valid listing.
+ virtual bool OnEndOfInput() = 0;
+
+ // Returns true if there is at least one FtpDirectoryListingEntry available.
+ virtual bool EntryAvailable() const = 0;
+
+ // Returns the next entry. It is an error to call this function unless
+ // EntryAvailable returns true.
+ virtual FtpDirectoryListingEntry PopEntry() = 0;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_H_
diff --git a/net/ftp/ftp_directory_listing_parser_ls.cc b/net/ftp/ftp_directory_listing_parser_ls.cc
new file mode 100644
index 0000000..195f4a4
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser_ls.cc
@@ -0,0 +1,191 @@
+// 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_directory_listing_parser_ls.h"
+
+#include <vector>
+
+#include "base/string_util.h"
+#include "net/ftp/ftp_util.h"
+
+namespace {
+
+bool LooksLikeUnixPermission(const string16& text) {
+ if (text.length() != 3)
+ return false;
+
+ // Meaning of the flags:
+ // r - file is readable
+ // w - file is writable
+ // x - file is executable
+ // s or S - setuid/setgid bit set
+ // t or T - "sticky" bit set
+ return ((text[0] == 'r' || text[0] == '-') &&
+ (text[1] == 'w' || text[1] == '-') &&
+ (text[2] == 'x' || text[2] == 's' || text[2] == 'S' ||
+ text[2] == 't' || text[2] == 'T' || text[2] == '-'));
+}
+
+bool LooksLikeUnixPermissionsListing(const string16& text) {
+ if (text.length() != 10)
+ return false;
+
+ if (text[0] != 'b' && text[0] != 'c' && text[0] != 'd' &&
+ text[0] != 'l' && text[0] != 'p' && text[0] != 's' &&
+ text[0] != '-')
+ return false;
+
+ return (LooksLikeUnixPermission(text.substr(1, 3)) &&
+ LooksLikeUnixPermission(text.substr(4, 3)) &&
+ LooksLikeUnixPermission(text.substr(7, 3)));
+}
+
+string16 GetStringPartAfterColumns(const string16& text, int columns) {
+ DCHECK_LE(1, columns);
+ int columns_so_far = 0;
+ size_t last = 0;
+ for (size_t i = 1; i < text.length(); ++i) {
+ if (!isspace(text[i - 1]) && isspace(text[i])) {
+ last = i;
+ if (++columns_so_far == columns)
+ break;
+ }
+ }
+ string16 result(text.substr(last));
+ TrimWhitespace(result, TRIM_ALL, &result);
+ return result;
+}
+
+bool UnixDateListingToTime(const std::vector<string16>& columns,
+ base::Time* time) {
+ DCHECK_LE(9U, columns.size());
+
+ base::Time::Exploded time_exploded = { 0 };
+
+ if (!net::FtpUtil::ThreeLetterMonthToNumber(columns[5], &time_exploded.month))
+ return false;
+
+ if (!StringToInt(columns[6], &time_exploded.day_of_month))
+ return false;
+
+ if (!StringToInt(columns[7], &time_exploded.year)) {
+ // Maybe it's time. Does it look like time (MM:HH)?
+ if (columns[7].length() != 5 || columns[7][2] != ':')
+ return false;
+
+ if (!StringToInt(columns[7].substr(0, 2), &time_exploded.hour))
+ return false;
+
+ if (!StringToInt(columns[7].substr(3, 2), &time_exploded.minute))
+ return false;
+
+ // Use current year.
+ base::Time::Exploded now_exploded;
+ base::Time::Now().LocalExplode(&now_exploded);
+ time_exploded.year = now_exploded.year;
+ }
+
+ // We don't know the time zone of the server, so just use local time.
+ *time = base::Time::FromLocalExploded(time_exploded);
+ return true;
+}
+
+} // namespace
+
+namespace net {
+
+FtpDirectoryListingParserLs::FtpDirectoryListingParserLs()
+ : received_nonempty_line_(false) {
+}
+
+bool FtpDirectoryListingParserLs::ConsumeLine(const string16& line) {
+ if (StartsWith(line, ASCIIToUTF16("total "), true) ||
+ StartsWith(line, ASCIIToUTF16("Gesamt "), true)) {
+ // 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.
+ //
+ // Note: "Gesamt" is a German word for "total". The case is important here:
+ // for "ls -l" style listings, "total" will be lowercase, and Gesamt will be
+ // capitalized. This helps us distinguish that from a VMS-style listing,
+ // which would use "Total" (note the uppercase first letter).
+
+ if (received_nonempty_line_)
+ return false;
+
+ received_nonempty_line_ = true;
+ return true;
+ }
+ if (line.empty() && !received_nonempty_line_) {
+ // Allow empty lines only at the beginning of the listing. For example VMS
+ // systems in Unix emulation mode add an empty line before the first listing
+ // entry.
+ return true;
+ }
+ received_nonempty_line_ = true;
+
+ std::vector<string16> columns;
+ SplitString(CollapseWhitespace(line, false), ' ', &columns);
+
+ // We may receive file names containing spaces, which can make the number of
+ // columns arbitrarily large. We will handle that later. For now just make
+ // sure we have all the columns that should normally be there.
+ if (columns.size() < 9)
+ return false;
+
+ if (!LooksLikeUnixPermissionsListing(columns[0]))
+ return false;
+
+ FtpDirectoryListingEntry entry;
+ if (columns[0][0] == 'l') {
+ entry.type = FtpDirectoryListingEntry::SYMLINK;
+ } else if (columns[0][0] == 'd') {
+ entry.type = FtpDirectoryListingEntry::DIRECTORY;
+ } else {
+ entry.type = FtpDirectoryListingEntry::FILE;
+ }
+
+ int number_of_links;
+ if (!StringToInt(columns[1], &number_of_links))
+ return false;
+ if (number_of_links < 0)
+ return false;
+
+ if (!StringToInt64(columns[4], &entry.size))
+ return false;
+ if (entry.size < 0)
+ return false;
+ if (entry.type != FtpDirectoryListingEntry::FILE)
+ entry.size = -1;
+
+ if (!UnixDateListingToTime(columns, &entry.last_modified))
+ return false;
+
+ entry.name = GetStringPartAfterColumns(line, 8);
+ if (entry.type == FtpDirectoryListingEntry::SYMLINK) {
+ string16::size_type pos = entry.name.rfind(ASCIIToUTF16(" -> "));
+ if (pos == string16::npos)
+ return false;
+ entry.name = entry.name.substr(0, pos);
+ }
+
+ entries_.push(entry);
+ return true;
+}
+
+bool FtpDirectoryListingParserLs::OnEndOfInput() {
+ return true;
+}
+
+bool FtpDirectoryListingParserLs::EntryAvailable() const {
+ return !entries_.empty();
+}
+
+FtpDirectoryListingEntry FtpDirectoryListingParserLs::PopEntry() {
+ FtpDirectoryListingEntry entry = entries_.front();
+ entries_.pop();
+ return entry;
+}
+
+} // namespace net
diff --git a/net/ftp/ftp_directory_listing_parser_ls.h b/net/ftp/ftp_directory_listing_parser_ls.h
new file mode 100644
index 0000000..689fb7e
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser_ls.h
@@ -0,0 +1,36 @@
+// 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_DIRECTORY_LISTING_PARSER_LS_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_LS_H_
+
+#include <queue>
+
+#include "net/ftp/ftp_directory_listing_parser.h"
+
+namespace net {
+
+// Parser for "ls -l"-style directory listing.
+class FtpDirectoryListingParserLs : public FtpDirectoryListingParser {
+ public:
+ FtpDirectoryListingParserLs();
+
+ // FtpDirectoryListingParser methods:
+ virtual FtpServerType GetServerType() const { return SERVER_LS; }
+ virtual bool ConsumeLine(const string16& line);
+ virtual bool OnEndOfInput();
+ virtual bool EntryAvailable() const;
+ virtual FtpDirectoryListingEntry PopEntry();
+
+ private:
+ bool received_nonempty_line_;
+
+ std::queue<FtpDirectoryListingEntry> entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpDirectoryListingParserLs);
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_LS_H_
diff --git a/net/ftp/ftp_directory_listing_parser_ls_unittest.cc b/net/ftp/ftp_directory_listing_parser_ls_unittest.cc
new file mode 100644
index 0000000..a58b839
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser_ls_unittest.cc
@@ -0,0 +1,69 @@
+// 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_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "net/ftp/ftp_directory_listing_parser_ls.h"
+
+namespace {
+
+typedef net::FtpDirectoryListingParserTest FtpDirectoryListingParserLsTest;
+
+TEST_F(FtpDirectoryListingParserLsTest, Good) {
+ base::Time::Exploded now_exploded;
+ base::Time::Now().LocalExplode(&now_exploded);
+
+ const struct SingleLineTestData good_cases[] = {
+ { "-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 README",
+ net::FtpDirectoryListingEntry::FILE, "README", 528,
+ 2007, 11, 1, 0, 0 },
+ { "drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 directory",
+ net::FtpDirectoryListingEntry::DIRECTORY, "directory", -1,
+ now_exploded.year, 5, 15, 18, 11 },
+ { "lrwxrwxrwx 1 0 0 26 Sep 18 2008 pub -> vol/1/.CLUSTER/var_ftp/pub",
+ net::FtpDirectoryListingEntry::SYMLINK, "pub", -1,
+ 2008, 9, 18, 0, 0 },
+ { "lrwxrwxrwx 1 0 0 3 Oct 12 13:37 mirror -> pub",
+ net::FtpDirectoryListingEntry::SYMLINK, "mirror", -1,
+ now_exploded.year, 10, 12, 13, 37 },
+ { "drwxrwsr-x 4 501 501 4096 Feb 20 2007 pub",
+ net::FtpDirectoryListingEntry::DIRECTORY, "pub", -1,
+ 2007, 2, 20, 0, 0 },
+ { "drwxr-xr-x 4 (?) (?) 4096 Apr 8 2007 jigdo",
+ net::FtpDirectoryListingEntry::DIRECTORY, "jigdo", -1,
+ 2007, 4, 8, 0, 0 },
+ { "drwx-wx-wt 2 root wheel 512 Jul 1 02:15 incoming",
+ net::FtpDirectoryListingEntry::DIRECTORY, "incoming", -1,
+ now_exploded.year, 7, 1, 2, 15 },
+ { "-rw-r--r-- 1 2 3 3447432 May 18 2009 Foo - Manual.pdf",
+ net::FtpDirectoryListingEntry::FILE, "Foo - Manual.pdf", 3447432,
+ 2009, 5, 18, 0, 0 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s", i, good_cases[i].input));
+
+ net::FtpDirectoryListingParserLs parser;
+ RunSingleLineTestCase(&parser, good_cases[i]);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserLsTest, Bad) {
+ const char* bad_cases[] = {
+ "garbage",
+ "-rw-r--r-- 1 ftp ftp",
+ "-rw-r--rgb 1 ftp ftp 528 Nov 01 2007 README",
+ "-rw-rgbr-- 1 ftp ftp 528 Nov 01 2007 README",
+ "qrwwr--r-- 1 ftp ftp 528 Nov 01 2007 README",
+ "-rw-r--r-- -1 ftp ftp 528 Nov 01 2007 README",
+ "-rw-r--r-- 1 ftp ftp -528 Nov 01 2007 README",
+ "-rw-r--r-- 1 ftp ftp 528 Foo 01 2007 README",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ net::FtpDirectoryListingParserLs parser;
+ EXPECT_FALSE(parser.ConsumeLine(UTF8ToUTF16(bad_cases[i]))) << bad_cases[i];
+ }
+}
+
+} // namespace
diff --git a/net/ftp/ftp_directory_listing_parser_unittest.h b/net/ftp/ftp_directory_listing_parser_unittest.h
new file mode 100644
index 0000000..7653d21
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser_unittest.h
@@ -0,0 +1,59 @@
+// 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_DIRECTORY_LISTING_PARSER_UNITTEST_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_UNITTEST_H_
+
+#include "base/string_util.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() {
+ }
+
+ void RunSingleLineTestCase(FtpDirectoryListingParser* parser,
+ const SingleLineTestData& test_case) {
+ ASSERT_TRUE(parser->ConsumeLine(UTF8ToUTF16(test_case.input)));
+ ASSERT_TRUE(parser->EntryAvailable());
+ FtpDirectoryListingEntry entry = parser->PopEntry();
+ 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);
+ 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(0, time_exploded.second);
+ EXPECT_EQ(0, time_exploded.millisecond);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpDirectoryListingParserTest);
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_UNITTEST_H_
+
diff --git a/net/ftp/ftp_directory_listing_parser_vms.cc b/net/ftp/ftp_directory_listing_parser_vms.cc
new file mode 100644
index 0000000..bfb14d4
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser_vms.cc
@@ -0,0 +1,295 @@
+// 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_directory_listing_parser_vms.h"
+
+#include <vector>
+
+#include "base/string_util.h"
+#include "net/ftp/ftp_util.h"
+
+namespace {
+
+// Converts the filename component in listing to the filename we can display.
+// Returns true on success.
+bool ParseVmsFilename(const string16& raw_filename, string16* parsed_filename,
+ bool* is_directory) {
+ // 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<string16> listing_parts;
+ SplitString(raw_filename, ';', &listing_parts);
+ if (listing_parts.size() != 2)
+ return false;
+ int version_number;
+ if (!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<string16> filename_parts;
+ 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]);
+ *is_directory = true;
+ } else {
+ *parsed_filename = StringToLowerASCII(listing_parts[0]);
+ *is_directory = false;
+ }
+ return true;
+}
+
+bool ParseVmsFilesize(const string16& input, int64* size) {
+ // 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 (StringToInt64(input, size)) {
+ *size *= kBlockSize;
+ return true;
+ }
+
+ std::vector<string16> parts;
+ SplitString(input, '/', &parts);
+ if (parts.size() != 2)
+ return false;
+
+ int64 blocks_used, blocks_allocated;
+ if (!StringToInt64(parts[0], &blocks_used))
+ return false;
+ if (!StringToInt64(parts[1], &blocks_allocated))
+ return false;
+ if (blocks_used > blocks_allocated)
+ return false;
+
+ *size = blocks_used * kBlockSize;
+ return true;
+}
+
+bool LooksLikeVmsFileProtectionListingPart(const 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");
+ 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 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<string16> parts;
+ 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 string16& input) {
+ if (input.length() < 2)
+ return false;
+ return input[0] == '[' && input[input.length() - 1] == ']';
+}
+
+bool VmsDateListingToTime(const std::vector<string16>& columns,
+ base::Time* time) {
+ DCHECK_EQ(3U, columns.size());
+
+ base::Time::Exploded time_exploded = { 0 };
+
+ // Date should be in format DD-MMM-YYYY.
+ std::vector<string16> date_parts;
+ SplitString(columns[1], '-', &date_parts);
+ if (date_parts.size() != 3)
+ return false;
+ if (!StringToInt(date_parts[0], &time_exploded.day_of_month))
+ return false;
+ if (!net::FtpUtil::ThreeLetterMonthToNumber(date_parts[1],
+ &time_exploded.month))
+ return false;
+ if (!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.
+ string16 time_column(columns[2]);
+ 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<string16> time_parts;
+ SplitString(time_column, ':', &time_parts);
+ if (time_parts.size() != 2)
+ return false;
+ if (!StringToInt(time_parts[0], &time_exploded.hour))
+ return false;
+ if (!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
+
+namespace net {
+
+FtpDirectoryListingParserVms::FtpDirectoryListingParserVms()
+ : state_(STATE_INITIAL),
+ last_is_directory_(false) {
+}
+
+bool FtpDirectoryListingParserVms::ConsumeLine(const string16& line) {
+ switch (state_) {
+ case STATE_INITIAL:
+ DCHECK(last_filename_.empty());
+ if (line.empty())
+ return true;
+ if (StartsWith(line, ASCIIToUTF16("Total of "), true)) {
+ state_ = STATE_END;
+ return true;
+ }
+ // We assume that the first non-empty line is the listing header. It often
+ // starts with "Directory ", but not always.
+ state_ = STATE_RECEIVED_HEADER;
+ return true;
+ case STATE_RECEIVED_HEADER:
+ DCHECK(last_filename_.empty());
+ if (line.empty())
+ return true;
+ state_ = STATE_ENTRIES;
+ return ConsumeEntryLine(line);
+ case STATE_ENTRIES:
+ if (line.empty()) {
+ if (!last_filename_.empty())
+ return false;
+ state_ = STATE_RECEIVED_LAST_ENTRY;
+ return true;
+ }
+ return ConsumeEntryLine(line);
+ case STATE_RECEIVED_LAST_ENTRY:
+ DCHECK(last_filename_.empty());
+ if (line.empty())
+ return true;
+ if (!StartsWith(line, ASCIIToUTF16("Total of "), true))
+ return false;
+ state_ = STATE_END;
+ return true;
+ case STATE_END:
+ DCHECK(last_filename_.empty());
+ return false;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+bool FtpDirectoryListingParserVms::OnEndOfInput() {
+ return (state_ == STATE_END);
+}
+
+bool FtpDirectoryListingParserVms::EntryAvailable() const {
+ return !entries_.empty();
+}
+
+FtpDirectoryListingEntry FtpDirectoryListingParserVms::PopEntry() {
+ FtpDirectoryListingEntry entry = entries_.front();
+ entries_.pop();
+ return entry;
+}
+
+bool FtpDirectoryListingParserVms::ConsumeEntryLine(const string16& line) {
+ std::vector<string16> columns;
+ SplitString(CollapseWhitespace(line, false), ' ', &columns);
+
+ if (columns.size() == 1) {
+ if (!last_filename_.empty())
+ return false;
+ return ParseVmsFilename(columns[0], &last_filename_, &last_is_directory_);
+ }
+
+ // Recognize listing entries which generate "access denied" message even when
+ // trying to list them. We don't display them in the final listing.
+ static const char* kAccessDeniedMessages[] = {
+ "%RMS-E-PRV",
+ "privilege",
+ };
+ for (size_t i = 0; i < arraysize(kAccessDeniedMessages); i++) {
+ if (line.find(ASCIIToUTF16(kAccessDeniedMessages[i])) != string16::npos) {
+ last_filename_.clear();
+ last_is_directory_ = false;
+ return true;
+ }
+ }
+
+ string16 filename;
+ bool is_directory = false;
+ if (last_filename_.empty()) {
+ if (!ParseVmsFilename(columns[0], &filename, &is_directory))
+ return false;
+ columns.erase(columns.begin());
+ } else {
+ filename = last_filename_;
+ is_directory = last_is_directory_;
+ last_filename_.clear();
+ last_is_directory_ = false;
+ }
+
+ if (columns.size() > 5)
+ return false;
+
+ if (columns.size() == 5) {
+ if (!LooksLikeVmsFileProtectionListing(columns[4]))
+ return false;
+ if (!LooksLikeVmsUserIdentificationCode(columns[3]))
+ return false;
+ columns.resize(3);
+ }
+
+ if (columns.size() != 3)
+ return false;
+
+ FtpDirectoryListingEntry entry;
+ entry.name = filename;
+ entry.type = is_directory ? FtpDirectoryListingEntry::DIRECTORY
+ : FtpDirectoryListingEntry::FILE;
+ if (!ParseVmsFilesize(columns[0], &entry.size))
+ return false;
+ if (entry.size < 0)
+ return false;
+ if (entry.type != FtpDirectoryListingEntry::FILE)
+ entry.size = -1;
+ if (!VmsDateListingToTime(columns, &entry.last_modified))
+ return false;
+
+ entries_.push(entry);
+ return true;
+}
+
+} // namespace net
diff --git a/net/ftp/ftp_directory_listing_parser_vms.h b/net/ftp/ftp_directory_listing_parser_vms.h
new file mode 100644
index 0000000..1512b63
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser_vms.h
@@ -0,0 +1,62 @@
+// 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_DIRECTORY_LISTING_PARSER_VMS_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_VMS_H_
+
+#include <queue>
+
+#include "net/ftp/ftp_directory_listing_parser.h"
+
+namespace net {
+
+// Parser for VMS-style directory listing (including variants).
+class FtpDirectoryListingParserVms : public FtpDirectoryListingParser {
+ public:
+ FtpDirectoryListingParserVms();
+
+ // FtpDirectoryListingParser methods:
+ virtual FtpServerType GetServerType() const { return SERVER_VMS; }
+ virtual bool ConsumeLine(const string16& line);
+ virtual bool OnEndOfInput();
+ virtual bool EntryAvailable() const;
+ virtual FtpDirectoryListingEntry PopEntry();
+
+ private:
+ // Consumes listing line which is expected to be a directory listing entry
+ // (and not a comment etc). Returns true on success.
+ bool ConsumeEntryLine(const string16& line);
+
+ enum State {
+ STATE_INITIAL,
+
+ // Indicates that we have received the header, like this:
+ // Directory SYS$SYSDEVICE:[ANONYMOUS]
+ STATE_RECEIVED_HEADER,
+
+ // Indicates that we have received the first listing entry, like this:
+ // MADGOAT.DIR;1 2 9-MAY-2001 22:23:44.85
+ STATE_ENTRIES,
+
+ // Indicates that we have received the last listing entry.
+ STATE_RECEIVED_LAST_ENTRY,
+
+ // Indicates that we have successfully received all parts of the listing.
+ STATE_END,
+ } state_;
+
+ // VMS can use two physical lines if the filename is long. The first line will
+ // contain the filename, and the second line everything else. Store the
+ // filename until we receive the next line.
+ string16 last_filename_;
+ bool last_is_directory_;
+
+ std::queue<FtpDirectoryListingEntry> entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpDirectoryListingParserVms);
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_VMS_H_
diff --git a/net/ftp/ftp_directory_listing_parser_vms_unittest.cc b/net/ftp/ftp_directory_listing_parser_vms_unittest.cc
new file mode 100644
index 0000000..723c2ac
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser_vms_unittest.cc
@@ -0,0 +1,118 @@
+// 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_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "net/ftp/ftp_directory_listing_parser_vms.h"
+
+namespace {
+
+typedef net::FtpDirectoryListingParserTest FtpDirectoryListingParserVmsTest;
+
+TEST_F(FtpDirectoryListingParserVmsTest, Good) {
+ const struct SingleLineTestData good_cases[] = {
+ { "README.TXT;4 2 18-APR-2000 10:40:39.90",
+ net::FtpDirectoryListingEntry::FILE, "readme.txt", 1024,
+ 2000, 4, 18, 10, 40 },
+ { ".WELCOME;1 2 13-FEB-2002 23:32:40.47",
+ net::FtpDirectoryListingEntry::FILE, ".welcome", 1024,
+ 2002, 2, 13, 23, 32 },
+ { "FILE.;1 2 13-FEB-2002 23:32:40.47",
+ net::FtpDirectoryListingEntry::FILE, "file.", 1024,
+ 2002, 2, 13, 23, 32 },
+ { "EXAMPLE.TXT;1 1 4-NOV-2009 06:02 [JOHNDOE] (RWED,RWED,,)",
+ net::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)",
+ net::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)",
+ net::FtpDirectoryListingEntry::DIRECTORY, "test", -1,
+ 1999, 3, 4, 22, 14 },
+ { "ANNOUNCE.TXT;2 1 12-MAR-2005 08:44:57 [X] (,,,)",
+ net::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)",
+ net::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)",
+ net::FtpDirectoryListingEntry::FILE, "announce.txt", 512,
+ 2005, 3, 12, 8, 44 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s", i, good_cases[i].input));
+
+ net::FtpDirectoryListingParserVms parser;
+ ASSERT_TRUE(
+ parser.ConsumeLine(ASCIIToUTF16("Directory ANONYMOUS_ROOT:[000000]")));
+ RunSingleLineTestCase(&parser, good_cases[i]);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserVmsTest, Bad) {
+ const char* bad_cases[] = {
+ "Directory ROOT|garbage",
+
+ // Missing file version number.
+ "Directory ROOT|README.TXT 2 18-APR-2000 10:40:39",
+
+ // Missing extension.
+ "Directory ROOT|README;1 2 18-APR-2000 10:40:39",
+
+ // Malformed file size.
+ "Directory ROOT|README.TXT;1 garbage 18-APR-2000 10:40:39",
+ "Directory ROOT|README.TXT;1 -2 18-APR-2000 10:40:39",
+
+ // Malformed date.
+ "Directory ROOT|README.TXT;1 2 APR-2000 10:40:39",
+ "Directory ROOT|README.TXT;1 2 -18-APR-2000 10:40:39",
+ "Directory ROOT|README.TXT;1 2 18-APR 10:40:39",
+ "Directory ROOT|README.TXT;1 2 18-APR-2000 10",
+ "Directory ROOT|README.TXT;1 2 18-APR-2000 10:40.25",
+ "Directory ROOT|README.TXT;1 2 18-APR-2000 10:40.25.25",
+
+ // Empty line inside the listing.
+ "Directory ROOT|README.TXT;1 2 18-APR-2000 10:40:42"
+ "||README.TXT;1 2 18-APR-2000 10:40:42",
+
+ // Data after footer.
+ "Directory ROOT|README.TXT;4 2 18-APR-2000 10:40:39"
+ "||Total of 1 file|",
+ "Directory ROOT|README.TXT;4 2 18-APR-2000 10:40:39"
+ "||Total of 1 file|garbage",
+ "Directory ROOT|README.TXT;4 2 18-APR-2000 10:40:39"
+ "||Total of 1 file|Total of 1 file",
+
+ // Malformed security information.
+ "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 (RWED,RWED,RE,RE)",
+ "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [SYSTEM]",
+ "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 (SYSTEM) (RWED,RWED,RE,RE)",
+ "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [SYSTEM] [RWED,RWED,RE,RE]",
+ "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED)",
+ "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWED,RE,RE,RE)",
+ "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWEDRWED,RE,RE)",
+ "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,DEWR,RE,RE)",
+ "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWED,Q,RE)",
+ "Directory ROOT|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(StringPrintf("Test[%" PRIuS "]: %s", i, bad_cases[i]));
+
+ std::vector<std::string> lines;
+ SplitString(bad_cases[i], '|', &lines);
+ net::FtpDirectoryListingParserVms parser;
+ bool failed = false;
+ for (std::vector<std::string>::const_iterator i = lines.begin();
+ i != lines.end(); ++i) {
+ if (!parser.ConsumeLine(UTF8ToUTF16(*i))) {
+ failed = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(failed);
+ }
+}
+
+} // namespace
diff --git a/net/ftp/ftp_directory_listing_parser_windows.cc b/net/ftp/ftp_directory_listing_parser_windows.cc
new file mode 100644
index 0000000..3f2818e
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser_windows.cc
@@ -0,0 +1,109 @@
+// 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_directory_listing_parser_windows.h"
+
+#include <vector>
+
+#include "base/string_util.h"
+
+namespace {
+
+bool WindowsDateListingToTime(const std::vector<string16>& columns,
+ base::Time* time) {
+ DCHECK_EQ(4U, columns.size());
+
+ base::Time::Exploded time_exploded = { 0 };
+
+ // Date should be in format MM-DD-YY[YY].
+ std::vector<string16> date_parts;
+ SplitString(columns[0], '-', &date_parts);
+ if (date_parts.size() != 3)
+ return false;
+ if (!StringToInt(date_parts[0], &time_exploded.month))
+ return false;
+ if (!StringToInt(date_parts[1], &time_exploded.day_of_month))
+ return false;
+ if (!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 (columns[1].length() != 7)
+ return false;
+ std::vector<string16> time_parts;
+ SplitString(columns[1].substr(0, 5), ':', &time_parts);
+ if (time_parts.size() != 2)
+ return false;
+ if (!StringToInt(time_parts[0], &time_exploded.hour))
+ return false;
+ if (!StringToInt(time_parts[1], &time_exploded.minute))
+ return false;
+ string16 am_or_pm(columns[1].substr(5, 2));
+ if (EqualsASCII(am_or_pm, "PM"))
+ time_exploded.hour += 12;
+ else if (!EqualsASCII(am_or_pm, "AM"))
+ 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
+
+namespace net {
+
+FtpDirectoryListingParserWindows::FtpDirectoryListingParserWindows() {
+}
+
+bool FtpDirectoryListingParserWindows::ConsumeLine(const string16& line) {
+ std::vector<string16> columns;
+ SplitString(CollapseWhitespace(line, false), ' ', &columns);
+ if (columns.size() != 4)
+ return false;
+
+ FtpDirectoryListingEntry entry;
+ entry.name = columns[3];
+
+ if (EqualsASCII(columns[2], "<DIR>")) {
+ entry.type = FtpDirectoryListingEntry::DIRECTORY;
+ entry.size = -1;
+ } else {
+ entry.type = FtpDirectoryListingEntry::FILE;
+ if (!StringToInt64(columns[2], &entry.size))
+ return false;
+ if (entry.size < 0)
+ return false;
+ }
+
+ if (!WindowsDateListingToTime(columns, &entry.last_modified))
+ return false;
+
+ entries_.push(entry);
+ return true;
+}
+
+bool FtpDirectoryListingParserWindows::OnEndOfInput() {
+ return true;
+}
+
+bool FtpDirectoryListingParserWindows::EntryAvailable() const {
+ return !entries_.empty();
+}
+
+FtpDirectoryListingEntry FtpDirectoryListingParserWindows::PopEntry() {
+ FtpDirectoryListingEntry entry = entries_.front();
+ entries_.pop();
+ return entry;
+}
+
+} // namespace net
diff --git a/net/ftp/ftp_directory_listing_parser_windows.h b/net/ftp/ftp_directory_listing_parser_windows.h
new file mode 100644
index 0000000..da58370
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser_windows.h
@@ -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.
+
+#ifndef NET_FTP_FTP_DIRECTORY_LISTING_PARSER_WINDOWS_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_WINDOWS_H_
+
+#include <queue>
+
+#include "net/ftp/ftp_directory_listing_parser.h"
+
+namespace net {
+
+class FtpDirectoryListingParserWindows : public FtpDirectoryListingParser {
+ public:
+ FtpDirectoryListingParserWindows();
+
+ // FtpDirectoryListingParser methods:
+ virtual FtpServerType GetServerType() const { return SERVER_WINDOWS; }
+ virtual bool ConsumeLine(const string16& line);
+ virtual bool OnEndOfInput();
+ virtual bool EntryAvailable() const;
+ virtual FtpDirectoryListingEntry PopEntry();
+
+ private:
+ std::queue<FtpDirectoryListingEntry> entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpDirectoryListingParserWindows);
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_WINDOWS_H_
diff --git a/net/ftp/ftp_directory_listing_parser_windows_unittest.cc b/net/ftp/ftp_directory_listing_parser_windows_unittest.cc
new file mode 100644
index 0000000..57fd216
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser_windows_unittest.cc
@@ -0,0 +1,74 @@
+// 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_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "net/ftp/ftp_directory_listing_parser_windows.h"
+
+namespace {
+
+typedef net::FtpDirectoryListingParserTest FtpDirectoryListingParserWindowsTest;
+
+TEST_F(FtpDirectoryListingParserWindowsTest, Good) {
+ base::Time::Exploded now_exploded;
+ base::Time::Now().LocalExplode(&now_exploded);
+
+ const struct SingleLineTestData good_cases[] = {
+ { "11-02-09 05:32PM <DIR> NT",
+ net::FtpDirectoryListingEntry::DIRECTORY, "NT", -1,
+ 2009, 11, 2, 17, 32 },
+ { "01-06-09 02:42PM 458 Readme.txt",
+ net::FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 2009, 1, 6, 14, 42 },
+ { "01-06-09 02:42AM 1 Readme.txt",
+ net::FtpDirectoryListingEntry::FILE, "Readme.txt", 1,
+ 2009, 1, 6, 2, 42 },
+ { "01-06-01 02:42AM 458 Readme.txt",
+ net::FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 2001, 1, 6, 2, 42 },
+ { "01-06-00 02:42AM 458 Corner1.txt",
+ net::FtpDirectoryListingEntry::FILE, "Corner1.txt", 458,
+ 2000, 1, 6, 2, 42 },
+ { "01-06-99 02:42AM 458 Corner2.txt",
+ net::FtpDirectoryListingEntry::FILE, "Corner2.txt", 458,
+ 1999, 1, 6, 2, 42 },
+ { "01-06-80 02:42AM 458 Corner3.txt",
+ net::FtpDirectoryListingEntry::FILE, "Corner3.txt", 458,
+ 1980, 1, 6, 2, 42 },
+#if !defined(OS_LINUX)
+ // TODO(phajdan.jr): Re-enable when 2038-year problem is fixed on Linux.
+ { "01-06-79 02:42AM 458 Corner4",
+ net::FtpDirectoryListingEntry::FILE, "Corner4", 458,
+ 2079, 1, 6, 2, 42 },
+#endif // !defined (OS_LINUX)
+ { "01-06-1979 02:42AM 458 Readme.txt",
+ net::FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 1979, 1, 6, 2, 42 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s", i, good_cases[i].input));
+
+ net::FtpDirectoryListingParserWindows parser;
+ RunSingleLineTestCase(&parser, good_cases[i]);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserWindowsTest, Bad) {
+ const char* bad_cases[] = {
+ "",
+ "garbage",
+ "11-02-09 05:32PM <GARBAGE> NT",
+ "11-02-09 05:32 <DIR> NT",
+ "11-FEB-09 05:32PM <DIR> NT",
+ "11-02 05:32PM <DIR> NT",
+ "11-02-09 05:32PM -1 NT",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ net::FtpDirectoryListingParserWindows parser;
+ EXPECT_FALSE(parser.ConsumeLine(UTF8ToUTF16(bad_cases[i]))) << bad_cases[i];
+ }
+}
+
+} // namespace
diff --git a/net/ftp/ftp_directory_listing_parsers.cc b/net/ftp/ftp_directory_listing_parsers.cc
deleted file mode 100644
index a559a69..0000000
--- a/net/ftp/ftp_directory_listing_parsers.cc
+++ /dev/null
@@ -1,601 +0,0 @@
-// 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_directory_listing_parsers.h"
-
-#include <ctype.h>
-
-#include "base/string_util.h"
-
-namespace {
-
-bool LooksLikeUnixPermission(const string16& text) {
- if (text.length() != 3)
- return false;
-
- // Meaning of the flags:
- // r - file is readable
- // w - file is writable
- // x - file is executable
- // s or S - setuid/setgid bit set
- // t or T - "sticky" bit set
- return ((text[0] == 'r' || text[0] == '-') &&
- (text[1] == 'w' || text[1] == '-') &&
- (text[2] == 'x' || text[2] == 's' || text[2] == 'S' ||
- text[2] == 't' || text[2] == 'T' || text[2] == '-'));
-}
-
-bool LooksLikeUnixPermissionsListing(const string16& text) {
- if (text.length() != 10)
- return false;
-
- if (text[0] != 'b' && text[0] != 'c' && text[0] != 'd' &&
- text[0] != 'l' && text[0] != 'p' && text[0] != 's' &&
- text[0] != '-')
- return false;
-
- return (LooksLikeUnixPermission(text.substr(1, 3)) &&
- LooksLikeUnixPermission(text.substr(4, 3)) &&
- LooksLikeUnixPermission(text.substr(7, 3)));
-}
-
-bool IsStringNonNegativeInteger(const string16& text) {
- int number;
- if (!StringToInt(text, &number))
- return false;
-
- return number >= 0;
-}
-
-string16 GetStringPartAfterColumns(const string16& text, int columns) {
- DCHECK_LE(1, columns);
- int columns_so_far = 0;
- size_t last = 0;
- for (size_t i = 1; i < text.length(); ++i) {
- if (!isspace(text[i - 1]) && isspace(text[i])) {
- last = i;
- if (++columns_so_far == columns)
- break;
- }
- }
- string16 result(text.substr(last));
- TrimWhitespace(result, TRIM_ALL, &result);
- return result;
-}
-
-bool ThreeLetterMonthToNumber(const string16& text, int* number) {
- const static char* months[] = { "jan", "feb", "mar", "apr", "may", "jun",
- "jul", "aug", "sep", "oct", "nov", "dec" };
-
- for (size_t i = 0; i < arraysize(months); i++) {
- if (LowerCaseEqualsASCII(text, months[i])) {
- *number = i + 1;
- return true;
- }
- }
-
- // Special cases for listings in German (other three-letter month
- // abbreviations are the same as in English). Note that we don't need to do
- // a case-insensitive compare here. Only "ls -l" style listings may use
- // localized month names, and they will always start capitalized. Also,
- // converting non-ASCII characters to lowercase would be more complicated.
- if (text == UTF8ToUTF16("M\xc3\xa4r")) {
- // The full month name is M-(a-umlaut)-rz (March), which is M-(a-umlaut)r
- // when abbreviated.
- *number = 3;
- return true;
- }
- if (text == ASCIIToUTF16("Mai")) {
- *number = 5;
- return true;
- }
- if (text == ASCIIToUTF16("Okt")) {
- *number = 10;
- return true;
- }
- if (text == ASCIIToUTF16("Dez")) {
- *number = 12;
- return true;
- }
-
- return false;
-}
-
-bool UnixDateListingToTime(const std::vector<string16>& columns,
- base::Time* time) {
- DCHECK_LE(9U, columns.size());
-
- base::Time::Exploded time_exploded = { 0 };
-
- if (!ThreeLetterMonthToNumber(columns[5], &time_exploded.month))
- return false;
-
- if (!StringToInt(columns[6], &time_exploded.day_of_month))
- return false;
-
- if (!StringToInt(columns[7], &time_exploded.year)) {
- // Maybe it's time. Does it look like time (MM:HH)?
- if (columns[7].length() != 5 || columns[7][2] != ':')
- return false;
-
- if (!StringToInt(columns[7].substr(0, 2), &time_exploded.hour))
- return false;
-
- if (!StringToInt(columns[7].substr(3, 2), &time_exploded.minute))
- return false;
-
- // Use current year.
- base::Time::Exploded now_exploded;
- base::Time::Now().LocalExplode(&now_exploded);
- time_exploded.year = now_exploded.year;
- }
-
- // We don't know the time zone of the server, so just use local time.
- *time = base::Time::FromLocalExploded(time_exploded);
- return true;
-}
-
-bool WindowsDateListingToTime(const std::vector<string16>& columns,
- base::Time* time) {
- DCHECK_EQ(4U, columns.size());
-
- base::Time::Exploded time_exploded = { 0 };
-
- // Date should be in format MM-DD-YY[YY].
- std::vector<string16> date_parts;
- SplitString(columns[0], '-', &date_parts);
- if (date_parts.size() != 3)
- return false;
- if (!StringToInt(date_parts[0], &time_exploded.month))
- return false;
- if (!StringToInt(date_parts[1], &time_exploded.day_of_month))
- return false;
- if (!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 (columns[1].length() != 7)
- return false;
- std::vector<string16> time_parts;
- SplitString(columns[1].substr(0, 5), ':', &time_parts);
- if (time_parts.size() != 2)
- return false;
- if (!StringToInt(time_parts[0], &time_exploded.hour))
- return false;
- if (!StringToInt(time_parts[1], &time_exploded.minute))
- return false;
- string16 am_or_pm(columns[1].substr(5, 2));
- if (EqualsASCII(am_or_pm, "PM"))
- time_exploded.hour += 12;
- else if (!EqualsASCII(am_or_pm, "AM"))
- 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;
-}
-
-// Converts the filename component in listing to the filename we can display.
-// Returns true on success.
-bool ParseVmsFilename(const string16& raw_filename, string16* parsed_filename,
- bool* is_directory) {
- // 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<string16> listing_parts;
- SplitString(raw_filename, ';', &listing_parts);
- if (listing_parts.size() != 2)
- return false;
- if (!IsStringNonNegativeInteger(listing_parts[1]))
- 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<string16> filename_parts;
- 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]);
- *is_directory = true;
- } else {
- *parsed_filename = StringToLowerASCII(listing_parts[0]);
- *is_directory = false;
- }
- return true;
-}
-
-bool ParseVmsFilesize(const string16& input, int64* size) {
- // 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 (StringToInt64(input, size)) {
- *size *= kBlockSize;
- return true;
- }
-
- std::vector<string16> parts;
- SplitString(input, '/', &parts);
- if (parts.size() != 2)
- return false;
-
- int64 blocks_used, blocks_allocated;
- if (!StringToInt64(parts[0], &blocks_used))
- return false;
- if (!StringToInt64(parts[1], &blocks_allocated))
- return false;
- if (blocks_used > blocks_allocated)
- return false;
-
- *size = blocks_used * kBlockSize;
- return true;
-}
-
-bool LooksLikeVmsFileProtectionListingPart(const 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");
- 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 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<string16> parts;
- 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 string16& input) {
- if (input.length() < 2)
- return false;
- return input[0] == '[' && input[input.length() - 1] == ']';
-}
-
-bool VmsDateListingToTime(const std::vector<string16>& columns,
- base::Time* time) {
- DCHECK_EQ(3U, columns.size());
-
- base::Time::Exploded time_exploded = { 0 };
-
- // Date should be in format DD-MMM-YYYY.
- std::vector<string16> date_parts;
- SplitString(columns[1], '-', &date_parts);
- if (date_parts.size() != 3)
- return false;
- if (!StringToInt(date_parts[0], &time_exploded.day_of_month))
- return false;
- if (!ThreeLetterMonthToNumber(date_parts[1], &time_exploded.month))
- return false;
- if (!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.
- string16 time_column(columns[2]);
- 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<string16> time_parts;
- SplitString(time_column, ':', &time_parts);
- if (time_parts.size() != 2)
- return false;
- if (!StringToInt(time_parts[0], &time_exploded.hour))
- return false;
- if (!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
-
-namespace net {
-
-FtpDirectoryListingParser::~FtpDirectoryListingParser() {
-}
-
-FtpLsDirectoryListingParser::FtpLsDirectoryListingParser()
- : received_nonempty_line_(false) {
-}
-
-bool FtpLsDirectoryListingParser::ConsumeLine(const string16& line) {
- if (StartsWith(line, ASCIIToUTF16("total "), true) ||
- StartsWith(line, ASCIIToUTF16("Gesamt "), true)) {
- // 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.
- //
- // Note: "Gesamt" is a German word for "total". The case is important here:
- // for "ls -l" style listings, "total" will be lowercase, and Gesamt will be
- // capitalized. This helps us distinguish that from a VMS-style listing,
- // which would use "Total" (note the uppercase first letter).
-
- if (received_nonempty_line_)
- return false;
-
- received_nonempty_line_ = true;
- return true;
- }
- if (line.empty() && !received_nonempty_line_) {
- // Allow empty lines only at the beginning of the listing. For example VMS
- // systems in Unix emulation mode add an empty line before the first listing
- // entry.
- return true;
- }
- received_nonempty_line_ = true;
-
- std::vector<string16> columns;
- SplitString(CollapseWhitespace(line, false), ' ', &columns);
-
- // We may receive file names containing spaces, which can make the number of
- // columns arbitrarily large. We will handle that later. For now just make
- // sure we have all the columns that should normally be there.
- if (columns.size() < 9)
- return false;
-
- if (!LooksLikeUnixPermissionsListing(columns[0]))
- return false;
-
- FtpDirectoryListingEntry entry;
- if (columns[0][0] == 'l') {
- entry.type = FtpDirectoryListingEntry::SYMLINK;
- } else if (columns[0][0] == 'd') {
- entry.type = FtpDirectoryListingEntry::DIRECTORY;
- } else {
- entry.type = FtpDirectoryListingEntry::FILE;
- }
-
- if (!IsStringNonNegativeInteger(columns[1]))
- return false;
-
- if (!StringToInt64(columns[4], &entry.size))
- return false;
- if (entry.size < 0)
- return false;
- if (entry.type != FtpDirectoryListingEntry::FILE)
- entry.size = -1;
-
- if (!UnixDateListingToTime(columns, &entry.last_modified))
- return false;
-
- entry.name = GetStringPartAfterColumns(line, 8);
- if (entry.type == FtpDirectoryListingEntry::SYMLINK) {
- string16::size_type pos = entry.name.rfind(ASCIIToUTF16(" -> "));
- if (pos == string16::npos)
- return false;
- entry.name = entry.name.substr(0, pos);
- }
-
- entries_.push(entry);
- return true;
-}
-
-bool FtpLsDirectoryListingParser::OnEndOfInput() {
- return true;
-}
-
-bool FtpLsDirectoryListingParser::EntryAvailable() const {
- return !entries_.empty();
-}
-
-FtpDirectoryListingEntry FtpLsDirectoryListingParser::PopEntry() {
- FtpDirectoryListingEntry entry = entries_.front();
- entries_.pop();
- return entry;
-}
-
-FtpWindowsDirectoryListingParser::FtpWindowsDirectoryListingParser() {
-}
-
-bool FtpWindowsDirectoryListingParser::ConsumeLine(const string16& line) {
- std::vector<string16> columns;
- SplitString(CollapseWhitespace(line, false), ' ', &columns);
- if (columns.size() != 4)
- return false;
-
- FtpDirectoryListingEntry entry;
- entry.name = columns[3];
-
- if (EqualsASCII(columns[2], "<DIR>")) {
- entry.type = FtpDirectoryListingEntry::DIRECTORY;
- entry.size = -1;
- } else {
- entry.type = FtpDirectoryListingEntry::FILE;
- if (!StringToInt64(columns[2], &entry.size))
- return false;
- if (entry.size < 0)
- return false;
- }
-
- if (!WindowsDateListingToTime(columns, &entry.last_modified))
- return false;
-
- entries_.push(entry);
- return true;
-}
-
-bool FtpWindowsDirectoryListingParser::OnEndOfInput() {
- return true;
-}
-
-bool FtpWindowsDirectoryListingParser::EntryAvailable() const {
- return !entries_.empty();
-}
-
-FtpDirectoryListingEntry FtpWindowsDirectoryListingParser::PopEntry() {
- FtpDirectoryListingEntry entry = entries_.front();
- entries_.pop();
- return entry;
-}
-
-FtpVmsDirectoryListingParser::FtpVmsDirectoryListingParser()
- : state_(STATE_INITIAL),
- last_is_directory_(false) {
-}
-
-bool FtpVmsDirectoryListingParser::ConsumeLine(const string16& line) {
- switch (state_) {
- case STATE_INITIAL:
- DCHECK(last_filename_.empty());
- if (line.empty())
- return true;
- if (StartsWith(line, ASCIIToUTF16("Total of "), true)) {
- state_ = STATE_END;
- return true;
- }
- // We assume that the first non-empty line is the listing header. It often
- // starts with "Directory ", but not always.
- state_ = STATE_RECEIVED_HEADER;
- return true;
- case STATE_RECEIVED_HEADER:
- DCHECK(last_filename_.empty());
- if (line.empty())
- return true;
- state_ = STATE_ENTRIES;
- return ConsumeEntryLine(line);
- case STATE_ENTRIES:
- if (line.empty()) {
- if (!last_filename_.empty())
- return false;
- state_ = STATE_RECEIVED_LAST_ENTRY;
- return true;
- }
- return ConsumeEntryLine(line);
- case STATE_RECEIVED_LAST_ENTRY:
- DCHECK(last_filename_.empty());
- if (line.empty())
- return true;
- if (!StartsWith(line, ASCIIToUTF16("Total of "), true))
- return false;
- state_ = STATE_END;
- return true;
- case STATE_END:
- DCHECK(last_filename_.empty());
- return false;
- default:
- NOTREACHED();
- return false;
- }
-}
-
-bool FtpVmsDirectoryListingParser::OnEndOfInput() {
- return (state_ == STATE_END);
-}
-
-bool FtpVmsDirectoryListingParser::EntryAvailable() const {
- return !entries_.empty();
-}
-
-FtpDirectoryListingEntry FtpVmsDirectoryListingParser::PopEntry() {
- FtpDirectoryListingEntry entry = entries_.front();
- entries_.pop();
- return entry;
-}
-
-bool FtpVmsDirectoryListingParser::ConsumeEntryLine(const string16& line) {
- std::vector<string16> columns;
- SplitString(CollapseWhitespace(line, false), ' ', &columns);
-
- if (columns.size() == 1) {
- if (!last_filename_.empty())
- return false;
- return ParseVmsFilename(columns[0], &last_filename_, &last_is_directory_);
- }
-
- // Recognize listing entries which generate "access denied" message even when
- // trying to list them. We don't display them in the final listing.
- static const char* kAccessDeniedMessages[] = {
- "%RMS-E-PRV",
- "privilege",
- };
- for (size_t i = 0; i < arraysize(kAccessDeniedMessages); i++) {
- if (line.find(ASCIIToUTF16(kAccessDeniedMessages[i])) != string16::npos) {
- last_filename_.clear();
- last_is_directory_ = false;
- return true;
- }
- }
-
- string16 filename;
- bool is_directory = false;
- if (last_filename_.empty()) {
- if (!ParseVmsFilename(columns[0], &filename, &is_directory))
- return false;
- columns.erase(columns.begin());
- } else {
- filename = last_filename_;
- is_directory = last_is_directory_;
- last_filename_.clear();
- last_is_directory_ = false;
- }
-
- if (columns.size() > 5)
- return false;
-
- if (columns.size() == 5) {
- if (!LooksLikeVmsFileProtectionListing(columns[4]))
- return false;
- if (!LooksLikeVmsUserIdentificationCode(columns[3]))
- return false;
- columns.resize(3);
- }
-
- if (columns.size() != 3)
- return false;
-
- FtpDirectoryListingEntry entry;
- entry.name = filename;
- entry.type = is_directory ? FtpDirectoryListingEntry::DIRECTORY
- : FtpDirectoryListingEntry::FILE;
- if (!ParseVmsFilesize(columns[0], &entry.size))
- return false;
- if (entry.size < 0)
- return false;
- if (entry.type != FtpDirectoryListingEntry::FILE)
- entry.size = -1;
- if (!VmsDateListingToTime(columns, &entry.last_modified))
- return false;
-
- entries_.push(entry);
- return true;
-}
-
-} // namespace net
diff --git a/net/ftp/ftp_directory_listing_parsers.h b/net/ftp/ftp_directory_listing_parsers.h
deleted file mode 100644
index 8b11ecb..0000000
--- a/net/ftp/ftp_directory_listing_parsers.h
+++ /dev/null
@@ -1,138 +0,0 @@
-// 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_DIRECTORY_LISTING_PARSERS_H_
-#define NET_FTP_FTP_DIRECTORY_LISTING_PARSERS_H_
-
-#include <queue>
-
-#include "base/basictypes.h"
-#include "base/string16.h"
-#include "base/time.h"
-#include "net/ftp/ftp_server_type_histograms.h"
-
-namespace net {
-
-struct FtpDirectoryListingEntry {
- enum Type {
- FILE,
- DIRECTORY,
- SYMLINK,
- };
-
- Type type;
- string16 name;
- int64 size; // File size, in bytes. -1 if not applicable.
-
- // Last modified time, in local time zone.
- base::Time last_modified;
-};
-
-class FtpDirectoryListingParser {
- public:
- virtual ~FtpDirectoryListingParser();
-
- virtual FtpServerType GetServerType() const = 0;
-
- // Adds |line| to the internal parsing buffer. Returns true on success.
- virtual bool ConsumeLine(const string16& line) = 0;
-
- // Called after all input has been consumed. Returns true if the parser
- // recognizes all received data as a valid listing.
- virtual bool OnEndOfInput() = 0;
-
- // Returns true if there is at least one FtpDirectoryListingEntry available.
- virtual bool EntryAvailable() const = 0;
-
- // Returns the next entry. It is an error to call this function unless
- // EntryAvailable returns true.
- virtual FtpDirectoryListingEntry PopEntry() = 0;
-};
-
-// Parser for "ls -l"-style directory listing.
-class FtpLsDirectoryListingParser : public FtpDirectoryListingParser {
- public:
- FtpLsDirectoryListingParser();
-
- // FtpDirectoryListingParser methods:
- virtual FtpServerType GetServerType() const { return SERVER_LS; }
- virtual bool ConsumeLine(const string16& line);
- virtual bool OnEndOfInput();
- virtual bool EntryAvailable() const;
- virtual FtpDirectoryListingEntry PopEntry();
-
- private:
- bool received_nonempty_line_;
-
- std::queue<FtpDirectoryListingEntry> entries_;
-
- DISALLOW_COPY_AND_ASSIGN(FtpLsDirectoryListingParser);
-};
-
-class FtpWindowsDirectoryListingParser : public FtpDirectoryListingParser {
- public:
- FtpWindowsDirectoryListingParser();
-
- // FtpDirectoryListingParser methods:
- virtual FtpServerType GetServerType() const { return SERVER_WINDOWS; }
- virtual bool ConsumeLine(const string16& line);
- virtual bool OnEndOfInput();
- virtual bool EntryAvailable() const;
- virtual FtpDirectoryListingEntry PopEntry();
-
- private:
- std::queue<FtpDirectoryListingEntry> entries_;
-
- DISALLOW_COPY_AND_ASSIGN(FtpWindowsDirectoryListingParser);
-};
-
-// Parser for VMS-style directory listing (including variants).
-class FtpVmsDirectoryListingParser : public FtpDirectoryListingParser {
- public:
- FtpVmsDirectoryListingParser();
-
- // FtpDirectoryListingParser methods:
- virtual FtpServerType GetServerType() const { return SERVER_VMS; }
- virtual bool ConsumeLine(const string16& line);
- virtual bool OnEndOfInput();
- virtual bool EntryAvailable() const;
- virtual FtpDirectoryListingEntry PopEntry();
-
- private:
- // Consumes listing line which is expected to be a directory listing entry
- // (and not a comment etc). Returns true on success.
- bool ConsumeEntryLine(const string16& line);
-
- enum State {
- STATE_INITIAL,
-
- // Indicates that we have received the header, like this:
- // Directory SYS$SYSDEVICE:[ANONYMOUS]
- STATE_RECEIVED_HEADER,
-
- // Indicates that we have received the first listing entry, like this:
- // MADGOAT.DIR;1 2 9-MAY-2001 22:23:44.85
- STATE_ENTRIES,
-
- // Indicates that we have received the last listing entry.
- STATE_RECEIVED_LAST_ENTRY,
-
- // Indicates that we have successfully received all parts of the listing.
- STATE_END,
- } state_;
-
- // VMS can use two physical lines if the filename is long. The first line will
- // contain the filename, and the second line everything else. Store the
- // filename until we receive the next line.
- string16 last_filename_;
- bool last_is_directory_;
-
- std::queue<FtpDirectoryListingEntry> entries_;
-
- DISALLOW_COPY_AND_ASSIGN(FtpVmsDirectoryListingParser);
-};
-
-} // namespace net
-
-#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSERS_H_
diff --git a/net/ftp/ftp_directory_listing_parsers_unittest.cc b/net/ftp/ftp_directory_listing_parsers_unittest.cc
deleted file mode 100644
index 5bb69e7..0000000
--- a/net/ftp/ftp_directory_listing_parsers_unittest.cc
+++ /dev/null
@@ -1,267 +0,0 @@
-// 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_directory_listing_parsers.h"
-
-#include "base/format_macros.h"
-#include "base/string_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace {
-
-struct SingleLineTestData {
- const char* input;
- net::FtpDirectoryListingEntry::Type type;
- const char* filename;
- int64 size;
- int year;
- int month;
- int day_of_month;
- int hour;
- int minute;
-};
-
-class FtpDirectoryListingParsersTest : public testing::Test {
- protected:
- FtpDirectoryListingParsersTest() {
- }
-
- void RunSingleLineTestCase(net::FtpDirectoryListingParser* parser,
- const SingleLineTestData& test_case) {
- ASSERT_TRUE(parser->ConsumeLine(UTF8ToUTF16(test_case.input)));
- ASSERT_TRUE(parser->EntryAvailable());
- net::FtpDirectoryListingEntry entry = parser->PopEntry();
- 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);
- 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(0, time_exploded.second);
- EXPECT_EQ(0, time_exploded.millisecond);
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(FtpDirectoryListingParsersTest);
-};
-
-TEST_F(FtpDirectoryListingParsersTest, Ls) {
- base::Time::Exploded now_exploded;
- base::Time::Now().LocalExplode(&now_exploded);
-
- const struct SingleLineTestData good_cases[] = {
- { "-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 README",
- net::FtpDirectoryListingEntry::FILE, "README", 528,
- 2007, 11, 1, 0, 0 },
- { "drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 directory",
- net::FtpDirectoryListingEntry::DIRECTORY, "directory", -1,
- now_exploded.year, 5, 15, 18, 11 },
- { "lrwxrwxrwx 1 0 0 26 Sep 18 2008 pub -> vol/1/.CLUSTER/var_ftp/pub",
- net::FtpDirectoryListingEntry::SYMLINK, "pub", -1,
- 2008, 9, 18, 0, 0 },
- { "lrwxrwxrwx 1 0 0 3 Oct 12 13:37 mirror -> pub",
- net::FtpDirectoryListingEntry::SYMLINK, "mirror", -1,
- now_exploded.year, 10, 12, 13, 37 },
- { "drwxrwsr-x 4 501 501 4096 Feb 20 2007 pub",
- net::FtpDirectoryListingEntry::DIRECTORY, "pub", -1,
- 2007, 2, 20, 0, 0 },
- { "drwxr-xr-x 4 (?) (?) 4096 Apr 8 2007 jigdo",
- net::FtpDirectoryListingEntry::DIRECTORY, "jigdo", -1,
- 2007, 4, 8, 0, 0 },
- { "drwx-wx-wt 2 root wheel 512 Jul 1 02:15 incoming",
- net::FtpDirectoryListingEntry::DIRECTORY, "incoming", -1,
- now_exploded.year, 7, 1, 2, 15 },
- { "-rw-r--r-- 1 2 3 3447432 May 18 2009 Foo - Manual.pdf",
- net::FtpDirectoryListingEntry::FILE, "Foo - Manual.pdf", 3447432,
- 2009, 5, 18, 0, 0 },
- };
- for (size_t i = 0; i < arraysize(good_cases); i++) {
- SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s", i, good_cases[i].input));
-
- net::FtpLsDirectoryListingParser parser;
- RunSingleLineTestCase(&parser, good_cases[i]);
- }
-
- const char* bad_cases[] = {
- "garbage",
- "-rw-r--r-- 1 ftp ftp",
- "-rw-r--rgb 1 ftp ftp 528 Nov 01 2007 README",
- "-rw-rgbr-- 1 ftp ftp 528 Nov 01 2007 README",
- "qrwwr--r-- 1 ftp ftp 528 Nov 01 2007 README",
- "-rw-r--r-- -1 ftp ftp 528 Nov 01 2007 README",
- "-rw-r--r-- 1 ftp ftp -528 Nov 01 2007 README",
- "-rw-r--r-- 1 ftp ftp 528 Foo 01 2007 README",
- };
- for (size_t i = 0; i < arraysize(bad_cases); i++) {
- net::FtpLsDirectoryListingParser parser;
- EXPECT_FALSE(parser.ConsumeLine(UTF8ToUTF16(bad_cases[i]))) << bad_cases[i];
- }
-}
-
-TEST_F(FtpDirectoryListingParsersTest, Windows) {
- base::Time::Exploded now_exploded;
- base::Time::Now().LocalExplode(&now_exploded);
-
- const struct SingleLineTestData good_cases[] = {
- { "11-02-09 05:32PM <DIR> NT",
- net::FtpDirectoryListingEntry::DIRECTORY, "NT", -1,
- 2009, 11, 2, 17, 32 },
- { "01-06-09 02:42PM 458 Readme.txt",
- net::FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
- 2009, 1, 6, 14, 42 },
- { "01-06-09 02:42AM 1 Readme.txt",
- net::FtpDirectoryListingEntry::FILE, "Readme.txt", 1,
- 2009, 1, 6, 2, 42 },
- { "01-06-01 02:42AM 458 Readme.txt",
- net::FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
- 2001, 1, 6, 2, 42 },
- { "01-06-00 02:42AM 458 Corner1.txt",
- net::FtpDirectoryListingEntry::FILE, "Corner1.txt", 458,
- 2000, 1, 6, 2, 42 },
- { "01-06-99 02:42AM 458 Corner2.txt",
- net::FtpDirectoryListingEntry::FILE, "Corner2.txt", 458,
- 1999, 1, 6, 2, 42 },
- { "01-06-80 02:42AM 458 Corner3.txt",
- net::FtpDirectoryListingEntry::FILE, "Corner3.txt", 458,
- 1980, 1, 6, 2, 42 },
-#if !defined(OS_LINUX)
- // TODO(phajdan.jr): Re-enable when 2038-year problem is fixed on Linux.
- { "01-06-79 02:42AM 458 Corner4",
- net::FtpDirectoryListingEntry::FILE, "Corner4", 458,
- 2079, 1, 6, 2, 42 },
-#endif // !defined (OS_LINUX)
- { "01-06-1979 02:42AM 458 Readme.txt",
- net::FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
- 1979, 1, 6, 2, 42 },
- };
- for (size_t i = 0; i < arraysize(good_cases); i++) {
- SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s", i, good_cases[i].input));
-
- net::FtpWindowsDirectoryListingParser parser;
- RunSingleLineTestCase(&parser, good_cases[i]);
- }
-
- const char* bad_cases[] = {
- "",
- "garbage",
- "11-02-09 05:32PM <GARBAGE> NT",
- "11-02-09 05:32 <DIR> NT",
- "11-FEB-09 05:32PM <DIR> NT",
- "11-02 05:32PM <DIR> NT",
- "11-02-09 05:32PM -1 NT",
- };
- for (size_t i = 0; i < arraysize(bad_cases); i++) {
- net::FtpWindowsDirectoryListingParser parser;
- EXPECT_FALSE(parser.ConsumeLine(UTF8ToUTF16(bad_cases[i]))) << bad_cases[i];
- }
-}
-
-TEST_F(FtpDirectoryListingParsersTest, Vms) {
- const struct SingleLineTestData good_cases[] = {
- { "README.TXT;4 2 18-APR-2000 10:40:39.90",
- net::FtpDirectoryListingEntry::FILE, "readme.txt", 1024,
- 2000, 4, 18, 10, 40 },
- { ".WELCOME;1 2 13-FEB-2002 23:32:40.47",
- net::FtpDirectoryListingEntry::FILE, ".welcome", 1024,
- 2002, 2, 13, 23, 32 },
- { "FILE.;1 2 13-FEB-2002 23:32:40.47",
- net::FtpDirectoryListingEntry::FILE, "file.", 1024,
- 2002, 2, 13, 23, 32 },
- { "EXAMPLE.TXT;1 1 4-NOV-2009 06:02 [JOHNDOE] (RWED,RWED,,)",
- net::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)",
- net::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)",
- net::FtpDirectoryListingEntry::DIRECTORY, "test", -1,
- 1999, 3, 4, 22, 14 },
- { "ANNOUNCE.TXT;2 1 12-MAR-2005 08:44:57 [X] (,,,)",
- net::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)",
- net::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)",
- net::FtpDirectoryListingEntry::FILE, "announce.txt", 512,
- 2005, 3, 12, 8, 44 },
- };
- for (size_t i = 0; i < arraysize(good_cases); i++) {
- SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s", i, good_cases[i].input));
-
- net::FtpVmsDirectoryListingParser parser;
- ASSERT_TRUE(
- parser.ConsumeLine(ASCIIToUTF16("Directory ANONYMOUS_ROOT:[000000]")));
- RunSingleLineTestCase(&parser, good_cases[i]);
- }
-
- const char* bad_cases[] = {
- "Directory ROOT|garbage",
-
- // Missing file version number.
- "Directory ROOT|README.TXT 2 18-APR-2000 10:40:39",
-
- // Missing extension.
- "Directory ROOT|README;1 2 18-APR-2000 10:40:39",
-
- // Malformed file size.
- "Directory ROOT|README.TXT;1 garbage 18-APR-2000 10:40:39",
- "Directory ROOT|README.TXT;1 -2 18-APR-2000 10:40:39",
-
- // Malformed date.
- "Directory ROOT|README.TXT;1 2 APR-2000 10:40:39",
- "Directory ROOT|README.TXT;1 2 -18-APR-2000 10:40:39",
- "Directory ROOT|README.TXT;1 2 18-APR 10:40:39",
- "Directory ROOT|README.TXT;1 2 18-APR-2000 10",
- "Directory ROOT|README.TXT;1 2 18-APR-2000 10:40.25",
- "Directory ROOT|README.TXT;1 2 18-APR-2000 10:40.25.25",
-
- // Empty line inside the listing.
- "Directory ROOT|README.TXT;1 2 18-APR-2000 10:40:42"
- "||README.TXT;1 2 18-APR-2000 10:40:42",
-
- // Data after footer.
- "Directory ROOT|README.TXT;4 2 18-APR-2000 10:40:39"
- "||Total of 1 file|",
- "Directory ROOT|README.TXT;4 2 18-APR-2000 10:40:39"
- "||Total of 1 file|garbage",
- "Directory ROOT|README.TXT;4 2 18-APR-2000 10:40:39"
- "||Total of 1 file|Total of 1 file",
-
- // Malformed security information.
- "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 (RWED,RWED,RE,RE)",
- "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [SYSTEM]",
- "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 (SYSTEM) (RWED,RWED,RE,RE)",
- "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [SYSTEM] [RWED,RWED,RE,RE]",
- "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED)",
- "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWED,RE,RE,RE)",
- "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWEDRWED,RE,RE)",
- "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,DEWR,RE,RE)",
- "Directory ROOT|X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWED,Q,RE)",
- "Directory ROOT|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(StringPrintf("Test[%" PRIuS "]: %s", i, bad_cases[i]));
-
- std::vector<std::string> lines;
- SplitString(bad_cases[i], '|', &lines);
- net::FtpVmsDirectoryListingParser parser;
- bool failed = false;
- for (std::vector<std::string>::const_iterator i = lines.begin();
- i != lines.end(); ++i) {
- if (!parser.ConsumeLine(UTF8ToUTF16(*i))) {
- failed = true;
- break;
- }
- }
- EXPECT_TRUE(failed);
- }
-}
-
-} // namespace
diff --git a/net/ftp/ftp_util.cc b/net/ftp/ftp_util.cc
index 591f7b2..5d4cd37 100644
--- a/net/ftp/ftp_util.cc
+++ b/net/ftp/ftp_util.cc
@@ -104,4 +104,44 @@ std::string FtpUtil::VMSPathToUnix(const std::string& vms_path) {
return result;
}
+// static
+bool FtpUtil::ThreeLetterMonthToNumber(const string16& text, int* number) {
+ const static char* months[] = { "jan", "feb", "mar", "apr", "may", "jun",
+ "jul", "aug", "sep", "oct", "nov", "dec" };
+
+ for (size_t i = 0; i < arraysize(months); i++) {
+ if (LowerCaseEqualsASCII(text, months[i])) {
+ *number = i + 1;
+ return true;
+ }
+ }
+
+ // Special cases for directory listings in German (other three-letter month
+ // abbreviations are the same as in English). Note that we don't need to do
+ // a case-insensitive compare here. Only "ls -l" style listings may use
+ // localized month names, and they will always start capitalized. Also,
+ // converting non-ASCII characters to lowercase would be more complicated.
+ if (text == UTF8ToUTF16("M\xc3\xa4r")) {
+ // The full month name is M-(a-umlaut)-rz (March), which is M-(a-umlaut)r
+ // when abbreviated.
+ *number = 3;
+ return true;
+ }
+ if (text == ASCIIToUTF16("Mai")) {
+ *number = 5;
+ return true;
+ }
+ if (text == ASCIIToUTF16("Okt")) {
+ *number = 10;
+ return true;
+ }
+ if (text == ASCIIToUTF16("Dez")) {
+ *number = 12;
+ return true;
+ }
+
+ return false;
+}
+
+
} // namespace
diff --git a/net/ftp/ftp_util.h b/net/ftp/ftp_util.h
index c71b919..2a73c68 100644
--- a/net/ftp/ftp_util.h
+++ b/net/ftp/ftp_util.h
@@ -7,6 +7,8 @@
#include <string>
+#include "base/string16.h"
+
namespace net {
class FtpUtil {
@@ -19,6 +21,10 @@ class FtpUtil {
// Convert VMS path to Unix-style path.
static std::string VMSPathToUnix(const std::string& vms_path);
+
+ // Convert three-letter month abbreviation (like Nov) to its number (in range
+ // 1-12).
+ static bool ThreeLetterMonthToNumber(const string16& text, int* number);
};
} // namespace net