| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this | 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 | 2 // source code is governed by a BSD-style license that can be found in the |
| 3 // LICENSE file. | 3 // LICENSE file. |
| 4 | 4 |
| 5 #include "net/ftp/ftp_directory_listing_parsers.h" | 5 #include "net/ftp/ftp_directory_listing_parsers.h" |
| 6 | 6 |
| 7 #include <ctype.h> |
| 8 |
| 7 #include "base/string_util.h" | 9 #include "base/string_util.h" |
| 8 | 10 |
| 9 namespace { | 11 namespace { |
| 10 | 12 |
| 11 bool LooksLikeUnixPermission(const string16& text) { | 13 bool LooksLikeUnixPermission(const string16& text) { |
| 12 if (text.length() != 3) | 14 if (text.length() != 3) |
| 13 return false; | 15 return false; |
| 14 | 16 |
| 17 // Meaning of the flags: |
| 18 // r - file is readable |
| 19 // w - file is writable |
| 20 // x - file is executable |
| 21 // s or S - setuid/setgid bit set |
| 22 // t or T - "sticky" bit set |
| 15 return ((text[0] == 'r' || text[0] == '-') && | 23 return ((text[0] == 'r' || text[0] == '-') && |
| 16 (text[1] == 'w' || text[1] == '-') && | 24 (text[1] == 'w' || text[1] == '-') && |
| 17 (text[2] == 'x' || text[2] == 's' || text[2] == 'S' || | 25 (text[2] == 'x' || text[2] == 's' || text[2] == 'S' || |
| 18 text[2] == '-')); | 26 text[2] == 't' || text[2] == 'T' || text[2] == '-')); |
| 19 } | 27 } |
| 20 | 28 |
| 21 bool LooksLikeUnixPermissionsListing(const string16& text) { | 29 bool LooksLikeUnixPermissionsListing(const string16& text) { |
| 22 if (text.length() != 10) | 30 if (text.length() != 10) |
| 23 return false; | 31 return false; |
| 24 | 32 |
| 25 if (text[0] != 'b' && text[0] != 'c' && text[0] != 'd' && | 33 if (text[0] != 'b' && text[0] != 'c' && text[0] != 'd' && |
| 26 text[0] != 'l' && text[0] != 'p' && text[0] != 's' && | 34 text[0] != 'l' && text[0] != 'p' && text[0] != 's' && |
| 27 text[0] != '-') | 35 text[0] != '-') |
| 28 return false; | 36 return false; |
| 29 | 37 |
| 30 return (LooksLikeUnixPermission(text.substr(1, 3)) && | 38 return (LooksLikeUnixPermission(text.substr(1, 3)) && |
| 31 LooksLikeUnixPermission(text.substr(4, 3)) && | 39 LooksLikeUnixPermission(text.substr(4, 3)) && |
| 32 LooksLikeUnixPermission(text.substr(7, 3))); | 40 LooksLikeUnixPermission(text.substr(7, 3))); |
| 33 } | 41 } |
| 34 | 42 |
| 35 bool IsStringNonNegativeInteger(const string16& text) { | 43 bool IsStringNonNegativeInteger(const string16& text) { |
| 36 int number; | 44 int number; |
| 37 if (!StringToInt(text, &number)) | 45 if (!StringToInt(text, &number)) |
| 38 return false; | 46 return false; |
| 39 | 47 |
| 40 return number >= 0; | 48 return number >= 0; |
| 41 } | 49 } |
| 42 | 50 |
| 51 string16 GetStringPartAfterColumns(const string16& text, int columns) { |
| 52 DCHECK_LE(1, columns); |
| 53 int columns_so_far = 0; |
| 54 size_t last = 0; |
| 55 for (size_t i = 1; i < text.length(); ++i) { |
| 56 if (!isspace(text[i - 1]) && isspace(text[i])) { |
| 57 last = i; |
| 58 if (++columns_so_far == columns) |
| 59 break; |
| 60 } |
| 61 } |
| 62 string16 result(text.substr(last)); |
| 63 TrimWhitespace(result, TRIM_ALL, &result); |
| 64 return result; |
| 65 } |
| 66 |
| 43 bool ThreeLetterMonthToNumber(const string16& text, int* number) { | 67 bool ThreeLetterMonthToNumber(const string16& text, int* number) { |
| 44 const static char* months[] = { "jan", "feb", "mar", "apr", "may", "jun", | 68 const static char* months[] = { "jan", "feb", "mar", "apr", "may", "jun", |
| 45 "jul", "aug", "sep", "oct", "nov", "dec" }; | 69 "jul", "aug", "sep", "oct", "nov", "dec" }; |
| 46 | 70 |
| 47 for (size_t i = 0; i < arraysize(months); i++) { | 71 for (size_t i = 0; i < arraysize(months); i++) { |
| 48 if (LowerCaseEqualsASCII(text, months[i])) { | 72 if (LowerCaseEqualsASCII(text, months[i])) { |
| 49 *number = i + 1; | 73 *number = i + 1; |
| 50 return true; | 74 return true; |
| 51 } | 75 } |
| 52 } | 76 } |
| 53 | 77 |
| 78 // Special cases for listings in German (other three-letter month |
| 79 // abbreviations are the same as in English). Note that we don't need to do |
| 80 // a case-insensitive compare here. Only "ls -l" style listings may use |
| 81 // localized month names, and they will always start capitalized. Also, |
| 82 // converting non-ASCII characters to lowercase would be more complicated. |
| 83 if (text == UTF8ToUTF16("M\xc3\xa4r")) { |
| 84 // The full month name is M-(a-umlaut)-rz (March), which is M-(a-umlaut)r |
| 85 // when abbreviated. |
| 86 *number = 3; |
| 87 return true; |
| 88 } |
| 89 if (text == ASCIIToUTF16("Mai")) { |
| 90 *number = 5; |
| 91 return true; |
| 92 } |
| 93 if (text == ASCIIToUTF16("Okt")) { |
| 94 *number = 10; |
| 95 return true; |
| 96 } |
| 97 if (text == ASCIIToUTF16("Dez")) { |
| 98 *number = 12; |
| 99 return true; |
| 100 } |
| 101 |
| 54 return false; | 102 return false; |
| 55 } | 103 } |
| 56 | 104 |
| 57 bool UnixDateListingToTime(const std::vector<string16>& columns, | 105 bool UnixDateListingToTime(const std::vector<string16>& columns, |
| 58 base::Time* time) { | 106 base::Time* time) { |
| 59 DCHECK_EQ(9U, columns.size()); | 107 DCHECK_LE(9U, columns.size()); |
| 60 | 108 |
| 61 base::Time::Exploded time_exploded = { 0 }; | 109 base::Time::Exploded time_exploded = { 0 }; |
| 62 | 110 |
| 63 if (!ThreeLetterMonthToNumber(columns[5], &time_exploded.month)) | 111 if (!ThreeLetterMonthToNumber(columns[5], &time_exploded.month)) |
| 64 return false; | 112 return false; |
| 65 | 113 |
| 66 if (!StringToInt(columns[6], &time_exploded.day_of_month)) | 114 if (!StringToInt(columns[6], &time_exploded.day_of_month)) |
| 67 return false; | 115 return false; |
| 68 | 116 |
| 69 if (!StringToInt(columns[7], &time_exploded.year)) { | 117 if (!StringToInt(columns[7], &time_exploded.year)) { |
| (...skipping 212 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 282 namespace net { | 330 namespace net { |
| 283 | 331 |
| 284 FtpDirectoryListingParser::~FtpDirectoryListingParser() { | 332 FtpDirectoryListingParser::~FtpDirectoryListingParser() { |
| 285 } | 333 } |
| 286 | 334 |
| 287 FtpLsDirectoryListingParser::FtpLsDirectoryListingParser() | 335 FtpLsDirectoryListingParser::FtpLsDirectoryListingParser() |
| 288 : received_nonempty_line_(false) { | 336 : received_nonempty_line_(false) { |
| 289 } | 337 } |
| 290 | 338 |
| 291 bool FtpLsDirectoryListingParser::ConsumeLine(const string16& line) { | 339 bool FtpLsDirectoryListingParser::ConsumeLine(const string16& line) { |
| 292 // Allow empty lines only at the beginning of the listing. For example VMS | 340 if (StartsWith(line, ASCIIToUTF16("total "), true) || |
| 293 // systems in Unix emulation mode add an empty line before the first listing | 341 StartsWith(line, ASCIIToUTF16("Gesamt "), true)) { |
| 294 // entry. | 342 // Some FTP servers put a "total n" line at the beginning of the listing |
| 295 if (line.empty() && !received_nonempty_line_) | 343 // (n is an integer). Allow such a line, but only once, and only if it's |
| 344 // the first non-empty line. |
| 345 // |
| 346 // Note: "Gesamt" is a German word for "total". The case is important here: |
| 347 // for "ls -l" style listings, "total" will be lowercase, and Gesamt will be |
| 348 // capitalized. This helps us distinguish that from a VMS-style listing, |
| 349 // which would use "Total" (note the uppercase first letter). |
| 350 |
| 351 if (received_nonempty_line_) |
| 352 return false; |
| 353 |
| 354 received_nonempty_line_ = true; |
| 296 return true; | 355 return true; |
| 356 } |
| 357 if (line.empty() && !received_nonempty_line_) { |
| 358 // Allow empty lines only at the beginning of the listing. For example VMS |
| 359 // systems in Unix emulation mode add an empty line before the first listing |
| 360 // entry. |
| 361 return true; |
| 362 } |
| 297 received_nonempty_line_ = true; | 363 received_nonempty_line_ = true; |
| 298 | 364 |
| 299 std::vector<string16> columns; | 365 std::vector<string16> columns; |
| 300 SplitString(CollapseWhitespace(line, false), ' ', &columns); | 366 SplitString(CollapseWhitespace(line, false), ' ', &columns); |
| 301 if (columns.size() == 11) { | |
| 302 // Check if it is a symlink. | |
| 303 if (!EqualsASCII(columns[9], "->")) | |
| 304 return false; | |
| 305 | 367 |
| 306 // Drop the symlink target from columns, we don't use it. | 368 // We may receive file names containing spaces, which can make the number of |
| 307 columns.resize(9); | 369 // columns arbitrarily large. We will handle that later. For now just make |
| 308 } | 370 // sure we have all the columns that should normally be there. |
| 309 | 371 if (columns.size() < 9) |
| 310 if (columns.size() != 9) | |
| 311 return false; | 372 return false; |
| 312 | 373 |
| 313 if (!LooksLikeUnixPermissionsListing(columns[0])) | 374 if (!LooksLikeUnixPermissionsListing(columns[0])) |
| 314 return false; | 375 return false; |
| 315 | 376 |
| 316 FtpDirectoryListingEntry entry; | 377 FtpDirectoryListingEntry entry; |
| 317 if (columns[0][0] == 'l') { | 378 if (columns[0][0] == 'l') { |
| 318 entry.type = FtpDirectoryListingEntry::SYMLINK; | 379 entry.type = FtpDirectoryListingEntry::SYMLINK; |
| 319 } else if (columns[0][0] == 'd') { | 380 } else if (columns[0][0] == 'd') { |
| 320 entry.type = FtpDirectoryListingEntry::DIRECTORY; | 381 entry.type = FtpDirectoryListingEntry::DIRECTORY; |
| 321 } else { | 382 } else { |
| 322 entry.type = FtpDirectoryListingEntry::FILE; | 383 entry.type = FtpDirectoryListingEntry::FILE; |
| 323 } | 384 } |
| 324 | 385 |
| 325 if (!IsStringNonNegativeInteger(columns[1])) | 386 if (!IsStringNonNegativeInteger(columns[1])) |
| 326 return false; | 387 return false; |
| 327 | 388 |
| 328 if (!StringToInt64(columns[4], &entry.size)) | 389 if (!StringToInt64(columns[4], &entry.size)) |
| 329 return false; | 390 return false; |
| 330 if (entry.size < 0) | 391 if (entry.size < 0) |
| 331 return false; | 392 return false; |
| 332 if (entry.type != FtpDirectoryListingEntry::FILE) | 393 if (entry.type != FtpDirectoryListingEntry::FILE) |
| 333 entry.size = -1; | 394 entry.size = -1; |
| 334 | 395 |
| 335 if (!UnixDateListingToTime(columns, &entry.last_modified)) | 396 if (!UnixDateListingToTime(columns, &entry.last_modified)) |
| 336 return false; | 397 return false; |
| 337 | 398 |
| 338 entry.name = columns[8]; | 399 entry.name = GetStringPartAfterColumns(line, 8); |
| 400 if (entry.type == FtpDirectoryListingEntry::SYMLINK) { |
| 401 string16::size_type pos = entry.name.rfind(ASCIIToUTF16(" -> ")); |
| 402 if (pos == string16::npos) |
| 403 return false; |
| 404 entry.name = entry.name.substr(0, pos); |
| 405 } |
| 339 | 406 |
| 340 entries_.push(entry); | 407 entries_.push(entry); |
| 341 return true; | 408 return true; |
| 342 } | 409 } |
| 343 | 410 |
| 344 bool FtpLsDirectoryListingParser::OnEndOfInput() { | 411 bool FtpLsDirectoryListingParser::OnEndOfInput() { |
| 345 return true; | 412 return true; |
| 346 } | 413 } |
| 347 | 414 |
| 348 bool FtpLsDirectoryListingParser::EntryAvailable() const { | 415 bool FtpLsDirectoryListingParser::EntryAvailable() const { |
| (...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 525 if (entry.type != FtpDirectoryListingEntry::FILE) | 592 if (entry.type != FtpDirectoryListingEntry::FILE) |
| 526 entry.size = -1; | 593 entry.size = -1; |
| 527 if (!VmsDateListingToTime(columns, &entry.last_modified)) | 594 if (!VmsDateListingToTime(columns, &entry.last_modified)) |
| 528 return false; | 595 return false; |
| 529 | 596 |
| 530 entries_.push(entry); | 597 entries_.push(entry); |
| 531 return true; | 598 return true; |
| 532 } | 599 } |
| 533 | 600 |
| 534 } // namespace net | 601 } // namespace net |
| OLD | NEW |