blob: c74dad031c2fd85df5a5d7d0a090e3b2835301ac [file] [log] [blame]
// Copyright (c) 2010 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_number_conversions.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.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;
base::SplitString(raw_filename, ';', &listing_parts);
if (listing_parts.size() != 2)
return false;
int version_number;
if (!base::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;
base::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 (base::StringToInt64(input, size)) {
*size *= kBlockSize;
return true;
}
std::vector<string16> parts;
base::SplitString(input, '/', &parts);
if (parts.size() != 2)
return false;
int64 blocks_used, blocks_allocated;
if (!base::StringToInt64(parts[0], &blocks_used))
return false;
if (!base::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;
base::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;
base::SplitString(columns[1], '-', &date_parts);
if (date_parts.size() != 3)
return false;
if (!base::StringToInt(date_parts[0], &time_exploded.day_of_month))
return false;
if (!net::FtpUtil::ThreeLetterMonthToNumber(date_parts[1],
&time_exploded.month))
return false;
if (!base::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;
base::SplitString(time_column, ':', &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;
// 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) {
}
FtpDirectoryListingParserVms::~FtpDirectoryListingParserVms() {}
FtpServerType FtpDirectoryListingParserVms::GetServerType() const {
return SERVER_VMS;
}
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;
base::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