Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/ftp/ftp_directory_listing_parser_ls.h" | 5 #include "net/ftp/ftp_directory_listing_parser_ls.h" |
| 6 | 6 |
| 7 #include <vector> | 7 #include <vector> |
| 8 | 8 |
| 9 #include "base/string_number_conversions.h" | 9 #include "base/string_number_conversions.h" |
| 10 #include "base/string_split.h" | 10 #include "base/string_split.h" |
| 11 #include "base/string_util.h" | 11 #include "base/string_util.h" |
| 12 #include "base/time.h" | |
| 12 #include "base/utf_string_conversions.h" | 13 #include "base/utf_string_conversions.h" |
| 14 #include "net/ftp/ftp_directory_listing_parser.h" | |
| 13 #include "net/ftp/ftp_util.h" | 15 #include "net/ftp/ftp_util.h" |
| 14 | 16 |
| 15 namespace { | 17 namespace { |
| 16 | 18 |
| 17 bool LooksLikeUnixPermission(const string16& text) { | 19 bool LooksLikeUnixPermission(const string16& text) { |
| 18 if (text.length() != 3) | 20 if (text.length() != 3) |
| 19 return false; | 21 return false; |
| 20 | 22 |
| 21 // Meaning of the flags: | 23 // Meaning of the flags: |
| 22 // r - file is readable | 24 // r - file is readable |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 93 } | 95 } |
| 94 | 96 |
| 95 // Unrecognized listing style. | 97 // Unrecognized listing style. |
| 96 return false; | 98 return false; |
| 97 } | 99 } |
| 98 | 100 |
| 99 } // namespace | 101 } // namespace |
| 100 | 102 |
| 101 namespace net { | 103 namespace net { |
| 102 | 104 |
| 103 FtpDirectoryListingParserLs::FtpDirectoryListingParserLs( | 105 bool ParseFtpDirectoryListingLs( |
| 104 const base::Time& current_time) | 106 const std::vector<string16>& lines, |
| 105 : received_total_line_(false), | 107 const base::Time& current_time, |
| 106 current_time_(current_time) { | 108 std::vector<FtpDirectoryListingEntry>* entries) { |
| 107 } | 109 // True after we have received a "total n" listing header, where n is an |
| 110 // integer. Only one such header is allowed per listing. | |
| 111 bool received_total_line = false; | |
| 108 | 112 |
| 109 FtpDirectoryListingParserLs::~FtpDirectoryListingParserLs() {} | 113 for (size_t i = 0; i < lines.size(); i++) { |
| 114 if (lines[i].empty()) | |
| 115 continue; | |
| 110 | 116 |
| 111 FtpServerType FtpDirectoryListingParserLs::GetServerType() const { | 117 std::vector<string16> columns; |
| 112 return SERVER_LS; | 118 base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns); |
| 113 } | |
| 114 | 119 |
| 115 bool FtpDirectoryListingParserLs::ConsumeLine(const string16& line) { | 120 // Some FTP servers put a "total n" line at the beginning of the listing |
| 116 if (line.empty()) | 121 // (n is an integer). Allow such a line, but only once, and only if it's |
| 117 return true; | 122 // the first non-empty line. Do not match the word exactly, because it may |
| 123 // be in different languages (at least English and German have been seen | |
| 124 // in the field). | |
| 125 if (columns.size() == 2 && !received_total_line) { | |
| 126 received_total_line = true; | |
| 118 | 127 |
| 119 std::vector<string16> columns; | 128 int total_number; |
| 120 base::SplitString(CollapseWhitespace(line, false), ' ', &columns); | 129 if (!base::StringToInt(columns[1], &total_number)) |
| 130 return false; | |
| 131 if (total_number < 0) | |
| 132 return false; | |
| 121 | 133 |
| 122 // Some FTP servers put a "total n" line at the beginning of the listing | 134 continue; |
| 123 // (n is an integer). Allow such a line, but only once, and only if it's | 135 } |
| 124 // the first non-empty line. Do not match the word exactly, because it may be | |
| 125 // in different languages (at least English and German have been seen in the | |
| 126 // field). | |
| 127 if (columns.size() == 2 && !received_total_line_) { | |
| 128 received_total_line_ = true; | |
| 129 | 136 |
| 130 int total_number; | 137 int column_offset; |
| 131 if (!base::StringToInt(columns[1], &total_number)) | 138 if (!DetectColumnOffset(columns, current_time, &column_offset)) { |
| 139 // If we can't recognize a normal listing line, maybe it's an error? | |
| 140 // In that case, just ignore the error, but still recognize the data | |
| 141 // as valid listing. | |
| 142 if (LooksLikePermissionDeniedError(lines[i])) | |
| 143 continue; | |
| 144 | |
| 132 return false; | 145 return false; |
| 133 if (total_number < 0) | 146 } |
| 147 | |
| 148 // We may receive file names containing spaces, which can make the number of | |
| 149 // columns arbitrarily large. We will handle that later. For now just make | |
| 150 // sure we have all the columns that should normally be there: | |
| 151 // | |
| 152 // 1. permission listing | |
| 153 // 2. number of links (optional) | |
| 154 // 3. owner name | |
| 155 // 4. group name (optional) | |
| 156 // 5. size in bytes | |
| 157 // 6. month | |
| 158 // 7. day of month | |
| 159 // 8. year or time | |
| 160 // | |
| 161 // The number of optional columns is stored in |column_offset| | |
| 162 // and is between 0 and 2 (inclusive). | |
| 163 if (columns.size() < 6U + column_offset) | |
| 134 return false; | 164 return false; |
| 135 | 165 |
| 136 return true; | 166 if (!LooksLikeUnixPermissionsListing(columns[0])) |
| 167 return false; | |
| 168 | |
| 169 FtpDirectoryListingEntry entry; | |
| 170 if (columns[0][0] == 'l') { | |
| 171 entry.type = FtpDirectoryListingEntry::SYMLINK; | |
| 172 } else if (columns[0][0] == 'd') { | |
| 173 entry.type = FtpDirectoryListingEntry::DIRECTORY; | |
| 174 } else { | |
| 175 entry.type = FtpDirectoryListingEntry::FILE; | |
| 176 } | |
| 177 | |
| 178 if (!base::StringToInt64(columns[2 + column_offset], &entry.size)) { | |
|
eroman
2011/03/24 23:09:35
I only glossed over this section quickly. I assume
Paweł Hajdan Jr.
2011/03/26 09:47:50
Yes, "ls -l" is just a move. VMS had some non-triv
| |
| 179 // Some FTP servers do not separate owning group name from file size, | |
| 180 // like "group1234". We still want to display the file name for that | |
| 181 // entry, but can't really get the size (What if the group is named | |
| 182 // "group1", and the size is in fact 234? We can't distinguish between | |
| 183 // that and "group" with size 1234). Use a dummy value for the size. | |
| 184 // TODO(phajdan.jr): Use a value that means "unknown" instead of 0 bytes. | |
| 185 entry.size = 0; | |
| 186 } | |
| 187 if (entry.size < 0) | |
| 188 return false; | |
| 189 if (entry.type != FtpDirectoryListingEntry::FILE) | |
| 190 entry.size = -1; | |
| 191 | |
| 192 if (!FtpUtil::LsDateListingToTime(columns[3 + column_offset], | |
| 193 columns[4 + column_offset], | |
| 194 columns[5 + column_offset], | |
| 195 current_time, | |
| 196 &entry.last_modified)) { | |
| 197 return false; | |
| 198 } | |
| 199 | |
| 200 entry.name = FtpUtil::GetStringPartAfterColumns(lines[i], | |
| 201 6 + column_offset); | |
| 202 | |
| 203 if (entry.name.empty()) { | |
| 204 // Some FTP servers send listing entries with empty names. | |
| 205 // It's not obvious how to display such an entry, so ignore them. | |
| 206 // We don't want to make the parsing fail at this point though. | |
| 207 // Other entries can still be useful. | |
| 208 continue; | |
| 209 } | |
| 210 | |
| 211 if (entry.type == FtpDirectoryListingEntry::SYMLINK) { | |
| 212 string16::size_type pos = entry.name.rfind(ASCIIToUTF16(" -> ")); | |
| 213 | |
| 214 // We don't require the " -> " to be present. Some FTP servers don't send | |
| 215 // the symlink target, possibly for security reasons. | |
| 216 if (pos != string16::npos) | |
| 217 entry.name = entry.name.substr(0, pos); | |
| 218 } | |
| 219 | |
| 220 entries->push_back(entry); | |
| 137 } | 221 } |
| 138 | 222 |
| 139 int column_offset; | |
| 140 if (!DetectColumnOffset(columns, current_time_, &column_offset)) { | |
| 141 // If we can't recognize a normal listing line, maybe it's an error? | |
| 142 // In that case, just ignore the error, but still recognize the data | |
| 143 // as valid listing. | |
| 144 return LooksLikePermissionDeniedError(line); | |
| 145 } | |
| 146 | |
| 147 // We may receive file names containing spaces, which can make the number of | |
| 148 // columns arbitrarily large. We will handle that later. For now just make | |
| 149 // sure we have all the columns that should normally be there: | |
| 150 // | |
| 151 // 1. permission listing | |
| 152 // 2. number of links (optional) | |
| 153 // 3. owner name | |
| 154 // 4. group name (optional) | |
| 155 // 5. size in bytes | |
| 156 // 6. month | |
| 157 // 7. day of month | |
| 158 // 8. year or time | |
| 159 // | |
| 160 // The number of optional columns is stored in |column_offset| | |
| 161 // and is between 0 and 2 (inclusive). | |
| 162 if (columns.size() < 6U + column_offset) | |
| 163 return false; | |
| 164 | |
| 165 if (!LooksLikeUnixPermissionsListing(columns[0])) | |
| 166 return false; | |
| 167 | |
| 168 FtpDirectoryListingEntry entry; | |
| 169 if (columns[0][0] == 'l') { | |
| 170 entry.type = FtpDirectoryListingEntry::SYMLINK; | |
| 171 } else if (columns[0][0] == 'd') { | |
| 172 entry.type = FtpDirectoryListingEntry::DIRECTORY; | |
| 173 } else { | |
| 174 entry.type = FtpDirectoryListingEntry::FILE; | |
| 175 } | |
| 176 | |
| 177 if (!base::StringToInt64(columns[2 + column_offset], &entry.size)) { | |
| 178 // Some FTP servers do not separate owning group name from file size, | |
| 179 // like "group1234". We still want to display the file name for that entry, | |
| 180 // but can't really get the size (What if the group is named "group1", | |
| 181 // and the size is in fact 234? We can't distinguish between that | |
| 182 // and "group" with size 1234). Use a dummy value for the size. | |
| 183 // TODO(phajdan.jr): Use a value that means "unknown" instead of 0 bytes. | |
| 184 entry.size = 0; | |
| 185 } | |
| 186 if (entry.size < 0) | |
| 187 return false; | |
| 188 if (entry.type != FtpDirectoryListingEntry::FILE) | |
| 189 entry.size = -1; | |
| 190 | |
| 191 if (!FtpUtil::LsDateListingToTime(columns[3 + column_offset], | |
| 192 columns[4 + column_offset], | |
| 193 columns[5 + column_offset], | |
| 194 current_time_, | |
| 195 &entry.last_modified)) { | |
| 196 return false; | |
| 197 } | |
| 198 | |
| 199 entry.name = FtpUtil::GetStringPartAfterColumns(line, 6 + column_offset); | |
| 200 | |
| 201 if (entry.name.empty()) { | |
| 202 // Some FTP servers send listing entries with empty names. It's not obvious | |
| 203 // how to display such an entry, so we ignore them. We don't want to make | |
| 204 // the parsing fail at this point though. Other entries can still be useful. | |
| 205 return true; | |
| 206 } | |
| 207 | |
| 208 if (entry.type == FtpDirectoryListingEntry::SYMLINK) { | |
| 209 string16::size_type pos = entry.name.rfind(ASCIIToUTF16(" -> ")); | |
| 210 | |
| 211 // We don't require the " -> " to be present. Some FTP servers don't send | |
| 212 // the symlink target, possibly for security reasons. | |
| 213 if (pos != string16::npos) | |
| 214 entry.name = entry.name.substr(0, pos); | |
| 215 } | |
| 216 | |
| 217 entries_.push(entry); | |
| 218 return true; | 223 return true; |
| 219 } | 224 } |
| 220 | 225 |
| 221 bool FtpDirectoryListingParserLs::OnEndOfInput() { | |
| 222 return true; | |
| 223 } | |
| 224 | |
| 225 bool FtpDirectoryListingParserLs::EntryAvailable() const { | |
| 226 return !entries_.empty(); | |
| 227 } | |
| 228 | |
| 229 FtpDirectoryListingEntry FtpDirectoryListingParserLs::PopEntry() { | |
| 230 FtpDirectoryListingEntry entry = entries_.front(); | |
| 231 entries_.pop(); | |
| 232 return entry; | |
| 233 } | |
| 234 | |
| 235 } // namespace net | 226 } // namespace net |
| OLD | NEW |