Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / net / ftp / ftp_directory_listing_parser_vms.cc
blobac7a464fb8309f29394667ed6dea02ac3ce646f9
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"
7 #include <vector>
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"
17 namespace net {
19 namespace {
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, base::ASCIIToUTF16(";"),
30 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
31 if (listing_parts.size() != 2)
32 return false;
33 int version_number;
34 if (!base::StringToInt(listing_parts[1], &version_number))
35 return false;
36 if (version_number < 0)
37 return false;
39 // Even directories have extensions in the listings. Don't display extensions
40 // for directories; it's awkward for non-VMS users. Also, VMS is
41 // case-insensitive, but generally uses uppercase characters. This may look
42 // awkward, so we convert them to lower case.
43 std::vector<base::string16> filename_parts =
44 base::SplitString(listing_parts[0], base::ASCIIToUTF16("."),
45 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
46 if (filename_parts.size() != 2)
47 return false;
48 if (base::EqualsASCII(filename_parts[1], "DIR")) {
49 *parsed_filename = base::StringToLowerASCII(filename_parts[0]);
50 *type = FtpDirectoryListingEntry::DIRECTORY;
51 } else {
52 *parsed_filename = base::StringToLowerASCII(listing_parts[0]);
53 *type = FtpDirectoryListingEntry::FILE;
55 return true;
58 bool ParseVmsFilesize(const base::string16& input, int64* size) {
59 if (base::ContainsOnlyChars(input, base::ASCIIToUTF16("*"))) {
60 // Response consisting of asterisks means unknown size.
61 *size = -1;
62 return true;
65 // VMS's directory listing gives us file size in blocks. We assume that
66 // the block size is 512 bytes. It doesn't give accurate file size, but is the
67 // best information we have.
68 const int kBlockSize = 512;
70 if (base::StringToInt64(input, size)) {
71 if (*size < 0)
72 return false;
73 *size *= kBlockSize;
74 return true;
77 std::vector<base::StringPiece16> parts =
78 base::SplitStringPiece(input, base::ASCIIToUTF16("/"),
79 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
80 if (parts.size() != 2)
81 return false;
83 int64 blocks_used, blocks_allocated;
84 if (!base::StringToInt64(parts[0], &blocks_used))
85 return false;
86 if (!base::StringToInt64(parts[1], &blocks_allocated))
87 return false;
88 if (blocks_used > blocks_allocated)
89 return false;
90 if (blocks_used < 0 || blocks_allocated < 0)
91 return false;
93 *size = blocks_used * kBlockSize;
94 return true;
97 bool LooksLikeVmsFileProtectionListingPart(const base::string16& input) {
98 if (input.length() > 4)
99 return false;
101 // On VMS there are four different permission bits: Read, Write, Execute,
102 // and Delete. They appear in that order in the permission listing.
103 std::string pattern("RWED");
104 base::string16 match(input);
105 while (!match.empty() && !pattern.empty()) {
106 if (match[0] == pattern[0])
107 match = match.substr(1);
108 pattern = pattern.substr(1);
110 return match.empty();
113 bool LooksLikeVmsFileProtectionListing(const base::string16& input) {
114 if (input.length() < 2)
115 return false;
116 if (input[0] != '(' || input[input.length() - 1] != ')')
117 return false;
119 // We expect four parts of the file protection listing: for System, Owner,
120 // Group, and World.
121 std::vector<base::string16> parts = base::SplitString(
122 base::StringPiece16(input).substr(1, input.length() - 2),
123 base::ASCIIToUTF16(","), base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
124 if (parts.size() != 4)
125 return false;
127 return LooksLikeVmsFileProtectionListingPart(parts[0]) &&
128 LooksLikeVmsFileProtectionListingPart(parts[1]) &&
129 LooksLikeVmsFileProtectionListingPart(parts[2]) &&
130 LooksLikeVmsFileProtectionListingPart(parts[3]);
133 bool LooksLikeVmsUserIdentificationCode(const base::string16& input) {
134 if (input.length() < 2)
135 return false;
136 return input[0] == '[' && input[input.length() - 1] == ']';
139 bool LooksLikeVMSError(const base::string16& text) {
140 static const char* const kPermissionDeniedMessages[] = {
141 "%RMS-E-FNF", // File not found.
142 "%RMS-E-PRV", // Access denied.
143 "%SYSTEM-F-NOPRIV",
144 "privilege",
147 for (size_t i = 0; i < arraysize(kPermissionDeniedMessages); i++) {
148 if (text.find(base::ASCIIToUTF16(kPermissionDeniedMessages[i])) !=
149 base::string16::npos)
150 return true;
153 return false;
156 bool VmsDateListingToTime(const std::vector<base::string16>& columns,
157 base::Time* time) {
158 DCHECK_EQ(4U, columns.size());
160 base::Time::Exploded time_exploded = { 0 };
162 // Date should be in format DD-MMM-YYYY.
163 std::vector<base::StringPiece16> date_parts =
164 base::SplitStringPiece(columns[2], base::ASCIIToUTF16("-"),
165 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
166 if (date_parts.size() != 3)
167 return false;
168 if (!base::StringToInt(date_parts[0], &time_exploded.day_of_month))
169 return false;
170 if (!FtpUtil::AbbreviatedMonthToNumber(date_parts[1].as_string(),
171 &time_exploded.month))
172 return false;
173 if (!base::StringToInt(date_parts[2], &time_exploded.year))
174 return false;
176 // Time can be in format HH:MM, HH:MM:SS, or HH:MM:SS.mm. Try to recognize the
177 // last type first. Do not parse the seconds, they will be ignored anyway.
178 base::string16 time_column(columns[3]);
179 if (time_column.length() == 11 && time_column[8] == '.')
180 time_column = time_column.substr(0, 8);
181 if (time_column.length() == 8 && time_column[5] == ':')
182 time_column = time_column.substr(0, 5);
183 if (time_column.length() != 5)
184 return false;
185 std::vector<base::StringPiece16> time_parts =
186 base::SplitStringPiece(time_column, base::ASCIIToUTF16(":"),
187 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
188 if (time_parts.size() != 2)
189 return false;
190 if (!base::StringToInt(time_parts[0], &time_exploded.hour))
191 return false;
192 if (!base::StringToInt(time_parts[1], &time_exploded.minute))
193 return false;
195 // We don't know the time zone of the server, so just use local time.
196 *time = base::Time::FromLocalExploded(time_exploded);
197 return true;
200 } // namespace
202 bool ParseFtpDirectoryListingVms(
203 const std::vector<base::string16>& lines,
204 std::vector<FtpDirectoryListingEntry>* entries) {
205 // The first non-empty line is the listing header. It often
206 // starts with "Directory ", but not always. We set a flag after
207 // seing the header.
208 bool seen_header = false;
210 // Sometimes the listing doesn't end with a "Total" line, but
211 // it's only okay when it contains some errors (it's needed
212 // to distinguish it from "ls -l" format).
213 bool seen_error = false;
215 base::string16 total_of = base::ASCIIToUTF16("Total of ");
216 base::char16 space[2] = { ' ', 0 };
217 for (size_t i = 0; i < lines.size(); i++) {
218 if (lines[i].empty())
219 continue;
221 if (base::StartsWith(lines[i], total_of, base::CompareCase::SENSITIVE)) {
222 // After the "total" line, all following lines must be empty.
223 for (size_t j = i + 1; j < lines.size(); j++)
224 if (!lines[j].empty())
225 return false;
227 return true;
230 if (!seen_header) {
231 seen_header = true;
232 continue;
235 if (LooksLikeVMSError(lines[i])) {
236 seen_error = true;
237 continue;
240 std::vector<base::string16> columns = base::SplitString(
241 base::CollapseWhitespace(lines[i], false), space,
242 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
244 if (columns.size() == 1) {
245 // There can be no continuation if the current line is the last one.
246 if (i == lines.size() - 1)
247 return false;
249 // Skip the next line.
250 i++;
252 // This refers to the continuation line.
253 if (LooksLikeVMSError(lines[i])) {
254 seen_error = true;
255 continue;
258 // Join the current and next line and split them into columns.
259 columns = base::SplitString(
260 base::CollapseWhitespace(
261 lines[i - 1] + space + lines[i], false),
262 space, base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
265 FtpDirectoryListingEntry entry;
266 if (!ParseVmsFilename(columns[0], &entry.name, &entry.type))
267 return false;
269 // There are different variants of a VMS listing. Some display
270 // the protection listing and user identification code, some do not.
271 if (columns.size() == 6) {
272 if (!LooksLikeVmsFileProtectionListing(columns[5]))
273 return false;
274 if (!LooksLikeVmsUserIdentificationCode(columns[4]))
275 return false;
277 // Drop the unneeded data, so that the following code can always expect
278 // just four columns.
279 columns.resize(4);
282 if (columns.size() != 4)
283 return false;
285 if (!ParseVmsFilesize(columns[1], &entry.size))
286 return false;
287 if (entry.type != FtpDirectoryListingEntry::FILE)
288 entry.size = -1;
289 if (!VmsDateListingToTime(columns, &entry.last_modified))
290 return false;
292 entries->push_back(entry);
295 // The only place where we return true is after receiving the "Total" line,
296 // that should be present in every VMS listing. Alternatively, if the listing
297 // contains error messages, it's OK not to have the "Total" line.
298 return seen_error;
301 } // namespace net