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)) { |
| 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 |