summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--net/data/ftp/dir-listing-vms-119
-rw-r--r--net/data/ftp/dir-listing-vms-1.expected98
-rw-r--r--net/data/ftp/dir-listing-vms-235
-rw-r--r--net/data/ftp/dir-listing-vms-2.expected260
-rw-r--r--net/data/ftp/dir-listing-vms-33
-rw-r--r--net/data/ftp/dir-listing-vms-3.expected0
-rw-r--r--net/data/ftp/dir-listing-vms-415
-rw-r--r--net/data/ftp/dir-listing-vms-4.expected71
-rw-r--r--net/ftp/ftp_directory_listing_buffer.cc34
-rw-r--r--net/ftp/ftp_directory_listing_buffer_unittest.cc32
-rw-r--r--net/ftp/ftp_directory_listing_parsers.cc335
-rw-r--r--net/ftp/ftp_directory_listing_parsers.h67
-rw-r--r--net/ftp/ftp_directory_listing_parsers_unittest.cc116
13 files changed, 1006 insertions, 79 deletions
diff --git a/net/data/ftp/dir-listing-vms-1 b/net/data/ftp/dir-listing-vms-1
new file mode 100644
index 0000000..ca7cdf1
--- /dev/null
+++ b/net/data/ftp/dir-listing-vms-1
@@ -0,0 +1,19 @@
+
+
+
+Directory ANONYMOUS_ROOT:[000000]
+
+.WELCOME;1 2 13-FEB-2002 23:32:40.47
+DECUS.DIR;1 1 9-MAY-2001 22:18:51.69
+INFORMATION.DIR;1 1 9-MAY-2001 22:23:42.78
+MADGOAT.DIR;1 2 9-MAY-2001 22:23:44.85
+MAIL_ARCHIVES.DIR;1
+ 1 13-DEC-2005 08:45:27.56
+MOZILLA.DIR;1 1 21-JUN-2001 14:57:51.38
+README.TXT;4 2 18-APR-2000 10:40:39.90
+SSH.DIR;1 1 22-JUN-2002 15:11:12.71
+SUPPORT.DIR;1 3 9-MAY-2001 22:29:45.02
+TCPWARE.DIR;1 1 9-MAY-2001 23:34:10.92
+VMS-FREEWARE.DIR;1 2 9-MAY-2001 23:58:31.39
+
+Total of 11 files, 17 blocks.
diff --git a/net/data/ftp/dir-listing-vms-1.expected b/net/data/ftp/dir-listing-vms-1.expected
new file mode 100644
index 0000000..5bc2ab5
--- /dev/null
+++ b/net/data/ftp/dir-listing-vms-1.expected
@@ -0,0 +1,98 @@
+-
+.welcome
+1024
+2002
+2
+13
+23
+32
+
+d
+decus
+-1
+2001
+5
+9
+22
+18
+
+d
+information
+-1
+2001
+5
+9
+22
+23
+
+d
+madgoat
+-1
+2001
+5
+9
+22
+23
+
+d
+mail_archives
+-1
+2005
+12
+13
+8
+45
+
+d
+mozilla
+-1
+2001
+6
+21
+14
+57
+
+-
+readme.txt
+1024
+2000
+4
+18
+10
+40
+
+d
+ssh
+-1
+2002
+6
+22
+15
+11
+
+d
+support
+-1
+2001
+5
+9
+22
+29
+
+d
+tcpware
+-1
+2001
+5
+9
+23
+34
+
+d
+vms-freeware
+-1
+2001
+5
+9
+23
+58
diff --git a/net/data/ftp/dir-listing-vms-2 b/net/data/ftp/dir-listing-vms-2
new file mode 100644
index 0000000..7fc20a9
--- /dev/null
+++ b/net/data/ftp/dir-listing-vms-2
@@ -0,0 +1,35 @@
+
+Directory SYS$SYSDEVICE:[ANONYMOUS]
+
+ANNOUNCE.TXT;2 1/16 12-MAR-2005 08:44:57 [SYSTEM] (RWED,RWED,RE,RE)
+BOINC.DIR;1 1/16 29-DEC-2005 21:33:21 [SYSTEM] (RWE,RWE,RE,RE)
+BZIP2.DIR;1 1/16 27-SEP-2005 19:45:39 [SYSTEM] (RWE,RWE,RE,RE)
+CDRTOOLS.DIR;1 3/16 10-MAR-2005 17:31:44 [SYSTEM] (RWE,RWE,RE,RE)
+DIFFUTILS.DIR;1 1/16 23-JUN-2007 23:04:21 [SYSTEM] (RWE,RWE,RE,RE)
+DTSS_NTP.DIR;1 2/16 25-SEP-2000 21:03:28 [SYSTEM] (RWE,RWE,RE,RE)
+FIXREC.DIR;1 1/16 20-DEC-2003 10:57:22 [SYSTEM] (RWE,RWE,RE,RE)
+GNUPG.DIR;1 1/16 9-AUG-2006 02:11:51 [SYSTEM] (RWE,RWE,RE,RE)
+GZIP.DIR;1 1/16 5-JUL-2006 21:59:45 [SYSTEM] (RWE,RWE,RE,RE)
+INFO-ZIP.DIR;1 15/16 20-SEP-2004 21:27:27 [SYSTEM] (RWE,RWE,RE,RE)
+INPUT.DIR;1 1/16 4-MAR-1999 22:14:34 [UCX$NOBO,ANONYMOUS] (RWE,RWE,RWE,RWE)
+KERMIT.DIR;1 1/16 25-FEB-2006 12:22:34 [SYSTEM] (RWE,RWE,RE,RE)
+LOGIN.COM;2 1/16 28-SEP-2006 09:20:32 [SYSTEM] (RWED,RWED,RE,RE)
+MISC.DIR;1 6/16 12-DEC-1999 17:31:56 [SYSTEM] (RWE,RWE,RE,RE)
+MMK.DIR;1 1/16 30-SEP-2009 08:06:26 [SYSTEM] (RWE,RWE,RE,RE)
+MOZ_TEST.DIR;1 1/16 8-APR-2008 17:12:53 [SYSTEM] (RWE,RWE,RE,RE)
+MPACK.DIR;1 1/16 21-AUG-2009 10:28:57 [SYSTEM] (RWE,RWE,RE,RE)
+MTOOLS.DIR;1 1/16 14-MAR-2006 15:05:01 [SYSTEM] (RWE,RWE,RE,RE)
+OPENSSL.DIR;1 1/16 12-JAN-2009 08:42:56 [SYSTEM] (RWE,RWE,RE,RE)
+PGP.DIR;1 1/16 19-SEP-1999 16:39:04 [SYSTEM] (RWE,RWE,RE,RE)
+PICS.DIR;1 No privilege for attempted operation
+QREADCD.DIR;1 1/16 29-SEP-2004 20:32:38 [SYSTEM] (RWE,RWE,RE,RE)
+RZSPINUP.DIR;1 1/16 24-JUL-2004 21:34:12 [SYSTEM] (RWE,RWE,RE,RE)
+TEST.DIR;1 1/16 5-NOV-2008 21:59:10 [SYSTEM] (RWE,RWE,RE,RE)
+VIM.DIR;1 1/16 30-APR-2005 16:32:56 [SYSTEM] (RWE,RWE,RE,RE)
+VMSTAR.DIR;1 1/16 7-JUN-2007 09:36:04 [SYSTEM] (RWE,RWE,RE,RE)
+WELCOME.TXT;3 1/16 12-MAR-2005 08:45:28 [SYSTEM] (RWED,RWED,RE,RE)
+WGET.DIR;1 3/16 17-AUG-1999 20:41:54 [SYSTEM] (RWE,RWE,RE,RE)
+WGET_TEST.DIR;1 1/16 13-JUN-2006 21:29:27 [SYSTEM] (RWE,RWE,RE,RE)
+WPUT.DIR;1 1/16 9-DEC-2004 20:16:46 [SYSTEM] (RWE,RWE,RE,RE)
+
+Total of 30 files, 53/464 blocks
diff --git a/net/data/ftp/dir-listing-vms-2.expected b/net/data/ftp/dir-listing-vms-2.expected
new file mode 100644
index 0000000..6b3ca66
--- /dev/null
+++ b/net/data/ftp/dir-listing-vms-2.expected
@@ -0,0 +1,260 @@
+-
+announce.txt
+512
+2005
+3
+12
+8
+44
+
+d
+boinc
+-1
+2005
+12
+29
+21
+33
+
+d
+bzip2
+-1
+2005
+9
+27
+19
+45
+
+d
+cdrtools
+-1
+2005
+3
+10
+17
+31
+
+d
+diffutils
+-1
+2007
+6
+23
+23
+4
+
+d
+dtss_ntp
+-1
+2000
+9
+25
+21
+3
+
+d
+fixrec
+-1
+2003
+12
+20
+10
+57
+
+d
+gnupg
+-1
+2006
+8
+9
+2
+11
+
+d
+gzip
+-1
+2006
+7
+5
+21
+59
+
+d
+info-zip
+-1
+2004
+9
+20
+21
+27
+
+d
+input
+-1
+1999
+3
+4
+22
+14
+
+d
+kermit
+-1
+2006
+2
+25
+12
+22
+
+-
+login.com
+512
+2006
+9
+28
+9
+20
+
+d
+misc
+-1
+1999
+12
+12
+17
+31
+
+d
+mmk
+-1
+2009
+9
+30
+8
+6
+
+d
+moz_test
+-1
+2008
+4
+8
+17
+12
+
+d
+mpack
+-1
+2009
+8
+21
+10
+28
+
+d
+mtools
+-1
+2006
+3
+14
+15
+5
+
+d
+openssl
+-1
+2009
+1
+12
+8
+42
+
+d
+pgp
+-1
+1999
+9
+19
+16
+39
+
+d
+qreadcd
+-1
+2004
+9
+29
+20
+32
+
+d
+rzspinup
+-1
+2004
+7
+24
+21
+34
+
+d
+test
+-1
+2008
+11
+5
+21
+59
+
+d
+vim
+-1
+2005
+4
+30
+16
+32
+
+d
+vmstar
+-1
+2007
+6
+7
+9
+36
+
+-
+welcome.txt
+512
+2005
+3
+12
+8
+45
+
+d
+wget
+-1
+1999
+8
+17
+20
+41
+
+d
+wget_test
+-1
+2006
+6
+13
+21
+29
+
+d
+wput
+-1
+2004
+12
+9
+20
+16
diff --git a/net/data/ftp/dir-listing-vms-3 b/net/data/ftp/dir-listing-vms-3
new file mode 100644
index 0000000..3bda7ff
--- /dev/null
+++ b/net/data/ftp/dir-listing-vms-3
@@ -0,0 +1,3 @@
+
+
+Total of 0 blocks in 0 files in 0 directories.
diff --git a/net/data/ftp/dir-listing-vms-3.expected b/net/data/ftp/dir-listing-vms-3.expected
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/net/data/ftp/dir-listing-vms-3.expected
diff --git a/net/data/ftp/dir-listing-vms-4 b/net/data/ftp/dir-listing-vms-4
new file mode 100644
index 0000000..0954441
--- /dev/null
+++ b/net/data/ftp/dir-listing-vms-4
@@ -0,0 +1,15 @@
+
+EISNER$DRA3:[DECUSERVE_USER.JOHNDOE]
+
+$MAIN.TPU$JOURNAL;1 1 4-NOV-2009 05:59 [JOHNDOE] (RWED,RWED,,)
+.WELCOME;1 1 4-NOV-2009 06:02 [JOHNDOE] (RWED,RWED,,)
+EXAMPLE.TXT;1 1 4-NOV-2009 06:02 [JOHNDOE] (RWED,RWED,,)
+FILE.;1 1 4-NOV-2009 08:59 [JOHNDOE] (RWED,RWED,,)
+FTP_SERVER.LOG;12 0 4-NOV-2009 09:12 [JOHNDOE] (RWED,RWED,,)
+LOGIN.COM;1 2 4-NOV-2009 05:58 [JOHNDOE] (RWED,RWED,,)
+NOTES$NOTEBOOK.NOTE;1
+ 36 4-NOV-2009 05:55 [DECUSERVE] (RWE,RWE,,)
+TEST.DIR;1 1 4-NOV-2009 08:15 [JOHNDOE] (RWE,RWE,,)
+
+
+Total of 43 blocks in 8 files.
diff --git a/net/data/ftp/dir-listing-vms-4.expected b/net/data/ftp/dir-listing-vms-4.expected
new file mode 100644
index 0000000..3c89235
--- /dev/null
+++ b/net/data/ftp/dir-listing-vms-4.expected
@@ -0,0 +1,71 @@
+-
+$main.tpu$journal
+512
+2009
+11
+4
+5
+59
+
+-
+.welcome
+512
+2009
+11
+4
+6
+2
+
+-
+example.txt
+512
+2009
+11
+4
+6
+2
+
+-
+file.
+512
+2009
+11
+4
+8
+59
+
+-
+ftp_server.log
+0
+2009
+11
+4
+9
+12
+
+-
+login.com
+1024
+2009
+11
+4
+5
+58
+
+-
+notes$notebook.note
+18432
+2009
+11
+4
+5
+55
+
+d
+test
+-1
+2009
+11
+4
+8
+15
diff --git a/net/ftp/ftp_directory_listing_buffer.cc b/net/ftp/ftp_directory_listing_buffer.cc
index 0099e91..030da83 100644
--- a/net/ftp/ftp_directory_listing_buffer.cc
+++ b/net/ftp/ftp_directory_listing_buffer.cc
@@ -37,10 +37,11 @@ std::string DetectEncoding(const std::string& text) {
} // namespace
namespace net {
-
+
FtpDirectoryListingBuffer::FtpDirectoryListingBuffer()
: current_parser_(NULL) {
parsers_.insert(new FtpLsDirectoryListingParser());
+ parsers_.insert(new FtpVmsDirectoryListingParser());
}
FtpDirectoryListingBuffer::~FtpDirectoryListingBuffer() {
@@ -66,11 +67,11 @@ int FtpDirectoryListingBuffer::ProcessRemainingData() {
return ParseLines();
}
-
+
bool FtpDirectoryListingBuffer::EntryAvailable() const {
return (current_parser_ ? current_parser_->EntryAvailable() : false);
}
-
+
FtpDirectoryListingEntry FtpDirectoryListingBuffer::PopEntry() {
DCHECK(EntryAvailable());
return current_parser_->PopEntry();
@@ -86,19 +87,24 @@ bool FtpDirectoryListingBuffer::ConvertToDetectedEncoding(
int FtpDirectoryListingBuffer::ExtractFullLinesFromBuffer() {
if (encoding_.empty())
encoding_ = DetectEncoding(buffer_);
-
+
int cut_pos = 0;
+ // TODO(phajdan.jr): This code accepts all endlines matching \r*\n. Should it
+ // be more strict, or enforce consistent line endings?
for (size_t i = 0; i < buffer_.length(); ++i) {
- if (i >= 1 && buffer_[i - 1] == '\r' && buffer_[i] == '\n') {
- std::string line(buffer_.substr(cut_pos, i - cut_pos - 1));
- cut_pos = i + 1;
- string16 line_converted;
- if (!ConvertToDetectedEncoding(line, &line_converted)) {
- buffer_.erase(0, cut_pos);
- return ERR_ENCODING_CONVERSION_FAILED;
- }
- lines_.push_back(line_converted);
+ if (buffer_[i] != '\n')
+ continue;
+ int line_length = i - cut_pos;
+ if (i >= 1 && buffer_[i - 1] == '\r')
+ line_length--;
+ std::string line(buffer_.substr(cut_pos, line_length));
+ cut_pos = i + 1;
+ string16 line_converted;
+ if (!ConvertToDetectedEncoding(line, &line_converted)) {
+ buffer_.erase(0, cut_pos);
+ return ERR_ENCODING_CONVERSION_FAILED;
}
+ lines_.push_back(line_converted);
}
buffer_.erase(0, cut_pos);
return OK;
@@ -127,7 +133,7 @@ int FtpDirectoryListingBuffer::ParseLines() {
current_parser_ = *parsers_.begin();
}
}
-
+
return OK;
}
diff --git a/net/ftp/ftp_directory_listing_buffer_unittest.cc b/net/ftp/ftp_directory_listing_buffer_unittest.cc
index f72a151..f8f1a51 100644
--- a/net/ftp/ftp_directory_listing_buffer_unittest.cc
+++ b/net/ftp/ftp_directory_listing_buffer_unittest.cc
@@ -18,45 +18,49 @@ TEST(FtpDirectoryListingBufferTest, Parse) {
const char* test_files[] = {
"dir-listing-ls-1",
"dir-listing-ls-2",
+ "dir-listing-vms-1",
+ "dir-listing-vms-2",
+ "dir-listing-vms-3",
+ "dir-listing-vms-4",
};
-
+
FilePath test_dir;
PathService::Get(base::DIR_SOURCE_ROOT, &test_dir);
test_dir = test_dir.AppendASCII("net");
test_dir = test_dir.AppendASCII("data");
test_dir = test_dir.AppendASCII("ftp");
-
+
for (size_t i = 0; i < arraysize(test_files); i++) {
SCOPED_TRACE(StringPrintf("Test[%d]: %s", i, test_files[i]));
-
+
net::FtpDirectoryListingBuffer buffer;
-
+
std::string test_listing;
EXPECT_TRUE(file_util::ReadFileToString(test_dir.AppendASCII(test_files[i]),
&test_listing));
-
+
EXPECT_EQ(net::OK, buffer.ConsumeData(test_listing.data(),
test_listing.length()));
EXPECT_EQ(net::OK, buffer.ProcessRemainingData());
-
+
std::string expected_listing;
ASSERT_TRUE(file_util::ReadFileToString(
test_dir.AppendASCII(std::string(test_files[i]) + ".expected"),
&expected_listing));
-
+
std::vector<std::string> lines;
StringTokenizer tokenizer(expected_listing, "\r\n");
while (tokenizer.GetNext())
lines.push_back(tokenizer.token());
ASSERT_EQ(0U, lines.size() % 8);
-
+
for (size_t i = 0; i < lines.size() / 8; i++) {
std::string type(lines[8 * i]);
std::string name(lines[8 * i + 1]);
int64 size = StringToInt64(lines[8 * i + 2]);
-
+
SCOPED_TRACE(StringPrintf("Filename: %s", name.c_str()));
-
+
int year;
if (lines[8 * i + 3] == "current") {
base::Time::Exploded now_exploded;
@@ -69,10 +73,10 @@ TEST(FtpDirectoryListingBufferTest, Parse) {
int day_of_month = StringToInt(lines[8 * i + 5]);
int hour = StringToInt(lines[8 * i + 6]);
int minute = StringToInt(lines[8 * i + 7]);
-
+
ASSERT_TRUE(buffer.EntryAvailable());
net::FtpDirectoryListingEntry entry = buffer.PopEntry();
-
+
if (type == "d") {
EXPECT_EQ(net::FtpDirectoryListingEntry::DIRECTORY, entry.type);
} else if (type == "-") {
@@ -82,10 +86,10 @@ TEST(FtpDirectoryListingBufferTest, Parse) {
} else {
ADD_FAILURE() << "invalid gold test data: " << type;
}
-
+
EXPECT_EQ(UTF8ToUTF16(name), entry.name);
EXPECT_EQ(size, entry.size);
-
+
base::Time::Exploded time_exploded;
entry.last_modified.LocalExplode(&time_exploded);
EXPECT_EQ(year, time_exploded.year);
diff --git a/net/ftp/ftp_directory_listing_parsers.cc b/net/ftp/ftp_directory_listing_parsers.cc
index 60cef80..5f160e3 100644
--- a/net/ftp/ftp_directory_listing_parsers.cc
+++ b/net/ftp/ftp_directory_listing_parsers.cc
@@ -7,11 +7,11 @@
#include "base/string_util.h"
namespace {
-
+
bool LooksLikeUnixPermission(const string16& text) {
if (text.length() != 3)
return false;
-
+
return ((text[0] == 'r' || text[0] == '-') &&
(text[1] == 'w' || text[1] == '-') &&
(text[2] == 'x' || text[2] == 's' || text[2] == 'S' ||
@@ -21,80 +21,221 @@ bool LooksLikeUnixPermission(const string16& text) {
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 IsStringNonNegativeNumber(const string16& text) {
+
+bool IsStringNonNegativeInteger(const string16& text) {
int number;
if (!StringToInt(text, &number))
return false;
-
+
return number >= 0;
}
-
+
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;
}
}
-
+
return false;
}
-
+
bool UnixDateListingToTime(const std::vector<string16>& columns,
base::Time* time) {
DCHECK_EQ(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;
+}
+
+// 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 (filename_parts[1] == ASCIIToUTF16("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() {
}
@@ -105,17 +246,17 @@ bool FtpLsDirectoryListingParser::ConsumeLine(const string16& line) {
// Check if it is a symlink.
if (columns[9] != ASCIIToUTF16("->"))
return false;
-
+
// Drop the symlink target from columns, we don't use it.
columns.resize(9);
}
-
+
if (columns.size() != 9)
return false;
-
+
if (!LooksLikeUnixPermissionsListing(columns[0]))
return false;
-
+
FtpDirectoryListingEntry entry;
if (columns[0][0] == 'l') {
entry.type = FtpDirectoryListingEntry::SYMLINK;
@@ -124,34 +265,162 @@ bool FtpLsDirectoryListingParser::ConsumeLine(const string16& line) {
} else {
entry.type = FtpDirectoryListingEntry::FILE;
}
-
- if (!IsStringNonNegativeNumber(columns[1]))
+
+ 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 = columns[8];
-
+
entries_.push(entry);
return true;
}
-
+
bool FtpLsDirectoryListingParser::EntryAvailable() const {
return !entries_.empty();
}
-
+
FtpDirectoryListingEntry FtpLsDirectoryListingParser::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::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
index 2f402e7..230c641 100644
--- a/net/ftp/ftp_directory_listing_parsers.h
+++ b/net/ftp/ftp_directory_listing_parsers.h
@@ -13,18 +13,18 @@
#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;
};
@@ -32,33 +32,78 @@ struct FtpDirectoryListingEntry {
class FtpDirectoryListingParser {
public:
virtual ~FtpDirectoryListingParser();
-
+
// Adds |line| to the internal parsing buffer. Returns true on success.
virtual bool ConsumeLine(const string16& line) = 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 bool ConsumeLine(const string16& line);
virtual bool EntryAvailable() const;
virtual FtpDirectoryListingEntry PopEntry();
-
+
private:
std::queue<FtpDirectoryListingEntry> entries_;
-
+
DISALLOW_COPY_AND_ASSIGN(FtpLsDirectoryListingParser);
};
-
+
+// Parser for VMS-style directory listing (including variants).
+class FtpVmsDirectoryListingParser : public FtpDirectoryListingParser {
+ public:
+ FtpVmsDirectoryListingParser();
+
+ // FtpDirectoryListingParser methods:
+ virtual bool ConsumeLine(const string16& line);
+ 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
index b7d9499..cc05f36 100644
--- a/net/ftp/ftp_directory_listing_parsers_unittest.cc
+++ b/net/ftp/ftp_directory_listing_parsers_unittest.cc
@@ -8,7 +8,7 @@
#include "testing/gtest/include/gtest/gtest.h"
namespace {
-
+
struct SingleLineTestData {
const char* input;
net::FtpDirectoryListingEntry::Type type;
@@ -25,7 +25,7 @@ class FtpDirectoryListingParsersTest : public testing::Test {
protected:
FtpDirectoryListingParsersTest() {
}
-
+
void RunSingleLineTestCase(net::FtpDirectoryListingParser* parser,
const SingleLineTestData& test_case) {
ASSERT_TRUE(parser->ConsumeLine(UTF8ToUTF16(test_case.input)));
@@ -34,7 +34,7 @@ class FtpDirectoryListingParsersTest : public testing::Test {
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);
@@ -45,7 +45,7 @@ class FtpDirectoryListingParsersTest : public testing::Test {
EXPECT_EQ(0, time_exploded.second);
EXPECT_EQ(0, time_exploded.millisecond);
}
-
+
private:
DISALLOW_COPY_AND_ASSIGN(FtpDirectoryListingParsersTest);
};
@@ -53,7 +53,7 @@ class FtpDirectoryListingParsersTest : public testing::Test {
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,
@@ -70,11 +70,11 @@ TEST_F(FtpDirectoryListingParsersTest, Ls) {
};
for (size_t i = 0; i < arraysize(good_cases); i++) {
SCOPED_TRACE(StringPrintf("Test[%d]: %s", i, good_cases[i].input));
-
+
net::FtpLsDirectoryListingParser parser;
RunSingleLineTestCase(&parser, good_cases[i]);
}
-
+
const char* bad_cases[] = {
"",
"garbage",
@@ -92,4 +92,106 @@ TEST_F(FtpDirectoryListingParsersTest, Ls) {
}
}
+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[%d]: %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[%d]: %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