diff options
author | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-07 19:51:46 +0000 |
---|---|---|
committer | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-07 19:51:46 +0000 |
commit | 8425e66e2202fa2f226a5077618219cf307792f5 (patch) | |
tree | 4bcf13f19e21228c5ee771b327261d0e874cf80a /net/ftp | |
parent | 6f17bdfe86a739074d44cefb9256001585036ce1 (diff) | |
download | chromium_src-8425e66e2202fa2f226a5077618219cf307792f5.zip chromium_src-8425e66e2202fa2f226a5077618219cf307792f5.tar.gz chromium_src-8425e66e2202fa2f226a5077618219cf307792f5.tar.bz2 |
Implement parser for Netware-style FTP LIST response listing.
TEST=Covered by net_unittests.
BUG=25520
Review URL: http://codereview.chromium.org/465059
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@33978 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/ftp')
-rw-r--r-- | net/ftp/ftp_directory_listing_buffer.cc | 16 | ||||
-rw-r--r-- | net/ftp/ftp_directory_listing_buffer_unittest.cc | 6 | ||||
-rw-r--r-- | net/ftp/ftp_directory_listing_parser_ls.cc | 75 | ||||
-rw-r--r-- | net/ftp/ftp_directory_listing_parser_ls.h | 4 | ||||
-rw-r--r-- | net/ftp/ftp_directory_listing_parser_netware.cc | 102 | ||||
-rw-r--r-- | net/ftp/ftp_directory_listing_parser_netware.h | 37 | ||||
-rw-r--r-- | net/ftp/ftp_directory_listing_parser_netware_unittest.cc | 53 | ||||
-rw-r--r-- | net/ftp/ftp_server_type_histograms.h | 1 | ||||
-rw-r--r-- | net/ftp/ftp_util.cc | 33 | ||||
-rw-r--r-- | net/ftp/ftp_util.h | 13 | ||||
-rw-r--r-- | net/ftp/ftp_util_unittest.cc | 52 |
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 |