15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/ftp/ftp_directory_listing_parser_vms.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <vector>
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
95e3f23d412006dc4db4e659864679f29341e113fTorne (Richard Coles)#include "base/strings/string_number_conversions.h"
102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/strings/string_split.h"
11eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "base/strings/string_util.h"
12868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/utf_string_conversions.h"
13eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "base/time/time.h"
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/ftp/ftp_directory_listing_parser.h"
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/ftp/ftp_util.h"
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace net {
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace {
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Converts the filename component in listing to the filename we can display.
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Returns true on success.
23c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool ParseVmsFilename(const base::string16& raw_filename,
24c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                      base::string16* parsed_filename,
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      FtpDirectoryListingEntry::Type* type) {
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // On VMS, the files and directories are versioned. The version number is
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // separated from the file name by a semicolon. Example: ANNOUNCE.TXT;2.
28c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  std::vector<base::string16> listing_parts;
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::SplitString(raw_filename, ';', &listing_parts);
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (listing_parts.size() != 2)
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  int version_number;
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!base::StringToInt(listing_parts[1], &version_number))
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (version_number < 0)
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Even directories have extensions in the listings. Don't display extensions
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // for directories; it's awkward for non-VMS users. Also, VMS is
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // case-insensitive, but generally uses uppercase characters. This may look
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // awkward, so we convert them to lower case.
42c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  std::vector<base::string16> filename_parts;
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::SplitString(listing_parts[0], '.', &filename_parts);
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (filename_parts.size() != 2)
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (EqualsASCII(filename_parts[1], "DIR")) {
476e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    *parsed_filename = base::StringToLowerASCII(filename_parts[0]);
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *type = FtpDirectoryListingEntry::DIRECTORY;
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
506e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    *parsed_filename = base::StringToLowerASCII(listing_parts[0]);
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *type = FtpDirectoryListingEntry::FILE;
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
56c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool ParseVmsFilesize(const base::string16& input, int64* size) {
57a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (base::ContainsOnlyChars(input, base::ASCIIToUTF16("*"))) {
582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // Response consisting of asterisks means unknown size.
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    *size = -1;
602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return true;
612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // VMS's directory listing gives us file size in blocks. We assume that
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // the block size is 512 bytes. It doesn't give accurate file size, but is the
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // best information we have.
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const int kBlockSize = 512;
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (base::StringToInt64(input, size)) {
692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (*size < 0)
702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return false;
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    *size *= kBlockSize;
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return true;
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  std::vector<base::string16> parts;
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::SplitString(input, '/', &parts);
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (parts.size() != 2)
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  int64 blocks_used, blocks_allocated;
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!base::StringToInt64(parts[0], &blocks_used))
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!base::StringToInt64(parts[1], &blocks_allocated))
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (blocks_used > blocks_allocated)
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (blocks_used < 0 || blocks_allocated < 0)
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return false;
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  *size = blocks_used * kBlockSize;
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
94c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool LooksLikeVmsFileProtectionListingPart(const base::string16& input) {
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (input.length() > 4)
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // On VMS there are four different permission bits: Read, Write, Execute,
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // and Delete. They appear in that order in the permission listing.
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::string pattern("RWED");
101c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  base::string16 match(input);
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while (!match.empty() && !pattern.empty()) {
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (match[0] == pattern[0])
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      match = match.substr(1);
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pattern = pattern.substr(1);
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return match.empty();
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
110c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool LooksLikeVmsFileProtectionListing(const base::string16& input) {
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (input.length() < 2)
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (input[0] != '(' || input[input.length() - 1] != ')')
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // We expect four parts of the file protection listing: for System, Owner,
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Group, and World.
118c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  std::vector<base::string16> parts;
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::SplitString(input.substr(1, input.length() - 2), ',', &parts);
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (parts.size() != 4)
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return LooksLikeVmsFileProtectionListingPart(parts[0]) &&
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      LooksLikeVmsFileProtectionListingPart(parts[1]) &&
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      LooksLikeVmsFileProtectionListingPart(parts[2]) &&
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      LooksLikeVmsFileProtectionListingPart(parts[3]);
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
129c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool LooksLikeVmsUserIdentificationCode(const base::string16& input) {
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (input.length() < 2)
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return input[0] == '[' && input[input.length() - 1] == ']';
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
135c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool LooksLikeVMSError(const base::string16& text) {
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  static const char* kPermissionDeniedMessages[] = {
1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    "%RMS-E-FNF",  // File not found.
1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    "%RMS-E-PRV",  // Access denied.
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    "%SYSTEM-F-NOPRIV",
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    "privilege",
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (size_t i = 0; i < arraysize(kPermissionDeniedMessages); i++) {
1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (text.find(base::ASCIIToUTF16(kPermissionDeniedMessages[i])) !=
145c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        base::string16::npos)
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return true;
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return false;
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
152c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool VmsDateListingToTime(const std::vector<base::string16>& columns,
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          base::Time* time) {
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK_EQ(4U, columns.size());
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::Time::Exploded time_exploded = { 0 };
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Date should be in format DD-MMM-YYYY.
159c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  std::vector<base::string16> date_parts;
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::SplitString(columns[2], '-', &date_parts);
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (date_parts.size() != 3)
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!base::StringToInt(date_parts[0], &time_exploded.day_of_month))
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!FtpUtil::AbbreviatedMonthToNumber(date_parts[1],
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                         &time_exploded.month))
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!base::StringToInt(date_parts[2], &time_exploded.year))
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Time can be in format HH:MM, HH:MM:SS, or HH:MM:SS.mm. Try to recognize the
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // last type first. Do not parse the seconds, they will be ignored anyway.
173c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  base::string16 time_column(columns[3]);
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (time_column.length() == 11 && time_column[8] == '.')
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    time_column = time_column.substr(0, 8);
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (time_column.length() == 8 && time_column[5] == ':')
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    time_column = time_column.substr(0, 5);
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (time_column.length() != 5)
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
180c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  std::vector<base::string16> time_parts;
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::SplitString(time_column, ':', &time_parts);
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (time_parts.size() != 2)
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!base::StringToInt(time_parts[0], &time_exploded.hour))
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!base::StringToInt(time_parts[1], &time_exploded.minute))
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // We don't know the time zone of the server, so just use local time.
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  *time = base::Time::FromLocalExploded(time_exploded);
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool ParseFtpDirectoryListingVms(
197c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    const std::vector<base::string16>& lines,
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    std::vector<FtpDirectoryListingEntry>* entries) {
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // The first non-empty line is the listing header. It often
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // starts with "Directory ", but not always. We set a flag after
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // seing the header.
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool seen_header = false;
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // Sometimes the listing doesn't end with a "Total" line, but
2052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // it's only okay when it contains some errors (it's needed
2062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // to distinguish it from "ls -l" format).
2072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  bool seen_error = false;
2082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (size_t i = 0; i < lines.size(); i++) {
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (lines[i].empty())
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue;
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (StartsWith(lines[i], base::ASCIIToUTF16("Total of "), true)) {
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // After the "total" line, all following lines must be empty.
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for (size_t j = i + 1; j < lines.size(); j++)
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (!lines[j].empty())
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          return false;
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return true;
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!seen_header) {
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      seen_header = true;
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue;
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (LooksLikeVMSError(lines[i])) {
2282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      seen_error = true;
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue;
2302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
232c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    std::vector<base::string16> columns;
233a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    base::SplitString(base::CollapseWhitespace(lines[i], false), ' ', &columns);
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (columns.size() == 1) {
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // There can be no continuation if the current line is the last one.
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (i == lines.size() - 1)
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return false;
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      // Skip the next line.
2412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      i++;
2422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      // This refers to the continuation line.
2442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if (LooksLikeVMSError(lines[i])) {
2452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        seen_error = true;
2462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        continue;
2472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      }
2482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // Join the current and next line and split them into columns.
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      base::SplitString(
251a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          base::CollapseWhitespace(
252a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              lines[i - 1] + base::ASCIIToUTF16(" ") + lines[i], false),
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          ' ',
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          &columns);
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    FtpDirectoryListingEntry entry;
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!ParseVmsFilename(columns[0], &entry.name, &entry.type))
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return false;
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // There are different variants of a VMS listing. Some display
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // the protection listing and user identification code, some do not.
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (columns.size() == 6) {
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (!LooksLikeVmsFileProtectionListing(columns[5]))
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return false;
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (!LooksLikeVmsUserIdentificationCode(columns[4]))
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return false;
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // Drop the unneeded data, so that the following code can always expect
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // just four columns.
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      columns.resize(4);
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (columns.size() != 4)
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return false;
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!ParseVmsFilesize(columns[1], &entry.size))
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return false;
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (entry.type != FtpDirectoryListingEntry::FILE)
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      entry.size = -1;
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!VmsDateListingToTime(columns, &entry.last_modified))
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return false;
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    entries->push_back(entry);
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // The only place where we return true is after receiving the "Total" line,
2882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // that should be present in every VMS listing. Alternatively, if the listing
2892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // contains error messages, it's OK not to have the "Total" line.
2902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return seen_error;
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace net
294