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