Home | History | Annotate | Download | only in ftp
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "net/ftp/ftp_directory_listing_parser_vms.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/string_number_conversions.h"
     10 #include "base/string_split.h"
     11 #include "base/string_util.h"
     12 #include "base/time.h"
     13 #include "base/utf_string_conversions.h"
     14 #include "net/ftp/ftp_directory_listing_parser.h"
     15 #include "net/ftp/ftp_util.h"
     16 
     17 namespace net {
     18 
     19 namespace {
     20 
     21 // Converts the filename component in listing to the filename we can display.
     22 // Returns true on success.
     23 bool ParseVmsFilename(const string16& raw_filename, string16* parsed_filename,
     24                       FtpDirectoryListingEntry::Type* type) {
     25   // On VMS, the files and directories are versioned. The version number is
     26   // separated from the file name by a semicolon. Example: ANNOUNCE.TXT;2.
     27   std::vector<string16> listing_parts;
     28   base::SplitString(raw_filename, ';', &listing_parts);
     29   if (listing_parts.size() != 2)
     30     return false;
     31   int version_number;
     32   if (!base::StringToInt(listing_parts[1], &version_number))
     33     return false;
     34   if (version_number < 0)
     35     return false;
     36 
     37   // Even directories have extensions in the listings. Don't display extensions
     38   // for directories; it's awkward for non-VMS users. Also, VMS is
     39   // case-insensitive, but generally uses uppercase characters. This may look
     40   // awkward, so we convert them to lower case.
     41   std::vector<string16> filename_parts;
     42   base::SplitString(listing_parts[0], '.', &filename_parts);
     43   if (filename_parts.size() != 2)
     44     return false;
     45   if (EqualsASCII(filename_parts[1], "DIR")) {
     46     *parsed_filename = StringToLowerASCII(filename_parts[0]);
     47     *type = FtpDirectoryListingEntry::DIRECTORY;
     48   } else {
     49     *parsed_filename = StringToLowerASCII(listing_parts[0]);
     50     *type = FtpDirectoryListingEntry::FILE;
     51   }
     52   return true;
     53 }
     54 
     55 bool ParseVmsFilesize(const string16& input, int64* size) {
     56   // VMS's directory listing gives us file size in blocks. We assume that
     57   // the block size is 512 bytes. It doesn't give accurate file size, but is the
     58   // best information we have.
     59   const int kBlockSize = 512;
     60 
     61   if (base::StringToInt64(input, size)) {
     62     *size *= kBlockSize;
     63     return true;
     64   }
     65 
     66   std::vector<string16> parts;
     67   base::SplitString(input, '/', &parts);
     68   if (parts.size() != 2)
     69     return false;
     70 
     71   int64 blocks_used, blocks_allocated;
     72   if (!base::StringToInt64(parts[0], &blocks_used))
     73     return false;
     74   if (!base::StringToInt64(parts[1], &blocks_allocated))
     75     return false;
     76   if (blocks_used > blocks_allocated)
     77     return false;
     78 
     79   *size = blocks_used * kBlockSize;
     80   return true;
     81 }
     82 
     83 bool LooksLikeVmsFileProtectionListingPart(const string16& input) {
     84   if (input.length() > 4)
     85     return false;
     86 
     87   // On VMS there are four different permission bits: Read, Write, Execute,
     88   // and Delete. They appear in that order in the permission listing.
     89   std::string pattern("RWED");
     90   string16 match(input);
     91   while (!match.empty() && !pattern.empty()) {
     92     if (match[0] == pattern[0])
     93       match = match.substr(1);
     94     pattern = pattern.substr(1);
     95   }
     96   return match.empty();
     97 }
     98 
     99 bool LooksLikeVmsFileProtectionListing(const string16& input) {
    100   if (input.length() < 2)
    101     return false;
    102   if (input[0] != '(' || input[input.length() - 1] != ')')
    103     return false;
    104 
    105   // We expect four parts of the file protection listing: for System, Owner,
    106   // Group, and World.
    107   std::vector<string16> parts;
    108   base::SplitString(input.substr(1, input.length() - 2), ',', &parts);
    109   if (parts.size() != 4)
    110     return false;
    111 
    112   return LooksLikeVmsFileProtectionListingPart(parts[0]) &&
    113       LooksLikeVmsFileProtectionListingPart(parts[1]) &&
    114       LooksLikeVmsFileProtectionListingPart(parts[2]) &&
    115       LooksLikeVmsFileProtectionListingPart(parts[3]);
    116 }
    117 
    118 bool LooksLikeVmsUserIdentificationCode(const string16& input) {
    119   if (input.length() < 2)
    120     return false;
    121   return input[0] == '[' && input[input.length() - 1] == ']';
    122 }
    123 
    124 bool LooksLikePermissionDeniedError(const string16& text) {
    125   static const char* kPermissionDeniedMessages[] = {
    126     "%RMS-E-PRV",
    127     "privilege",
    128   };
    129 
    130   for (size_t i = 0; i < arraysize(kPermissionDeniedMessages); i++) {
    131     if (text.find(ASCIIToUTF16(kPermissionDeniedMessages[i])) != string16::npos)
    132       return true;
    133   }
    134 
    135   return false;
    136 }
    137 
    138 bool VmsDateListingToTime(const std::vector<string16>& columns,
    139                           base::Time* time) {
    140   DCHECK_EQ(4U, columns.size());
    141 
    142   base::Time::Exploded time_exploded = { 0 };
    143 
    144   // Date should be in format DD-MMM-YYYY.
    145   std::vector<string16> date_parts;
    146   base::SplitString(columns[2], '-', &date_parts);
    147   if (date_parts.size() != 3)
    148     return false;
    149   if (!base::StringToInt(date_parts[0], &time_exploded.day_of_month))
    150     return false;
    151   if (!FtpUtil::AbbreviatedMonthToNumber(date_parts[1],
    152                                          &time_exploded.month))
    153     return false;
    154   if (!base::StringToInt(date_parts[2], &time_exploded.year))
    155     return false;
    156 
    157   // Time can be in format HH:MM, HH:MM:SS, or HH:MM:SS.mm. Try to recognize the
    158   // last type first. Do not parse the seconds, they will be ignored anyway.
    159   string16 time_column(columns[3]);
    160   if (time_column.length() == 11 && time_column[8] == '.')
    161     time_column = time_column.substr(0, 8);
    162   if (time_column.length() == 8 && time_column[5] == ':')
    163     time_column = time_column.substr(0, 5);
    164   if (time_column.length() != 5)
    165     return false;
    166   std::vector<string16> time_parts;
    167   base::SplitString(time_column, ':', &time_parts);
    168   if (time_parts.size() != 2)
    169     return false;
    170   if (!base::StringToInt(time_parts[0], &time_exploded.hour))
    171     return false;
    172   if (!base::StringToInt(time_parts[1], &time_exploded.minute))
    173     return false;
    174 
    175   // We don't know the time zone of the server, so just use local time.
    176   *time = base::Time::FromLocalExploded(time_exploded);
    177   return true;
    178 }
    179 
    180 }  // namespace
    181 
    182 bool ParseFtpDirectoryListingVms(
    183     const std::vector<string16>& lines,
    184     std::vector<FtpDirectoryListingEntry>* entries) {
    185   // The first non-empty line is the listing header. It often
    186   // starts with "Directory ", but not always. We set a flag after
    187   // seing the header.
    188   bool seen_header = false;
    189 
    190   for (size_t i = 0; i < lines.size(); i++) {
    191     if (lines[i].empty())
    192       continue;
    193 
    194     if (StartsWith(lines[i], ASCIIToUTF16("Total of "), true)) {
    195       // After the "total" line, all following lines must be empty.
    196       for (size_t j = i + 1; j < lines.size(); j++)
    197         if (!lines[j].empty())
    198           return false;
    199 
    200       return true;
    201     }
    202 
    203     if (!seen_header) {
    204       seen_header = true;
    205       continue;
    206     }
    207 
    208     if (LooksLikePermissionDeniedError(lines[i]))
    209       continue;
    210 
    211     std::vector<string16> columns;
    212     base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
    213 
    214     if (columns.size() == 1) {
    215       // There can be no continuation if the current line is the last one.
    216       if (i == lines.size() - 1)
    217         return false;
    218 
    219       // Join the current and next line and split them into columns.
    220       columns.clear();
    221       base::SplitString(
    222           CollapseWhitespace(lines[i] + ASCIIToUTF16(" ") + lines[i + 1],
    223                              false),
    224           ' ',
    225           &columns);
    226       i++;
    227     }
    228 
    229     FtpDirectoryListingEntry entry;
    230     if (!ParseVmsFilename(columns[0], &entry.name, &entry.type))
    231       return false;
    232 
    233     // There are different variants of a VMS listing. Some display
    234     // the protection listing and user identification code, some do not.
    235     if (columns.size() == 6) {
    236       if (!LooksLikeVmsFileProtectionListing(columns[5]))
    237         return false;
    238       if (!LooksLikeVmsUserIdentificationCode(columns[4]))
    239         return false;
    240 
    241       // Drop the unneeded data, so that the following code can always expect
    242       // just four columns.
    243       columns.resize(4);
    244     }
    245 
    246     if (columns.size() != 4)
    247       return false;
    248 
    249     if (!ParseVmsFilesize(columns[1], &entry.size))
    250       return false;
    251     if (entry.size < 0)
    252       return false;
    253     if (entry.type != FtpDirectoryListingEntry::FILE)
    254       entry.size = -1;
    255     if (!VmsDateListingToTime(columns, &entry.last_modified))
    256       return false;
    257 
    258     entries->push_back(entry);
    259   }
    260 
    261   // The only place where we return true is after receiving the "Total" line,
    262   // that should be present in every VMS listing.
    263   return false;
    264 }
    265 
    266 }  // namespace net
    267