Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(355)

Side by Side Diff: net/ftp/ftp_directory_listing_parsers.cc

Issue 449011: Make new FTP LIST parsing code more robust. (Closed)
Patch Set: fixes Created 11 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
OLDNEW
« no previous file with comments | « net/ftp/ftp_directory_listing_buffer_unittest.cc ('k') | net/ftp/ftp_directory_listing_parsers_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698