OLD | NEW |
1 // Copyright (c) 2011 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_vms.h" | 5 #include "net/ftp/ftp_directory_listing_parser_vms.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 |
| 17 namespace net { |
| 18 |
15 namespace { | 19 namespace { |
16 | 20 |
17 // Converts the filename component in listing to the filename we can display. | 21 // Converts the filename component in listing to the filename we can display. |
18 // Returns true on success. | 22 // Returns true on success. |
19 bool ParseVmsFilename(const string16& raw_filename, string16* parsed_filename, | 23 bool ParseVmsFilename(const string16& raw_filename, string16* parsed_filename, |
20 bool* is_directory) { | 24 FtpDirectoryListingEntry::Type* type) { |
21 // On VMS, the files and directories are versioned. The version number is | 25 // On VMS, the files and directories are versioned. The version number is |
22 // separated from the file name by a semicolon. Example: ANNOUNCE.TXT;2. | 26 // separated from the file name by a semicolon. Example: ANNOUNCE.TXT;2. |
23 std::vector<string16> listing_parts; | 27 std::vector<string16> listing_parts; |
24 base::SplitString(raw_filename, ';', &listing_parts); | 28 base::SplitString(raw_filename, ';', &listing_parts); |
25 if (listing_parts.size() != 2) | 29 if (listing_parts.size() != 2) |
26 return false; | 30 return false; |
27 int version_number; | 31 int version_number; |
28 if (!base::StringToInt(listing_parts[1], &version_number)) | 32 if (!base::StringToInt(listing_parts[1], &version_number)) |
29 return false; | 33 return false; |
30 if (version_number < 0) | 34 if (version_number < 0) |
31 return false; | 35 return false; |
32 | 36 |
33 // Even directories have extensions in the listings. Don't display extensions | 37 // Even directories have extensions in the listings. Don't display extensions |
34 // for directories; it's awkward for non-VMS users. Also, VMS is | 38 // for directories; it's awkward for non-VMS users. Also, VMS is |
35 // case-insensitive, but generally uses uppercase characters. This may look | 39 // case-insensitive, but generally uses uppercase characters. This may look |
36 // awkward, so we convert them to lower case. | 40 // awkward, so we convert them to lower case. |
37 std::vector<string16> filename_parts; | 41 std::vector<string16> filename_parts; |
38 base::SplitString(listing_parts[0], '.', &filename_parts); | 42 base::SplitString(listing_parts[0], '.', &filename_parts); |
39 if (filename_parts.size() != 2) | 43 if (filename_parts.size() != 2) |
40 return false; | 44 return false; |
41 if (EqualsASCII(filename_parts[1], "DIR")) { | 45 if (EqualsASCII(filename_parts[1], "DIR")) { |
42 *parsed_filename = StringToLowerASCII(filename_parts[0]); | 46 *parsed_filename = StringToLowerASCII(filename_parts[0]); |
43 *is_directory = true; | 47 *type = FtpDirectoryListingEntry::DIRECTORY; |
44 } else { | 48 } else { |
45 *parsed_filename = StringToLowerASCII(listing_parts[0]); | 49 *parsed_filename = StringToLowerASCII(listing_parts[0]); |
46 *is_directory = false; | 50 *type = FtpDirectoryListingEntry::FILE; |
47 } | 51 } |
48 return true; | 52 return true; |
49 } | 53 } |
50 | 54 |
51 bool ParseVmsFilesize(const string16& input, int64* size) { | 55 bool ParseVmsFilesize(const string16& input, int64* size) { |
52 // VMS's directory listing gives us file size in blocks. We assume that | 56 // VMS's directory listing gives us file size in blocks. We assume that |
53 // the block size is 512 bytes. It doesn't give accurate file size, but is the | 57 // the block size is 512 bytes. It doesn't give accurate file size, but is the |
54 // best information we have. | 58 // best information we have. |
55 const int kBlockSize = 512; | 59 const int kBlockSize = 512; |
56 | 60 |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
110 LooksLikeVmsFileProtectionListingPart(parts[2]) && | 114 LooksLikeVmsFileProtectionListingPart(parts[2]) && |
111 LooksLikeVmsFileProtectionListingPart(parts[3]); | 115 LooksLikeVmsFileProtectionListingPart(parts[3]); |
112 } | 116 } |
113 | 117 |
114 bool LooksLikeVmsUserIdentificationCode(const string16& input) { | 118 bool LooksLikeVmsUserIdentificationCode(const string16& input) { |
115 if (input.length() < 2) | 119 if (input.length() < 2) |
116 return false; | 120 return false; |
117 return input[0] == '[' && input[input.length() - 1] == ']'; | 121 return input[0] == '[' && input[input.length() - 1] == ']'; |
118 } | 122 } |
119 | 123 |
| 124 bool LooksLikePermissionDeniedError(const string16& text) { |
| 125 static const char* kPermissionDeniedMessages[] = { |
| 126 "%RMS-E-PRV", |
| 127 "privilege", |
| 128 }; |
| 129 |
| 130 for (size_t i = 0; i < arraysize(kPermissionDeniedMessages); i++) { |
| 131 if (text.find(ASCIIToUTF16(kPermissionDeniedMessages[i])) != string16::npos) |
| 132 return true; |
| 133 } |
| 134 |
| 135 return false; |
| 136 } |
| 137 |
120 bool VmsDateListingToTime(const std::vector<string16>& columns, | 138 bool VmsDateListingToTime(const std::vector<string16>& columns, |
121 base::Time* time) { | 139 base::Time* time) { |
122 DCHECK_EQ(3U, columns.size()); | 140 DCHECK_EQ(4U, columns.size()); |
123 | 141 |
124 base::Time::Exploded time_exploded = { 0 }; | 142 base::Time::Exploded time_exploded = { 0 }; |
125 | 143 |
126 // Date should be in format DD-MMM-YYYY. | 144 // Date should be in format DD-MMM-YYYY. |
127 std::vector<string16> date_parts; | 145 std::vector<string16> date_parts; |
128 base::SplitString(columns[1], '-', &date_parts); | 146 base::SplitString(columns[2], '-', &date_parts); |
129 if (date_parts.size() != 3) | 147 if (date_parts.size() != 3) |
130 return false; | 148 return false; |
131 if (!base::StringToInt(date_parts[0], &time_exploded.day_of_month)) | 149 if (!base::StringToInt(date_parts[0], &time_exploded.day_of_month)) |
132 return false; | 150 return false; |
133 if (!net::FtpUtil::AbbreviatedMonthToNumber(date_parts[1], | 151 if (!FtpUtil::AbbreviatedMonthToNumber(date_parts[1], |
134 &time_exploded.month)) | 152 &time_exploded.month)) |
135 return false; | 153 return false; |
136 if (!base::StringToInt(date_parts[2], &time_exploded.year)) | 154 if (!base::StringToInt(date_parts[2], &time_exploded.year)) |
137 return false; | 155 return false; |
138 | 156 |
139 // Time can be in format HH:MM, HH:MM:SS, or HH:MM:SS.mm. Try to recognize the | 157 // Time can be in format HH:MM, HH:MM:SS, or HH:MM:SS.mm. Try to recognize the |
140 // last type first. Do not parse the seconds, they will be ignored anyway. | 158 // last type first. Do not parse the seconds, they will be ignored anyway. |
141 string16 time_column(columns[2]); | 159 string16 time_column(columns[3]); |
142 if (time_column.length() == 11 && time_column[8] == '.') | 160 if (time_column.length() == 11 && time_column[8] == '.') |
143 time_column = time_column.substr(0, 8); | 161 time_column = time_column.substr(0, 8); |
144 if (time_column.length() == 8 && time_column[5] == ':') | 162 if (time_column.length() == 8 && time_column[5] == ':') |
145 time_column = time_column.substr(0, 5); | 163 time_column = time_column.substr(0, 5); |
146 if (time_column.length() != 5) | 164 if (time_column.length() != 5) |
147 return false; | 165 return false; |
148 std::vector<string16> time_parts; | 166 std::vector<string16> time_parts; |
149 base::SplitString(time_column, ':', &time_parts); | 167 base::SplitString(time_column, ':', &time_parts); |
150 if (time_parts.size() != 2) | 168 if (time_parts.size() != 2) |
151 return false; | 169 return false; |
152 if (!base::StringToInt(time_parts[0], &time_exploded.hour)) | 170 if (!base::StringToInt(time_parts[0], &time_exploded.hour)) |
153 return false; | 171 return false; |
154 if (!base::StringToInt(time_parts[1], &time_exploded.minute)) | 172 if (!base::StringToInt(time_parts[1], &time_exploded.minute)) |
155 return false; | 173 return false; |
156 | 174 |
157 // We don't know the time zone of the server, so just use local time. | 175 // We don't know the time zone of the server, so just use local time. |
158 *time = base::Time::FromLocalExploded(time_exploded); | 176 *time = base::Time::FromLocalExploded(time_exploded); |
159 return true; | 177 return true; |
160 } | 178 } |
161 | 179 |
162 } // namespace | 180 } // namespace |
163 | 181 |
164 namespace net { | 182 bool ParseFtpDirectoryListingVms( |
| 183 const std::vector<string16>& lines, |
| 184 std::vector<FtpDirectoryListingEntry>* entries) { |
| 185 // The first non-empty line is the listing header. It often |
| 186 // starts with "Directory ", but not always. We set a flag after |
| 187 // seing the header. |
| 188 bool seen_header = false; |
165 | 189 |
166 FtpDirectoryListingParserVms::FtpDirectoryListingParserVms() | 190 for (size_t i = 0; i < lines.size(); i++) { |
167 : state_(STATE_INITIAL), | 191 if (lines[i].empty()) |
168 last_is_directory_(false) { | 192 continue; |
169 } | |
170 | 193 |
171 FtpDirectoryListingParserVms::~FtpDirectoryListingParserVms() {} | 194 if (StartsWith(lines[i], ASCIIToUTF16("Total of "), true)) { |
| 195 // After the "total" line, all following lines must be empty. |
| 196 for (size_t j = i + 1; j < lines.size(); j++) |
| 197 if (!lines[j].empty()) |
| 198 return false; |
172 | 199 |
173 FtpServerType FtpDirectoryListingParserVms::GetServerType() const { | 200 return true; |
174 return SERVER_VMS; | 201 } |
175 } | |
176 | 202 |
177 bool FtpDirectoryListingParserVms::ConsumeLine(const string16& line) { | 203 if (!seen_header) { |
178 switch (state_) { | 204 seen_header = true; |
179 case STATE_INITIAL: | 205 continue; |
180 DCHECK(last_filename_.empty()); | 206 } |
181 if (line.empty()) | 207 |
182 return true; | 208 if (LooksLikePermissionDeniedError(lines[i])) |
183 if (StartsWith(line, ASCIIToUTF16("Total of "), true)) { | 209 continue; |
184 state_ = STATE_END; | 210 |
185 return true; | 211 std::vector<string16> columns; |
186 } | 212 base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns); |
187 // We assume that the first non-empty line is the listing header. It often | 213 |
188 // starts with "Directory ", but not always. | 214 if (columns.size() == 1) { |
189 state_ = STATE_RECEIVED_HEADER; | 215 // There can be no continuation if the current line is the last one. |
190 return true; | 216 if (i == lines.size() - 1) |
191 case STATE_RECEIVED_HEADER: | |
192 DCHECK(last_filename_.empty()); | |
193 if (line.empty()) | |
194 return true; | |
195 state_ = STATE_ENTRIES; | |
196 return ConsumeEntryLine(line); | |
197 case STATE_ENTRIES: | |
198 if (line.empty()) { | |
199 if (!last_filename_.empty()) | |
200 return false; | |
201 state_ = STATE_RECEIVED_LAST_ENTRY; | |
202 return true; | |
203 } | |
204 return ConsumeEntryLine(line); | |
205 case STATE_RECEIVED_LAST_ENTRY: | |
206 DCHECK(last_filename_.empty()); | |
207 if (line.empty()) | |
208 return true; | |
209 if (!StartsWith(line, ASCIIToUTF16("Total of "), true)) | |
210 return false; | 217 return false; |
211 state_ = STATE_END; | 218 |
212 return true; | 219 // Join the current and next line and split them into columns. |
213 case STATE_END: | 220 columns.clear(); |
214 DCHECK(last_filename_.empty()); | 221 base::SplitString( |
| 222 CollapseWhitespace(lines[i] + ASCIIToUTF16(" ") + lines[i + 1], |
| 223 false), |
| 224 ' ', |
| 225 &columns); |
| 226 i++; |
| 227 } |
| 228 |
| 229 FtpDirectoryListingEntry entry; |
| 230 if (!ParseVmsFilename(columns[0], &entry.name, &entry.type)) |
215 return false; | 231 return false; |
216 default: | 232 |
217 NOTREACHED(); | 233 // There are different variants of a VMS listing. Some display |
| 234 // the protection listing and user identification code, some do not. |
| 235 if (columns.size() == 6) { |
| 236 if (!LooksLikeVmsFileProtectionListing(columns[5])) |
| 237 return false; |
| 238 if (!LooksLikeVmsUserIdentificationCode(columns[4])) |
| 239 return false; |
| 240 |
| 241 // Drop the unneeded data, so that the following code can always expect |
| 242 // just four columns. |
| 243 columns.resize(4); |
| 244 } |
| 245 |
| 246 if (columns.size() != 4) |
218 return false; | 247 return false; |
219 } | |
220 } | |
221 | 248 |
222 bool FtpDirectoryListingParserVms::OnEndOfInput() { | 249 if (!ParseVmsFilesize(columns[1], &entry.size)) |
223 return (state_ == STATE_END); | 250 return false; |
224 } | 251 if (entry.size < 0) |
| 252 return false; |
| 253 if (entry.type != FtpDirectoryListingEntry::FILE) |
| 254 entry.size = -1; |
| 255 if (!VmsDateListingToTime(columns, &entry.last_modified)) |
| 256 return false; |
225 | 257 |
226 bool FtpDirectoryListingParserVms::EntryAvailable() const { | 258 entries->push_back(entry); |
227 return !entries_.empty(); | |
228 } | |
229 | |
230 FtpDirectoryListingEntry FtpDirectoryListingParserVms::PopEntry() { | |
231 FtpDirectoryListingEntry entry = entries_.front(); | |
232 entries_.pop(); | |
233 return entry; | |
234 } | |
235 | |
236 bool FtpDirectoryListingParserVms::ConsumeEntryLine(const string16& line) { | |
237 std::vector<string16> columns; | |
238 base::SplitString(CollapseWhitespace(line, false), ' ', &columns); | |
239 | |
240 if (columns.size() == 1) { | |
241 if (!last_filename_.empty()) | |
242 return false; | |
243 return ParseVmsFilename(columns[0], &last_filename_, &last_is_directory_); | |
244 } | 259 } |
245 | 260 |
246 // Recognize listing entries which generate "access denied" message even when | 261 // The only place where we return true is after receiving the "Total" line, |
247 // trying to list them. We don't display them in the final listing. | 262 // that should be present in every VMS listing. |
248 static const char* kAccessDeniedMessages[] = { | 263 return false; |
249 "%RMS-E-PRV", | |
250 "privilege", | |
251 }; | |
252 for (size_t i = 0; i < arraysize(kAccessDeniedMessages); i++) { | |
253 if (line.find(ASCIIToUTF16(kAccessDeniedMessages[i])) != string16::npos) { | |
254 last_filename_.clear(); | |
255 last_is_directory_ = false; | |
256 return true; | |
257 } | |
258 } | |
259 | |
260 string16 filename; | |
261 bool is_directory = false; | |
262 if (last_filename_.empty()) { | |
263 if (!ParseVmsFilename(columns[0], &filename, &is_directory)) | |
264 return false; | |
265 columns.erase(columns.begin()); | |
266 } else { | |
267 filename = last_filename_; | |
268 is_directory = last_is_directory_; | |
269 last_filename_.clear(); | |
270 last_is_directory_ = false; | |
271 } | |
272 | |
273 if (columns.size() > 5) | |
274 return false; | |
275 | |
276 if (columns.size() == 5) { | |
277 if (!LooksLikeVmsFileProtectionListing(columns[4])) | |
278 return false; | |
279 if (!LooksLikeVmsUserIdentificationCode(columns[3])) | |
280 return false; | |
281 columns.resize(3); | |
282 } | |
283 | |
284 if (columns.size() != 3) | |
285 return false; | |
286 | |
287 FtpDirectoryListingEntry entry; | |
288 entry.name = filename; | |
289 entry.type = is_directory ? FtpDirectoryListingEntry::DIRECTORY | |
290 : FtpDirectoryListingEntry::FILE; | |
291 if (!ParseVmsFilesize(columns[0], &entry.size)) | |
292 return false; | |
293 if (entry.size < 0) | |
294 return false; | |
295 if (entry.type != FtpDirectoryListingEntry::FILE) | |
296 entry.size = -1; | |
297 if (!VmsDateListingToTime(columns, &entry.last_modified)) | |
298 return false; | |
299 | |
300 entries_.push(entry); | |
301 return true; | |
302 } | 264 } |
303 | 265 |
304 } // namespace net | 266 } // namespace net |
OLD | NEW |