1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/ftp/ftp_directory_listing_parser_vms.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/time/time.h"
14 #include "net/ftp/ftp_directory_listing_parser.h"
15 #include "net/ftp/ftp_util.h"
21 // Converts the filename component in listing to the filename we can display.
22 // Returns true on success.
23 bool ParseVmsFilename(const base::string16& raw_filename,
24 base::string16* parsed_filename,
25 FtpDirectoryListingEntry::Type* type) {
26 // On VMS, the files and directories are versioned. The version number is
27 // separated from the file name by a semicolon. Example: ANNOUNCE.TXT;2.
28 std::vector<base::string16> listing_parts;
29 base::SplitString(raw_filename, ';', &listing_parts);
30 if (listing_parts.size() != 2)
33 if (!base::StringToInt(listing_parts[1], &version_number))
35 if (version_number < 0)
38 // Even directories have extensions in the listings. Don't display extensions
39 // for directories; it's awkward for non-VMS users. Also, VMS is
40 // case-insensitive, but generally uses uppercase characters. This may look
41 // awkward, so we convert them to lower case.
42 std::vector<base::string16> filename_parts;
43 base::SplitString(listing_parts[0], '.', &filename_parts);
44 if (filename_parts.size() != 2)
46 if (EqualsASCII(filename_parts[1], "DIR")) {
47 *parsed_filename = StringToLowerASCII(filename_parts[0]);
48 *type = FtpDirectoryListingEntry::DIRECTORY;
50 *parsed_filename = StringToLowerASCII(listing_parts[0]);
51 *type = FtpDirectoryListingEntry::FILE;
56 bool ParseVmsFilesize(const base::string16& input, int64* size) {
57 if (ContainsOnlyChars(input, ASCIIToUTF16("*"))) {
58 // Response consisting of asterisks means unknown size.
63 // VMS's directory listing gives us file size in blocks. We assume that
64 // the block size is 512 bytes. It doesn't give accurate file size, but is the
65 // best information we have.
66 const int kBlockSize = 512;
68 if (base::StringToInt64(input, size)) {
75 std::vector<base::string16> parts;
76 base::SplitString(input, '/', &parts);
77 if (parts.size() != 2)
80 int64 blocks_used, blocks_allocated;
81 if (!base::StringToInt64(parts[0], &blocks_used))
83 if (!base::StringToInt64(parts[1], &blocks_allocated))
85 if (blocks_used > blocks_allocated)
87 if (blocks_used < 0 || blocks_allocated < 0)
90 *size = blocks_used * kBlockSize;
94 bool LooksLikeVmsFileProtectionListingPart(const base::string16& input) {
95 if (input.length() > 4)
98 // On VMS there are four different permission bits: Read, Write, Execute,
99 // and Delete. They appear in that order in the permission listing.
100 std::string pattern("RWED");
101 base::string16 match(input);
102 while (!match.empty() && !pattern.empty()) {
103 if (match[0] == pattern[0])
104 match = match.substr(1);
105 pattern = pattern.substr(1);
107 return match.empty();
110 bool LooksLikeVmsFileProtectionListing(const base::string16& input) {
111 if (input.length() < 2)
113 if (input[0] != '(' || input[input.length() - 1] != ')')
116 // We expect four parts of the file protection listing: for System, Owner,
118 std::vector<base::string16> parts;
119 base::SplitString(input.substr(1, input.length() - 2), ',', &parts);
120 if (parts.size() != 4)
123 return LooksLikeVmsFileProtectionListingPart(parts[0]) &&
124 LooksLikeVmsFileProtectionListingPart(parts[1]) &&
125 LooksLikeVmsFileProtectionListingPart(parts[2]) &&
126 LooksLikeVmsFileProtectionListingPart(parts[3]);
129 bool LooksLikeVmsUserIdentificationCode(const base::string16& input) {
130 if (input.length() < 2)
132 return input[0] == '[' && input[input.length() - 1] == ']';
135 bool LooksLikeVMSError(const base::string16& text) {
136 static const char* kPermissionDeniedMessages[] = {
137 "%RMS-E-FNF", // File not found.
138 "%RMS-E-PRV", // Access denied.
143 for (size_t i = 0; i < arraysize(kPermissionDeniedMessages); i++) {
144 if (text.find(ASCIIToUTF16(kPermissionDeniedMessages[i])) !=
145 base::string16::npos)
152 bool VmsDateListingToTime(const std::vector<base::string16>& columns,
154 DCHECK_EQ(4U, columns.size());
156 base::Time::Exploded time_exploded = { 0 };
158 // Date should be in format DD-MMM-YYYY.
159 std::vector<base::string16> date_parts;
160 base::SplitString(columns[2], '-', &date_parts);
161 if (date_parts.size() != 3)
163 if (!base::StringToInt(date_parts[0], &time_exploded.day_of_month))
165 if (!FtpUtil::AbbreviatedMonthToNumber(date_parts[1],
166 &time_exploded.month))
168 if (!base::StringToInt(date_parts[2], &time_exploded.year))
171 // Time can be in format HH:MM, HH:MM:SS, or HH:MM:SS.mm. Try to recognize the
172 // last type first. Do not parse the seconds, they will be ignored anyway.
173 base::string16 time_column(columns[3]);
174 if (time_column.length() == 11 && time_column[8] == '.')
175 time_column = time_column.substr(0, 8);
176 if (time_column.length() == 8 && time_column[5] == ':')
177 time_column = time_column.substr(0, 5);
178 if (time_column.length() != 5)
180 std::vector<base::string16> time_parts;
181 base::SplitString(time_column, ':', &time_parts);
182 if (time_parts.size() != 2)
184 if (!base::StringToInt(time_parts[0], &time_exploded.hour))
186 if (!base::StringToInt(time_parts[1], &time_exploded.minute))
189 // We don't know the time zone of the server, so just use local time.
190 *time = base::Time::FromLocalExploded(time_exploded);
196 bool ParseFtpDirectoryListingVms(
197 const std::vector<base::string16>& lines,
198 std::vector<FtpDirectoryListingEntry>* entries) {
199 // The first non-empty line is the listing header. It often
200 // starts with "Directory ", but not always. We set a flag after
202 bool seen_header = false;
204 // Sometimes the listing doesn't end with a "Total" line, but
205 // it's only okay when it contains some errors (it's needed
206 // to distinguish it from "ls -l" format).
207 bool seen_error = false;
209 for (size_t i = 0; i < lines.size(); i++) {
210 if (lines[i].empty())
213 if (StartsWith(lines[i], ASCIIToUTF16("Total of "), true)) {
214 // After the "total" line, all following lines must be empty.
215 for (size_t j = i + 1; j < lines.size(); j++)
216 if (!lines[j].empty())
227 if (LooksLikeVMSError(lines[i])) {
232 std::vector<base::string16> columns;
233 base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
235 if (columns.size() == 1) {
236 // There can be no continuation if the current line is the last one.
237 if (i == lines.size() - 1)
240 // Skip the next line.
243 // This refers to the continuation line.
244 if (LooksLikeVMSError(lines[i])) {
249 // Join the current and next line and split them into columns.
251 CollapseWhitespace(lines[i - 1] + ASCIIToUTF16(" ") + lines[i],
257 FtpDirectoryListingEntry entry;
258 if (!ParseVmsFilename(columns[0], &entry.name, &entry.type))
261 // There are different variants of a VMS listing. Some display
262 // the protection listing and user identification code, some do not.
263 if (columns.size() == 6) {
264 if (!LooksLikeVmsFileProtectionListing(columns[5]))
266 if (!LooksLikeVmsUserIdentificationCode(columns[4]))
269 // Drop the unneeded data, so that the following code can always expect
270 // just four columns.
274 if (columns.size() != 4)
277 if (!ParseVmsFilesize(columns[1], &entry.size))
279 if (entry.type != FtpDirectoryListingEntry::FILE)
281 if (!VmsDateListingToTime(columns, &entry.last_modified))
284 entries->push_back(entry);
287 // The only place where we return true is after receiving the "Total" line,
288 // that should be present in every VMS listing. Alternatively, if the listing
289 // contains error messages, it's OK not to have the "Total" line.