FTP: add directory listing parser for OS/2 format.

BUG=92154
TEST=navigate to ftp://ftp.os4.su/ - no errors should appear

Review URL: http://codereview.chromium.org/7590011

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@96275 0039d316-1c4b-4281-b951-d872f2087c98
parent 49308e28
0 DIR 08-08-11 01:47 Archive
0 DIR 01-13-11 10:18 BootCD
0 DIR 10-29-10 23:20 BootUSB512
65201 A 08-04-11 10:24 csort.exe
0 DIR 08-04-11 16:07 misc
0 DIR 06-24-11 01:18 moveton
1031106 A 08-08-11 01:47 os2krnlSVN3370_unoff.zip
603 A 01-08-11 14:18 SSE_TEST.EXE
91018 A 04-07-11 18:26 TETRIS.ZIP
d
Archive
-1
2011
8
8
1
47
d
BootCD
-1
2011
1
13
10
18
d
BootUSB512
-1
2010
10
29
23
20
-
csort.exe
65201
2011
8
4
10
24
d
misc
-1
2011
8
4
16
7
d
moveton
-1
2011
6
24
1
18
-
os2krnlSVN3370_unoff.zip
1031106
2011
8
8
1
47
-
SSE_TEST.EXE
603
2011
1
8
14
18
-
TETRIS.ZIP
91018
2011
4
7
18
26
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "net/base/net_errors.h" #include "net/base/net_errors.h"
#include "net/ftp/ftp_directory_listing_parser_ls.h" #include "net/ftp/ftp_directory_listing_parser_ls.h"
#include "net/ftp/ftp_directory_listing_parser_netware.h" #include "net/ftp/ftp_directory_listing_parser_netware.h"
#include "net/ftp/ftp_directory_listing_parser_os2.h"
#include "net/ftp/ftp_directory_listing_parser_vms.h" #include "net/ftp/ftp_directory_listing_parser_vms.h"
#include "net/ftp/ftp_directory_listing_parser_windows.h" #include "net/ftp/ftp_directory_listing_parser_windows.h"
#include "net/ftp/ftp_server_type_histograms.h" #include "net/ftp/ftp_server_type_histograms.h"
...@@ -71,6 +72,12 @@ int ParseListing(const string16& text, ...@@ -71,6 +72,12 @@ int ParseListing(const string16& text,
return FillInRawName(encoding, entries); return FillInRawName(encoding, entries);
} }
entries->clear();
if (ParseFtpDirectoryListingOS2(lines, entries)) {
*server_type = SERVER_OS2;
return FillInRawName(encoding, entries);
}
entries->clear(); entries->clear();
return ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT; return ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT;
} }
......
// Copyright (c) 2011 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_os2.h"
#include <vector>
#include "base/string_number_conversions.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "base/time.h"
#include "net/ftp/ftp_directory_listing_parser.h"
#include "net/ftp/ftp_util.h"
namespace net {
bool ParseFtpDirectoryListingOS2(
const std::vector<string16>& lines,
std::vector<FtpDirectoryListingEntry>* entries) {
for (size_t i = 0; i < lines.size(); i++) {
if (lines[i].empty())
continue;
std::vector<string16> columns;
base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
// Every line of the listing consists of the following:
//
// 1. size in bytes (0 for directories)
// 2. type (A for files, DIR for directories)
// 3. date
// 4. time
// 5. filename (may be empty or contain spaces)
//
// For now, make sure we have 1-4, and handle 5 later.
if (columns.size() < 4)
return false;
FtpDirectoryListingEntry entry;
if (!base::StringToInt64(columns[0], &entry.size))
return false;
if (EqualsASCII(columns[1], "DIR")) {
if (entry.size != 0)
return false;
entry.type = FtpDirectoryListingEntry::DIRECTORY;
entry.size = -1;
} else if (EqualsASCII(columns[1], "A")) {
entry.type = FtpDirectoryListingEntry::FILE;
if (entry.size < 0)
return false;
} else {
return false;
}
if (!FtpUtil::WindowsDateListingToTime(columns[2],
columns[3],
&entry.last_modified)) {
return false;
}
entry.name = FtpUtil::GetStringPartAfterColumns(lines[i], 4);
if (entry.name.empty()) {
// Some FTP servers send listing entries with empty names.
// It's not obvious how to display such an entry, so ignore them.
// We don't want to make the parsing fail at this point though.
// Other entries can still be useful.
continue;
}
entries->push_back(entry);
}
return true;
}
} // namespace net
// Copyright (c) 2011 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_OS2_H_
#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_OS2_H_
#pragma once
#include <vector>
#include "base/string16.h"
#include "net/base/net_api.h"
namespace net {
struct FtpDirectoryListingEntry;
// Parses OS/2 FTP directory listing. Returns true on success.
NET_TEST bool ParseFtpDirectoryListingOS2(
const std::vector<string16>& lines,
std::vector<FtpDirectoryListingEntry>* entries);
} // namespace net
#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_OS2_H_
// Copyright (c) 2011 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 "base/string_util.h"
#include "base/stringprintf.h"
#include "net/ftp/ftp_directory_listing_parser_os2.h"
namespace net {
namespace {
typedef FtpDirectoryListingParserTest FtpDirectoryListingParserOS2Test;
TEST_F(FtpDirectoryListingParserOS2Test, Good) {
const struct SingleLineTestData good_cases[] = {
{ "0 DIR 11-02-09 17:32 NT",
FtpDirectoryListingEntry::DIRECTORY, "NT", -1,
2009, 11, 2, 17, 32 },
{ "458 A 01-06-09 14:42 Readme.txt",
FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
2009, 1, 6, 14, 42 },
{ "1 A 01-06-09 02:42 Readme.txt",
FtpDirectoryListingEntry::FILE, "Readme.txt", 1,
2009, 1, 6, 2, 42 },
{ "458 A 01-06-01 02:42 Readme.txt",
FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
2001, 1, 6, 2, 42 },
{ "458 A 01-06-00 02:42 Corner1.txt",
FtpDirectoryListingEntry::FILE, "Corner1.txt", 458,
2000, 1, 6, 2, 42 },
{ "458 A 01-06-99 02:42 Corner2.txt",
FtpDirectoryListingEntry::FILE, "Corner2.txt", 458,
1999, 1, 6, 2, 42 },
{ "458 A 01-06-80 02:42 Corner3.txt",
FtpDirectoryListingEntry::FILE, "Corner3.txt", 458,
1980, 1, 6, 2, 42 },
{ "458 A 01-06-1979 02:42 Readme.txt",
FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
1979, 1, 6, 2, 42 },
{ "0 DIR 11-02-09 17:32 My Directory",
FtpDirectoryListingEntry::DIRECTORY, "My Directory", -1,
2009, 11, 2, 17, 32 },
{ "0 DIR 12-25-10 00:00 Christmas Midnight",
FtpDirectoryListingEntry::DIRECTORY, "Christmas Midnight", -1,
2010, 12, 25, 0, 0 },
{ "0 DIR 12-25-10 12:00 Christmas Midday",
FtpDirectoryListingEntry::DIRECTORY, "Christmas Midday", -1,
2010, 12, 25, 12, 0 },
};
for (size_t i = 0; i < arraysize(good_cases); i++) {
SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
good_cases[i].input));
std::vector<FtpDirectoryListingEntry> entries;
EXPECT_TRUE(ParseFtpDirectoryListingOS2(
GetSingleLineTestCase(good_cases[i].input),
&entries));
VerifySingleLineTestCase(good_cases[i], entries);
}
}
TEST_F(FtpDirectoryListingParserOS2Test, Ignored) {
const char* ignored_cases[] = {
"1234 A 12-07-10 12:05",
"0 DIR 11-02-09 05:32",
};
for (size_t i = 0; i < arraysize(ignored_cases); i++) {
SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
ignored_cases[i]));
std::vector<FtpDirectoryListingEntry> entries;
EXPECT_TRUE(ParseFtpDirectoryListingOS2(
GetSingleLineTestCase(ignored_cases[i]),
&entries));
EXPECT_EQ(0U, entries.size());
}
}
TEST_F(FtpDirectoryListingParserOS2Test, Bad) {
const char* bad_cases[] = {
"garbage",
"0 GARBAGE 11-02-09 05:32",
"0 GARBAGE 11-02-09 05:32 NT",
"0 DIR 11-FEB-09 05:32",
"0 DIR 11-02 05:32",
"-1 A 11-02-09 05:32",
"0 DIR 11-FEB-09 05:32",
"0 DIR 11-02 05:32 NT",
"-1 A 11-02-09 05:32 NT",
"0 A 99-25-10 12:00",
"0 A 12-99-10 12:00",
"0 A 12-25-10 99:00",
"0 A 12-25-10 12:99",
"0 A 99-25-10 12:00 months out of range",
"0 A 12-99-10 12:00 days out of range",
"0 A 12-25-10 99:00 hours out of range",
"0 A 12-25-10 12:99 minutes out of range",
};
for (size_t i = 0; i < arraysize(bad_cases); i++) {
SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
bad_cases[i]));
std::vector<FtpDirectoryListingEntry> entries;
EXPECT_FALSE(ParseFtpDirectoryListingOS2(
GetSingleLineTestCase(bad_cases[i]),
&entries));
}
}
} // namespace
} // namespace net
...@@ -139,6 +139,7 @@ const char* kTestFiles[] = { ...@@ -139,6 +139,7 @@ const char* kTestFiles[] = {
"dir-listing-netware-1", "dir-listing-netware-1",
"dir-listing-netware-2", "dir-listing-netware-2",
"dir-listing-os2-1",
"dir-listing-vms-1", "dir-listing-vms-1",
"dir-listing-vms-2", "dir-listing-vms-2",
"dir-listing-vms-3", "dir-listing-vms-3",
......
...@@ -13,65 +13,6 @@ ...@@ -13,65 +13,6 @@
#include "net/ftp/ftp_directory_listing_parser.h" #include "net/ftp/ftp_directory_listing_parser.h"
#include "net/ftp/ftp_util.h" #include "net/ftp/ftp_util.h"
namespace {
bool WindowsDateListingToTime(const std::vector<string16>& columns,
base::Time* time) {
DCHECK_LE(3U, columns.size());
base::Time::Exploded time_exploded = { 0 };
// Date should be in format MM-DD-YY[YY].
std::vector<string16> date_parts;
base::SplitString(columns[0], '-', &date_parts);
if (date_parts.size() != 3)
return false;
if (!base::StringToInt(date_parts[0], &time_exploded.month))
return false;
if (!base::StringToInt(date_parts[1], &time_exploded.day_of_month))
return false;
if (!base::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;
base::SplitString(columns[1].substr(0, 5), ':', &time_parts);
if (time_parts.size() != 2)
return false;
if (!base::StringToInt(time_parts[0], &time_exploded.hour))
return false;
if (!base::StringToInt(time_parts[1], &time_exploded.minute))
return false;
if (!time_exploded.HasValidValues())
return false;
string16 am_or_pm(columns[1].substr(5, 2));
if (EqualsASCII(am_or_pm, "PM")) {
if (time_exploded.hour < 12)
time_exploded.hour += 12;
} else if (EqualsASCII(am_or_pm, "AM")) {
if (time_exploded.hour == 12)
time_exploded.hour = 0;
} else {
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 { namespace net {
bool ParseFtpDirectoryListingWindows( bool ParseFtpDirectoryListingWindows(
...@@ -107,8 +48,11 @@ bool ParseFtpDirectoryListingWindows( ...@@ -107,8 +48,11 @@ bool ParseFtpDirectoryListingWindows(
return false; return false;
} }
if (!WindowsDateListingToTime(columns, &entry.last_modified)) if (!FtpUtil::WindowsDateListingToTime(columns[0],
columns[1],
&entry.last_modified)) {
return false; return false;
}
entry.name = FtpUtil::GetStringPartAfterColumns(lines[i], 3); entry.name = FtpUtil::GetStringPartAfterColumns(lines[i], 3);
if (entry.name.empty()) { if (entry.name.empty()) {
......
...@@ -73,6 +73,8 @@ TEST_F(FtpDirectoryListingParserWindowsTest, Ignored) { ...@@ -73,6 +73,8 @@ TEST_F(FtpDirectoryListingParserWindowsTest, Ignored) {
const char* ignored_cases[] = { const char* ignored_cases[] = {
"12-07-10 12:05AM <DIR> ", // http://crbug.com/66097 "12-07-10 12:05AM <DIR> ", // http://crbug.com/66097
"12-07-10 12:05AM 1234 ", "12-07-10 12:05AM 1234 ",
"11-02-09 05:32 <DIR>",
"11-02-09 05:32PM <DIR>",
}; };
for (size_t i = 0; i < arraysize(ignored_cases); i++) { for (size_t i = 0; i < arraysize(ignored_cases); i++) {
SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i, SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
...@@ -91,11 +93,9 @@ TEST_F(FtpDirectoryListingParserWindowsTest, Bad) { ...@@ -91,11 +93,9 @@ TEST_F(FtpDirectoryListingParserWindowsTest, Bad) {
"garbage", "garbage",
"11-02-09 05:32PM <GARBAGE>", "11-02-09 05:32PM <GARBAGE>",
"11-02-09 05:32PM <GARBAGE> NT", "11-02-09 05:32PM <GARBAGE> NT",
"11-02-09 05:32 <DIR>",
"11-FEB-09 05:32PM <DIR>", "11-FEB-09 05:32PM <DIR>",
"11-02 05:32PM <DIR>", "11-02 05:32PM <DIR>",
"11-02-09 05:32PM -1", "11-02-09 05:32PM -1",
"11-02-09 05:32 <DIR> NT",
"11-FEB-09 05:32PM <DIR> NT", "11-FEB-09 05:32PM <DIR> NT",
"11-02 05:32PM <DIR> NT", "11-02 05:32PM <DIR> NT",
"11-02-09 05:32PM -1 NT", "11-02-09 05:32PM -1 NT",
......
...@@ -27,6 +27,8 @@ enum FtpServerType { ...@@ -27,6 +27,8 @@ enum FtpServerType {
// Types 13-14 are RESERVED (were earlier used for MLSD listings). // Types 13-14 are RESERVED (were earlier used for MLSD listings).
SERVER_OS2 = 15, // Server using OS/2 listing style.
NUM_OF_SERVER_TYPES NUM_OF_SERVER_TYPES
}; };
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/memory/singleton.h" #include "base/memory/singleton.h"
#include "base/string_number_conversions.h" #include "base/string_number_conversions.h"
#include "base/string_split.h"
#include "base/string_tokenizer.h" #include "base/string_tokenizer.h"
#include "base/string_util.h" #include "base/string_util.h"
#include "base/time.h" #include "base/time.h"
...@@ -254,6 +255,67 @@ bool FtpUtil::LsDateListingToTime(const string16& month, const string16& day, ...@@ -254,6 +255,67 @@ bool FtpUtil::LsDateListingToTime(const string16& month, const string16& day,
return true; return true;
} }
// static
bool FtpUtil::WindowsDateListingToTime(const string16& date,
const string16& time,
base::Time* result) {
base::Time::Exploded time_exploded = { 0 };
// Date should be in format MM-DD-YY[YY].
std::vector<string16> date_parts;
base::SplitString(date, '-', &date_parts);
if (date_parts.size() != 3)
return false;
if (!base::StringToInt(date_parts[0], &time_exploded.month))
return false;
if (!base::StringToInt(date_parts[1], &time_exploded.day_of_month))
return false;
if (!base::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 (time.length() < 5)
return false;
std::vector<string16> time_parts;
base::SplitString(time.substr(0, 5), ':', &time_parts);
if (time_parts.size() != 2)
return false;
if (!base::StringToInt(time_parts[0], &time_exploded.hour))
return false;
if (!base::StringToInt(time_parts[1], &time_exploded.minute))
return false;
if (!time_exploded.HasValidValues())
return false;
if (time.length() > 5) {
if (time.length() != 7)
return false;
string16 am_or_pm(time.substr(5, 2));
if (EqualsASCII(am_or_pm, "PM")) {
if (time_exploded.hour < 12)
time_exploded.hour += 12;
} else if (EqualsASCII(am_or_pm, "AM")) {
if (time_exploded.hour == 12)
time_exploded.hour = 0;
} else {
return false;
}
}
// We don't know the time zone of the server, so just use local time.
*result = base::Time::FromLocalExploded(time_exploded);
return true;
}
// static // static
string16 FtpUtil::GetStringPartAfterColumns(const string16& text, int columns) { string16 FtpUtil::GetStringPartAfterColumns(const string16& text, int columns) {
base::i18n::UTF16CharIterator iter(&text); base::i18n::UTF16CharIterator iter(&text);
......
...@@ -43,6 +43,11 @@ class NET_TEST FtpUtil { ...@@ -43,6 +43,11 @@ class NET_TEST FtpUtil {
const base::Time& current_time, const base::Time& current_time,
base::Time* result); base::Time* result);
// Converts a Windows date listing to time. Returns true on success.
static bool WindowsDateListingToTime(const string16& date,
const string16& time,
base::Time* result);
// Skips |columns| columns from |text| (whitespace-delimited), and returns the // Skips |columns| columns from |text| (whitespace-delimited), and returns the
// remaining part, without leading/trailing whitespace. // remaining part, without leading/trailing whitespace.
static string16 GetStringPartAfterColumns(const string16& text, int columns); static string16 GetStringPartAfterColumns(const string16& text, int columns);
......
...@@ -175,6 +175,47 @@ TEST(FtpUtilTest, LsDateListingToTime) { ...@@ -175,6 +175,47 @@ TEST(FtpUtilTest, LsDateListingToTime) {
} }
} }
TEST(FtpUtilTest, WindowsDateListingToTime) {
const struct {
// Input.
const char* date;
const char* time;
// Expected output.
int expected_year;
int expected_month;
int expected_day_of_month;
int expected_hour;
int expected_minute;
} kTestCases[] = {
{ "11-01-07", "12:42", 2007, 11, 1, 12, 42 },
{ "11-01-07", "12:42AM", 2007, 11, 1, 0, 42 },
{ "11-01-07", "12:42PM", 2007, 11, 1, 12, 42 },
{ "11-01-2007", "12:42", 2007, 11, 1, 12, 42 },
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s %s", i,
kTestCases[i].date, kTestCases[i].time));
base::Time time;
ASSERT_TRUE(net::FtpUtil::WindowsDateListingToTime(
UTF8ToUTF16(kTestCases[i].date),
UTF8ToUTF16(kTestCases[i].time),
&time));
base::Time::Exploded time_exploded;
time.LocalExplode(&time_exploded);
EXPECT_EQ(kTestCases[i].expected_year, time_exploded.year);
EXPECT_EQ(kTestCases[i].expected_month, time_exploded.month);
EXPECT_EQ(kTestCases[i].expected_day_of_month, time_exploded.day_of_month);
EXPECT_EQ(kTestCases[i].expected_hour, time_exploded.hour);
EXPECT_EQ(kTestCases[i].expected_minute, time_exploded.minute);
EXPECT_EQ(0, time_exploded.second);
EXPECT_EQ(0, time_exploded.millisecond);
}
}
TEST(FtpUtilTest, GetStringPartAfterColumns) { TEST(FtpUtilTest, GetStringPartAfterColumns) {
const struct { const struct {
const char* text; const char* text;
......
...@@ -305,6 +305,8 @@ ...@@ -305,6 +305,8 @@
'ftp/ftp_directory_listing_parser_ls.h', 'ftp/ftp_directory_listing_parser_ls.h',
'ftp/ftp_directory_listing_parser_netware.cc', 'ftp/ftp_directory_listing_parser_netware.cc',
'ftp/ftp_directory_listing_parser_netware.h', 'ftp/ftp_directory_listing_parser_netware.h',
'ftp/ftp_directory_listing_parser_os2.cc',
'ftp/ftp_directory_listing_parser_os2.h',
'ftp/ftp_directory_listing_parser_vms.cc', 'ftp/ftp_directory_listing_parser_vms.cc',
'ftp/ftp_directory_listing_parser_vms.h', 'ftp/ftp_directory_listing_parser_vms.h',
'ftp/ftp_directory_listing_parser_windows.cc', 'ftp/ftp_directory_listing_parser_windows.cc',
...@@ -933,6 +935,7 @@ ...@@ -933,6 +935,7 @@
'ftp/ftp_ctrl_response_buffer_unittest.cc', 'ftp/ftp_ctrl_response_buffer_unittest.cc',
'ftp/ftp_directory_listing_parser_ls_unittest.cc', 'ftp/ftp_directory_listing_parser_ls_unittest.cc',
'ftp/ftp_directory_listing_parser_netware_unittest.cc', 'ftp/ftp_directory_listing_parser_netware_unittest.cc',
'ftp/ftp_directory_listing_parser_os2_unittest.cc',
'ftp/ftp_directory_listing_parser_unittest.cc', 'ftp/ftp_directory_listing_parser_unittest.cc',
'ftp/ftp_directory_listing_parser_vms_unittest.cc', 'ftp/ftp_directory_listing_parser_vms_unittest.cc',
'ftp/ftp_directory_listing_parser_windows_unittest.cc', 'ftp/ftp_directory_listing_parser_windows_unittest.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment