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 (base::EqualsASCII(filename_parts
[1], "DIR")) {
47 *parsed_filename
= base::StringToLowerASCII(filename_parts
[0]);
48 *type
= FtpDirectoryListingEntry::DIRECTORY
;
50 *parsed_filename
= base::StringToLowerASCII(listing_parts
[0]);
51 *type
= FtpDirectoryListingEntry::FILE;
56 bool ParseVmsFilesize(const base::string16
& input
, int64
* size
) {
57 if (base::ContainsOnlyChars(input
, base::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* const 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(base::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 (base::StartsWith(lines
[i
], base::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(base::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 base::CollapseWhitespace(
252 lines
[i
- 1] + base::ASCIIToUTF16(" ") + lines
[i
], false),
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.