| Index: net/ftp/ftp_directory_listing_parser_vms.cc
|
| 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 0000000000000000000000000000000000000000..bfb14d4847d6b36e949e886ea10271f9fb4f9171
|
| --- /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
|
|
|