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