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_ls.h" 6 7 #include <vector> 8 9 #include "base/string_util.h" 10 #include "net/ftp/ftp_util.h" 11 12 namespace { 13 14 bool LooksLikeUnixPermission(const string16& text) { 15 if (text.length() != 3) 16 return false; 17 18 // Meaning of the flags: 19 // r - file is readable 20 // w - file is writable 21 // x - file is executable 22 // s or S - setuid/setgid bit set 23 // t or T - "sticky" bit set 24 return ((text[0] == 'r' || text[0] == '-') && 25 (text[1] == 'w' || text[1] == '-') && 26 (text[2] == 'x' || text[2] == 's' || text[2] == 'S' || 27 text[2] == 't' || text[2] == 'T' || text[2] == '-')); 28 } 29 30 bool LooksLikeUnixPermissionsListing(const string16& text) { 31 if (text.length() < 10) 32 return false; 33 34 if (text[0] != 'b' && text[0] != 'c' && text[0] != 'd' && 35 text[0] != 'l' && text[0] != 'p' && text[0] != 's' && 36 text[0] != '-') 37 return false; 38 39 return (LooksLikeUnixPermission(text.substr(1, 3)) && 40 LooksLikeUnixPermission(text.substr(4, 3)) && 41 LooksLikeUnixPermission(text.substr(7, 3)) && 42 (text.substr(10).empty() || text.substr(10) == ASCIIToUTF16("+"))); 43 } 44 45 bool DetectColumnOffset(const std::vector<string16>& columns, int* offset) { 46 base::Time time; 47 48 if (columns.size() >= 8 && 49 net::FtpUtil::LsDateListingToTime(columns[5], columns[6], columns[7], 50 &time)) { 51 // Standard listing, exactly like ls -l. 52 *offset = 2; 53 return true; 54 } 55 56 if (columns.size() >= 7 && 57 net::FtpUtil::LsDateListingToTime(columns[4], columns[5], columns[6], 58 &time)) { 59 // wu-ftpd listing, no "number of links" column. 60 *offset = 1; 61 return true; 62 } 63 64 if (columns.size() >= 6 && 65 net::FtpUtil::LsDateListingToTime(columns[3], columns[4], columns[5], 66 &time)) { 67 // Xplain FTP Server listing for folders, like this: 68 // drwxr-xr-x folder 0 Jul 17 2006 online 69 *offset = 0; 70 return true; 71 } 72 73 // Unrecognized listing style. 74 return false; 75 } 76 77 } // namespace 78 79 namespace net { 80 81 FtpDirectoryListingParserLs::FtpDirectoryListingParserLs() 82 : received_nonempty_line_(false), 83 received_total_line_(false) { 84 } 85 86 bool FtpDirectoryListingParserLs::ConsumeLine(const string16& line) { 87 if (line.empty() && !received_nonempty_line_) { 88 // Allow empty lines only at the beginning of the listing. For example VMS 89 // systems in Unix emulation mode add an empty line before the first listing 90 // entry. 91 return true; 92 } 93 received_nonempty_line_ = true; 94 95 std::vector<string16> columns; 96 SplitString(CollapseWhitespace(line, false), ' ', &columns); 97 98 // Some FTP servers put a "total n" line at the beginning of the listing 99 // (n is an integer). Allow such a line, but only once, and only if it's 100 // the first non-empty line. Do not match the word exactly, because it may be 101 // in different languages (at least English and German have been seen in the 102 // field). 103 if (columns.size() == 2 && !received_total_line_) { 104 received_total_line_ = true; 105 106 int total_number; 107 if (!StringToInt(columns[1], &total_number)) 108 return false; 109 if (total_number < 0) 110 return false; 111 112 return true; 113 } 114 115 int column_offset; 116 if (!DetectColumnOffset(columns, &column_offset)) 117 return false; 118 119 // We may receive file names containing spaces, which can make the number of 120 // columns arbitrarily large. We will handle that later. For now just make 121 // sure we have all the columns that should normally be there. 122 if (columns.size() < 7U + column_offset) 123 return false; 124 125 if (!LooksLikeUnixPermissionsListing(columns[0])) 126 return false; 127 128 FtpDirectoryListingEntry entry; 129 if (columns[0][0] == 'l') { 130 entry.type = FtpDirectoryListingEntry::SYMLINK; 131 } else if (columns[0][0] == 'd') { 132 entry.type = FtpDirectoryListingEntry::DIRECTORY; 133 } else { 134 entry.type = FtpDirectoryListingEntry::FILE; 135 } 136 137 if (!StringToInt64(columns[2 + column_offset], &entry.size)) 138 return false; 139 if (entry.size < 0) 140 return false; 141 if (entry.type != FtpDirectoryListingEntry::FILE) 142 entry.size = -1; 143 144 if (!FtpUtil::LsDateListingToTime(columns[3 + column_offset], 145 columns[4 + column_offset], 146 columns[5 + column_offset], 147 &entry.last_modified)) { 148 return false; 149 } 150 151 entry.name = FtpUtil::GetStringPartAfterColumns(line, 6 + column_offset); 152 if (entry.type == FtpDirectoryListingEntry::SYMLINK) { 153 string16::size_type pos = entry.name.rfind(ASCIIToUTF16(" -> ")); 154 if (pos == string16::npos) 155 return false; 156 entry.name = entry.name.substr(0, pos); 157 } 158 159 entries_.push(entry); 160 return true; 161 } 162 163 bool FtpDirectoryListingParserLs::OnEndOfInput() { 164 return true; 165 } 166 167 bool FtpDirectoryListingParserLs::EntryAvailable() const { 168 return !entries_.empty(); 169 } 170 171 FtpDirectoryListingEntry FtpDirectoryListingParserLs::PopEntry() { 172 FtpDirectoryListingEntry entry = entries_.front(); 173 entries_.pop(); 174 return entry; 175 } 176 177 } // namespace net 178