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 17namespace net { 18 19namespace { 20 21// Converts the filename component in listing to the filename we can display. 22// Returns true on success. 23bool 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 55bool 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 83bool 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 99bool 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 118bool LooksLikeVmsUserIdentificationCode(const string16& input) { 119 if (input.length() < 2) 120 return false; 121 return input[0] == '[' && input[input.length() - 1] == ']'; 122} 123 124bool 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 138bool 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 182bool 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