summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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
-rw-r--r--net/net.gyp14
-rw-r--r--webkit/glue/ftp_directory_listing_response_delegate.cc2
21 files changed, 1176 insertions, 1015 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
diff --git a/net/net.gyp b/net/net.gyp
index cfc2286..51e3cb7 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -294,8 +294,14 @@
'ftp/ftp_ctrl_response_buffer.h',
'ftp/ftp_directory_listing_buffer.cc',
'ftp/ftp_directory_listing_buffer.h',
- 'ftp/ftp_directory_listing_parsers.cc',
- 'ftp/ftp_directory_listing_parsers.h',
+ 'ftp/ftp_directory_listing_parser.cc',
+ 'ftp/ftp_directory_listing_parser.h',
+ 'ftp/ftp_directory_listing_parser_ls.cc',
+ 'ftp/ftp_directory_listing_parser_ls.h',
+ 'ftp/ftp_directory_listing_parser_vms.cc',
+ 'ftp/ftp_directory_listing_parser_vms.h',
+ 'ftp/ftp_directory_listing_parser_windows.cc',
+ 'ftp/ftp_directory_listing_parser_windows.h',
'ftp/ftp_network_layer.cc',
'ftp/ftp_network_layer.h',
'ftp/ftp_network_session.h',
@@ -607,7 +613,9 @@
'ftp/ftp_auth_cache_unittest.cc',
'ftp/ftp_ctrl_response_buffer_unittest.cc',
'ftp/ftp_directory_listing_buffer_unittest.cc',
- 'ftp/ftp_directory_listing_parsers_unittest.cc',
+ 'ftp/ftp_directory_listing_parser_ls_unittest.cc',
+ 'ftp/ftp_directory_listing_parser_vms_unittest.cc',
+ 'ftp/ftp_directory_listing_parser_windows_unittest.cc',
'ftp/ftp_network_transaction_unittest.cc',
'ftp/ftp_util_unittest.cc',
'http/des_unittest.cc',
diff --git a/webkit/glue/ftp_directory_listing_response_delegate.cc b/webkit/glue/ftp_directory_listing_response_delegate.cc
index b437d2c..c06717d 100644
--- a/webkit/glue/ftp_directory_listing_response_delegate.cc
+++ b/webkit/glue/ftp_directory_listing_response_delegate.cc
@@ -14,7 +14,7 @@
#include "net/base/escape.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
-#include "net/ftp/ftp_directory_listing_parsers.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
#include "net/ftp/ftp_server_type_histograms.h"
#include "unicode/ucsdet.h"
#include "third_party/WebKit/WebKit/chromium/public/WebURL.h"