summaryrefslogtreecommitdiffstats
path: root/net/ftp
diff options
context:
space:
mode:
Diffstat (limited to 'net/ftp')
-rw-r--r--net/ftp/ftp_directory_listing_buffer.cc16
-rw-r--r--net/ftp/ftp_directory_listing_buffer_unittest.cc6
-rw-r--r--net/ftp/ftp_directory_listing_parser_ls.cc75
-rw-r--r--net/ftp/ftp_directory_listing_parser_ls.h4
-rw-r--r--net/ftp/ftp_directory_listing_parser_netware.cc102
-rw-r--r--net/ftp/ftp_directory_listing_parser_netware.h37
-rw-r--r--net/ftp/ftp_directory_listing_parser_netware_unittest.cc53
-rw-r--r--net/ftp/ftp_server_type_histograms.h1
-rw-r--r--net/ftp/ftp_util.cc33
-rw-r--r--net/ftp/ftp_util.h13
-rw-r--r--net/ftp/ftp_util_unittest.cc52
11 files changed, 335 insertions, 57 deletions
diff --git a/net/ftp/ftp_directory_listing_buffer.cc b/net/ftp/ftp_directory_listing_buffer.cc
index a74b7fe..644f59e 100644
--- a/net/ftp/ftp_directory_listing_buffer.cc
+++ b/net/ftp/ftp_directory_listing_buffer.cc
@@ -9,6 +9,7 @@
#include "base/string_util.h"
#include "net/base/net_errors.h"
#include "net/ftp/ftp_directory_listing_parser_ls.h"
+#include "net/ftp/ftp_directory_listing_parser_netware.h"
#include "net/ftp/ftp_directory_listing_parser_vms.h"
#include "net/ftp/ftp_directory_listing_parser_windows.h"
#include "unicode/ucsdet.h"
@@ -44,6 +45,7 @@ namespace net {
FtpDirectoryListingBuffer::FtpDirectoryListingBuffer()
: current_parser_(NULL) {
parsers_.insert(new FtpDirectoryListingParserLs());
+ parsers_.insert(new FtpDirectoryListingParserNetware());
parsers_.insert(new FtpDirectoryListingParserVms());
parsers_.insert(new FtpDirectoryListingParserWindows());
}
@@ -80,7 +82,6 @@ int FtpDirectoryListingBuffer::ProcessRemainingData() {
if (rv != OK)
return rv;
- DCHECK(current_parser_);
return OK;
}
@@ -169,7 +170,18 @@ int FtpDirectoryListingBuffer::OnEndOfInput() {
}
if (parsers_.size() != 1) {
- current_parser_ = NULL;
+ DCHECK(!current_parser_);
+
+ // We may hit an ambiguity in case of listings which have no entries. That's
+ // fine, as long as all remaining parsers agree that the listing is empty.
+ bool all_listings_empty = true;
+ for (ParserSet::iterator i = parsers_.begin(); i != parsers_.end(); ++i) {
+ if ((*i)->EntryAvailable())
+ all_listings_empty = false;
+ }
+ if (all_listings_empty)
+ return OK;
+
return ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT;
}
diff --git a/net/ftp/ftp_directory_listing_buffer_unittest.cc b/net/ftp/ftp_directory_listing_buffer_unittest.cc
index d5063c2..cf107b9 100644
--- a/net/ftp/ftp_directory_listing_buffer_unittest.cc
+++ b/net/ftp/ftp_directory_listing_buffer_unittest.cc
@@ -28,13 +28,15 @@ TEST(FtpDirectoryListingBufferTest, Parse) {
"dir-listing-ls-8",
"dir-listing-ls-9",
"dir-listing-ls-10",
- "dir-listing-windows-1",
- "dir-listing-windows-2",
+ "dir-listing-netware-1",
+ "dir-listing-netware-2",
"dir-listing-vms-1",
"dir-listing-vms-2",
"dir-listing-vms-3",
"dir-listing-vms-4",
"dir-listing-vms-5",
+ "dir-listing-windows-1",
+ "dir-listing-windows-2",
};
FilePath test_dir;
diff --git a/net/ftp/ftp_directory_listing_parser_ls.cc b/net/ftp/ftp_directory_listing_parser_ls.cc
index 195f4a4..64feeed 100644
--- a/net/ftp/ftp_directory_listing_parser_ls.cc
+++ b/net/ftp/ftp_directory_listing_parser_ls.cc
@@ -57,66 +57,16 @@ string16 GetStringPartAfterColumns(const string16& text, int columns) {
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) {
+ : received_nonempty_line_(false),
+ received_total_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
@@ -128,6 +78,23 @@ bool FtpDirectoryListingParserLs::ConsumeLine(const string16& line) {
std::vector<string16> columns;
SplitString(CollapseWhitespace(line, false), ' ', &columns);
+ // Some FTP servers put a "total n" line at the beginning of the listing
+ // (n is an integer). Allow such a line, but only once, and only if it's
+ // the first non-empty line. Do not match the word exactly, because it may be
+ // in different languages (at least English and German have been seen in the
+ // field).
+ if (columns.size() == 2 && !received_total_line_) {
+ received_total_line_ = true;
+
+ int total_number;
+ if (!StringToInt(columns[1], &total_number))
+ return false;
+ if (total_number < 0)
+ return false;
+
+ return true;
+ }
+
// 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.
@@ -159,8 +126,10 @@ bool FtpDirectoryListingParserLs::ConsumeLine(const string16& line) {
if (entry.type != FtpDirectoryListingEntry::FILE)
entry.size = -1;
- if (!UnixDateListingToTime(columns, &entry.last_modified))
+ if (!FtpUtil::LsDateListingToTime(columns[5], columns[6], columns[7],
+ &entry.last_modified)) {
return false;
+ }
entry.name = GetStringPartAfterColumns(line, 8);
if (entry.type == FtpDirectoryListingEntry::SYMLINK) {
diff --git a/net/ftp/ftp_directory_listing_parser_ls.h b/net/ftp/ftp_directory_listing_parser_ls.h
index 689fb7e..3fd0d32 100644
--- a/net/ftp/ftp_directory_listing_parser_ls.h
+++ b/net/ftp/ftp_directory_listing_parser_ls.h
@@ -26,6 +26,10 @@ class FtpDirectoryListingParserLs : public FtpDirectoryListingParser {
private:
bool received_nonempty_line_;
+ // True after we have received a "total n" listing header, where n is an
+ // integer. Only one such header is allowed per listing.
+ bool received_total_line_;
+
std::queue<FtpDirectoryListingEntry> entries_;
DISALLOW_COPY_AND_ASSIGN(FtpDirectoryListingParserLs);
diff --git a/net/ftp/ftp_directory_listing_parser_netware.cc b/net/ftp/ftp_directory_listing_parser_netware.cc
new file mode 100644
index 0000000..20d2b25
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser_netware.cc
@@ -0,0 +1,102 @@
+// 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_netware.h"
+
+#include <vector>
+
+#include "base/string_util.h"
+#include "net/ftp/ftp_util.h"
+
+namespace {
+
+bool LooksLikeNetwarePermissionsListing(const string16& text) {
+ if (text.length() != 10)
+ return false;
+
+ if (text[0] != '[' || text[9] != ']')
+ return false;
+ return (text[1] == 'R' || text[1] == '-') &&
+ (text[2] == 'W' || text[2] == '-') &&
+ (text[3] == 'C' || text[3] == '-') &&
+ (text[4] == 'E' || text[4] == '-') &&
+ (text[5] == 'A' || text[5] == '-') &&
+ (text[6] == 'F' || text[6] == '-') &&
+ (text[7] == 'M' || text[7] == '-') &&
+ (text[8] == 'S' || text[8] == '-');
+}
+
+} // namespace
+
+namespace net {
+
+FtpDirectoryListingParserNetware::FtpDirectoryListingParserNetware()
+ : received_first_line_(false) {
+}
+
+bool FtpDirectoryListingParserNetware::ConsumeLine(const string16& line) {
+ if (!received_first_line_) {
+ received_first_line_ = true;
+
+ return StartsWith(line, ASCIIToUTF16("total "), true);
+ }
+
+ std::vector<string16> columns;
+ SplitString(CollapseWhitespace(line, false), ' ', &columns);
+
+ if (columns.size() != 8)
+ return false;
+
+ FtpDirectoryListingEntry entry;
+
+ if (columns[0].length() != 1)
+ return false;
+ if (columns[0][0] == 'd') {
+ entry.type = FtpDirectoryListingEntry::DIRECTORY;
+ } else if (columns[0][0] == '-') {
+ entry.type = FtpDirectoryListingEntry::FILE;
+ } else {
+ return false;
+ }
+
+ // Note: on older Netware systems the permissions listing is in the same
+ // column as the entry type (just there is no space between them). We do not
+ // support the older format here for simplicity.
+ if (!LooksLikeNetwarePermissionsListing(columns[1]))
+ return false;
+
+ if (!StringToInt64(columns[3], &entry.size))
+ return false;
+ if (entry.size < 0)
+ return false;
+ if (entry.type != FtpDirectoryListingEntry::FILE)
+ entry.size = -1;
+
+ // Netware uses the same date listing format as Unix "ls -l".
+ if (!FtpUtil::LsDateListingToTime(columns[4], columns[5], columns[6],
+ &entry.last_modified)) {
+ return false;
+ }
+
+ entry.name = columns[7];
+
+ entries_.push(entry);
+ return true;
+}
+
+bool FtpDirectoryListingParserNetware::OnEndOfInput() {
+ return true;
+}
+
+bool FtpDirectoryListingParserNetware::EntryAvailable() const {
+ return !entries_.empty();
+}
+
+FtpDirectoryListingEntry FtpDirectoryListingParserNetware::PopEntry() {
+ FtpDirectoryListingEntry entry = entries_.front();
+ entries_.pop();
+ return entry;
+}
+
+} // namespace net
diff --git a/net/ftp/ftp_directory_listing_parser_netware.h b/net/ftp/ftp_directory_listing_parser_netware.h
new file mode 100644
index 0000000..a3b2cef
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser_netware.h
@@ -0,0 +1,37 @@
+// 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_NETWARE_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_NETWARE_H_
+
+#include <queue>
+
+#include "net/ftp/ftp_directory_listing_parser.h"
+
+namespace net {
+
+// Parser for Netware-style directory listing.
+class FtpDirectoryListingParserNetware : public FtpDirectoryListingParser {
+ public:
+ FtpDirectoryListingParserNetware();
+
+ // FtpDirectoryListingParser methods:
+ virtual FtpServerType GetServerType() const { return SERVER_NETWARE; }
+ virtual bool ConsumeLine(const string16& line);
+ virtual bool OnEndOfInput();
+ virtual bool EntryAvailable() const;
+ virtual FtpDirectoryListingEntry PopEntry();
+
+ private:
+ // True after we have received the first line of input.
+ bool received_first_line_;
+
+ std::queue<FtpDirectoryListingEntry> entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpDirectoryListingParserNetware);
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_NETWARE_H_
diff --git a/net/ftp/ftp_directory_listing_parser_netware_unittest.cc b/net/ftp/ftp_directory_listing_parser_netware_unittest.cc
new file mode 100644
index 0000000..7076a3f
--- /dev/null
+++ b/net/ftp/ftp_directory_listing_parser_netware_unittest.cc
@@ -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.
+
+#include "net/ftp/ftp_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "net/ftp/ftp_directory_listing_parser_netware.h"
+
+namespace {
+
+typedef net::FtpDirectoryListingParserTest FtpDirectoryListingParserNetwareTest;
+
+TEST_F(FtpDirectoryListingParserNetwareTest, Good) {
+ base::Time::Exploded now_exploded;
+ base::Time::Now().LocalExplode(&now_exploded);
+
+ const struct SingleLineTestData good_cases[] = {
+ { "d [RWCEAFMS] ftpadmin 512 Jan 29 2004 pub",
+ net::FtpDirectoryListingEntry::DIRECTORY, "pub", -1,
+ 2004, 1, 29, 0, 0 },
+ { "- [RW------] ftpadmin 123 Nov 11 18:25 afile",
+ net::FtpDirectoryListingEntry::FILE, "afile", 123,
+ now_exploded.year, 11, 11, 18, 25 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s", i, good_cases[i].input));
+
+ net::FtpDirectoryListingParserNetware parser;
+ // The parser requires a "total n" like before accepting regular input.
+ ASSERT_TRUE(parser.ConsumeLine(UTF8ToUTF16("total 1")));
+ RunSingleLineTestCase(&parser, good_cases[i]);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserNetwareTest, Bad) {
+ const char* bad_cases[] = {
+ "garbage",
+ "d [] ftpadmin 512 Jan 29 2004 pub",
+ "d [XGARBAGE] ftpadmin 512 Jan 29 2004 pub",
+ "d [RWCEAFMS] 512 Jan 29 2004 pub",
+ "d [RWCEAFMS] ftpadmin -1 Jan 29 2004 pub",
+ "l [RW------] ftpadmin 512 Jan 29 2004 pub",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ net::FtpDirectoryListingParserNetware parser;
+ // The parser requires a "total n" like before accepting regular input.
+ ASSERT_TRUE(parser.ConsumeLine(UTF8ToUTF16("total 1")));
+ EXPECT_FALSE(parser.ConsumeLine(UTF8ToUTF16(bad_cases[i]))) << bad_cases[i];
+ }
+}
+
+} // namespace
diff --git a/net/ftp/ftp_server_type_histograms.h b/net/ftp/ftp_server_type_histograms.h
index fef9bb2..ca9f6e4 100644
--- a/net/ftp/ftp_server_type_histograms.h
+++ b/net/ftp/ftp_server_type_histograms.h
@@ -35,6 +35,7 @@ enum FtpServerType {
SERVER_LS = 9, // Server using /bin/ls -l listing style.
SERVER_WINDOWS = 10, // Server using Windows listing style.
SERVER_VMS = 11, // Server using VMS listing style.
+ SERVER_NETWARE = 12, // Server using Netware listing style.
NUM_OF_SERVER_TYPES
};
diff --git a/net/ftp/ftp_util.cc b/net/ftp/ftp_util.cc
index 5d4cd37..a0a47cf 100644
--- a/net/ftp/ftp_util.cc
+++ b/net/ftp/ftp_util.cc
@@ -9,6 +9,7 @@
#include "base/logging.h"
#include "base/string_tokenizer.h"
#include "base/string_util.h"
+#include "base/time.h"
// For examples of Unix<->VMS path conversions, see the unit test file. On VMS
// a path looks differently depending on whether it's a file or directory.
@@ -143,5 +144,37 @@ bool FtpUtil::ThreeLetterMonthToNumber(const string16& text, int* number) {
return false;
}
+// static
+bool FtpUtil::LsDateListingToTime(const string16& month, const string16& day,
+ const string16& rest, base::Time* time) {
+ base::Time::Exploded time_exploded = { 0 };
+
+ if (!ThreeLetterMonthToNumber(month, &time_exploded.month))
+ return false;
+
+ if (!StringToInt(day, &time_exploded.day_of_month))
+ return false;
+
+ if (!StringToInt(rest, &time_exploded.year)) {
+ // Maybe it's time. Does it look like time (MM:HH)?
+ if (rest.length() != 5 || rest[2] != ':')
+ return false;
+
+ if (!StringToInt(rest.substr(0, 2), &time_exploded.hour))
+ return false;
+
+ if (!StringToInt(rest.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 listing, so just use local time.
+ *time = base::Time::FromLocalExploded(time_exploded);
+ return true;
+}
} // namespace
diff --git a/net/ftp/ftp_util.h b/net/ftp/ftp_util.h
index 2a73c68..518801b 100644
--- a/net/ftp/ftp_util.h
+++ b/net/ftp/ftp_util.h
@@ -9,6 +9,10 @@
#include "base/string16.h"
+namespace base {
+class Time;
+}
+
namespace net {
class FtpUtil {
@@ -25,6 +29,15 @@ class FtpUtil {
// Convert three-letter month abbreviation (like Nov) to its number (in range
// 1-12).
static bool ThreeLetterMonthToNumber(const string16& text, int* number);
+
+ // Convert a "ls -l" date listing to time. The listing comes in three columns.
+ // The first one contains month, the second one contains day of month.
+ // The first one is either a time (and then the current year is assumed),
+ // or is a year (and then we don't know the time).
+ static bool LsDateListingToTime(const string16& month,
+ const string16& day,
+ const string16& rest,
+ base::Time* time);
};
} // namespace net
diff --git a/net/ftp/ftp_util_unittest.cc b/net/ftp/ftp_util_unittest.cc
index 2ccb75b..46db873d 100644
--- a/net/ftp/ftp_util_unittest.cc
+++ b/net/ftp/ftp_util_unittest.cc
@@ -5,6 +5,9 @@
#include "net/ftp/ftp_util.h"
#include "base/basictypes.h"
+#include "base/format_macros.h"
+#include "base/string_util.h"
+#include "base/time.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
@@ -98,4 +101,53 @@ TEST(FtpUtilTest, VMSPathToUnix) {
}
}
+TEST(FtpUtilTest, LsDateListingToTime) {
+ base::Time::Exploded now_exploded;
+ base::Time::Now().LocalExplode(&now_exploded);
+
+ const struct {
+ // Input.
+ const char* month;
+ const char* day;
+ const char* rest;
+
+ // Expected output.
+ int expected_year;
+ int expected_month;
+ int expected_day_of_month;
+ int expected_hour;
+ int expected_minute;
+ } kTestCases[] = {
+ { "Nov", "01", "2007", 2007, 11, 1, 0, 0 },
+ { "Jul", "25", "13:37", now_exploded.year, 7, 25, 13, 37 },
+
+ // Test date listings in German, we should support them for FTP servers
+ // giving localized listings.
+ { "M\xc3\xa4r", "13", "2009", 2009, 3, 13, 0, 0 },
+ { "Mai", "1", "10:10", now_exploded.year, 5, 1, 10, 10 },
+ { "Okt", "14", "21:18", now_exploded.year, 10, 14, 21, 18 },
+ { "Dez", "25", "2008", 2008, 12, 25, 0, 0 },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
+ SCOPED_TRACE(StringPrintf("Test[%" PRIuS "]: %s %s %s", i,
+ kTestCases[i].month, kTestCases[i].day,
+ kTestCases[i].rest));
+
+ base::Time time;
+ ASSERT_TRUE(net::FtpUtil::LsDateListingToTime(
+ UTF8ToUTF16(kTestCases[i].month), UTF8ToUTF16(kTestCases[i].day),
+ UTF8ToUTF16(kTestCases[i].rest), &time));
+
+ base::Time::Exploded time_exploded;
+ time.LocalExplode(&time_exploded);
+ EXPECT_EQ(kTestCases[i].expected_year, time_exploded.year);
+ EXPECT_EQ(kTestCases[i].expected_month, time_exploded.month);
+ EXPECT_EQ(kTestCases[i].expected_day_of_month, time_exploded.day_of_month);
+ EXPECT_EQ(kTestCases[i].expected_hour, time_exploded.hour);
+ EXPECT_EQ(kTestCases[i].expected_minute, time_exploded.minute);
+ EXPECT_EQ(0, time_exploded.second);
+ EXPECT_EQ(0, time_exploded.millisecond);
+ }
+}
+
} // namespace