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

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

Issue 6670085: FTP: Detect the character encoding only after the entire listing is received. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: test coverage Created 9 years, 9 months 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 | Annotate | Revision Log
OLDNEW
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
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
OLDNEW
« no previous file with comments | « net/ftp/ftp_directory_listing_parser_vms.h ('k') | net/ftp/ftp_directory_listing_parser_vms_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698