• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "net/ftp/ftp_directory_listing_parser_vms.h"
6 
7 #include <vector>
8 
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"
16 
17 namespace net {
18 
19 namespace {
20 
21 // Converts the filename component in listing to the filename we can display.
22 // Returns true on success.
ParseVmsFilename(const base::string16 & raw_filename,base::string16 * parsed_filename,FtpDirectoryListingEntry::Type * type)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)
31     return false;
32   int version_number;
33   if (!base::StringToInt(listing_parts[1], &version_number))
34     return false;
35   if (version_number < 0)
36     return false;
37 
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)
45     return false;
46   if (EqualsASCII(filename_parts[1], "DIR")) {
47     *parsed_filename = base::StringToLowerASCII(filename_parts[0]);
48     *type = FtpDirectoryListingEntry::DIRECTORY;
49   } else {
50     *parsed_filename = base::StringToLowerASCII(listing_parts[0]);
51     *type = FtpDirectoryListingEntry::FILE;
52   }
53   return true;
54 }
55 
ParseVmsFilesize(const base::string16 & input,int64 * size)56 bool ParseVmsFilesize(const base::string16& input, int64* size) {
57   if (base::ContainsOnlyChars(input, base::ASCIIToUTF16("*"))) {
58     // Response consisting of asterisks means unknown size.
59     *size = -1;
60     return true;
61   }
62 
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;
67 
68   if (base::StringToInt64(input, size)) {
69     if (*size < 0)
70       return false;
71     *size *= kBlockSize;
72     return true;
73   }
74 
75   std::vector<base::string16> parts;
76   base::SplitString(input, '/', &parts);
77   if (parts.size() != 2)
78     return false;
79 
80   int64 blocks_used, blocks_allocated;
81   if (!base::StringToInt64(parts[0], &blocks_used))
82     return false;
83   if (!base::StringToInt64(parts[1], &blocks_allocated))
84     return false;
85   if (blocks_used > blocks_allocated)
86     return false;
87   if (blocks_used < 0 || blocks_allocated < 0)
88     return false;
89 
90   *size = blocks_used * kBlockSize;
91   return true;
92 }
93 
LooksLikeVmsFileProtectionListingPart(const base::string16 & input)94 bool LooksLikeVmsFileProtectionListingPart(const base::string16& input) {
95   if (input.length() > 4)
96     return false;
97 
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);
106   }
107   return match.empty();
108 }
109 
LooksLikeVmsFileProtectionListing(const base::string16 & input)110 bool LooksLikeVmsFileProtectionListing(const base::string16& input) {
111   if (input.length() < 2)
112     return false;
113   if (input[0] != '(' || input[input.length() - 1] != ')')
114     return false;
115 
116   // We expect four parts of the file protection listing: for System, Owner,
117   // Group, and World.
118   std::vector<base::string16> parts;
119   base::SplitString(input.substr(1, input.length() - 2), ',', &parts);
120   if (parts.size() != 4)
121     return false;
122 
123   return LooksLikeVmsFileProtectionListingPart(parts[0]) &&
124       LooksLikeVmsFileProtectionListingPart(parts[1]) &&
125       LooksLikeVmsFileProtectionListingPart(parts[2]) &&
126       LooksLikeVmsFileProtectionListingPart(parts[3]);
127 }
128 
LooksLikeVmsUserIdentificationCode(const base::string16 & input)129 bool LooksLikeVmsUserIdentificationCode(const base::string16& input) {
130   if (input.length() < 2)
131     return false;
132   return input[0] == '[' && input[input.length() - 1] == ']';
133 }
134 
LooksLikeVMSError(const base::string16 & text)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.
139     "%SYSTEM-F-NOPRIV",
140     "privilege",
141   };
142 
143   for (size_t i = 0; i < arraysize(kPermissionDeniedMessages); i++) {
144     if (text.find(base::ASCIIToUTF16(kPermissionDeniedMessages[i])) !=
145         base::string16::npos)
146       return true;
147   }
148 
149   return false;
150 }
151 
VmsDateListingToTime(const std::vector<base::string16> & columns,base::Time * time)152 bool VmsDateListingToTime(const std::vector<base::string16>& columns,
153                           base::Time* time) {
154   DCHECK_EQ(4U, columns.size());
155 
156   base::Time::Exploded time_exploded = { 0 };
157 
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)
162     return false;
163   if (!base::StringToInt(date_parts[0], &time_exploded.day_of_month))
164     return false;
165   if (!FtpUtil::AbbreviatedMonthToNumber(date_parts[1],
166                                          &time_exploded.month))
167     return false;
168   if (!base::StringToInt(date_parts[2], &time_exploded.year))
169     return false;
170 
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)
179     return false;
180   std::vector<base::string16> time_parts;
181   base::SplitString(time_column, ':', &time_parts);
182   if (time_parts.size() != 2)
183     return false;
184   if (!base::StringToInt(time_parts[0], &time_exploded.hour))
185     return false;
186   if (!base::StringToInt(time_parts[1], &time_exploded.minute))
187     return false;
188 
189   // We don't know the time zone of the server, so just use local time.
190   *time = base::Time::FromLocalExploded(time_exploded);
191   return true;
192 }
193 
194 }  // namespace
195 
ParseFtpDirectoryListingVms(const std::vector<base::string16> & lines,std::vector<FtpDirectoryListingEntry> * entries)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
201   // seing the header.
202   bool seen_header = false;
203 
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;
208 
209   for (size_t i = 0; i < lines.size(); i++) {
210     if (lines[i].empty())
211       continue;
212 
213     if (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())
217           return false;
218 
219       return true;
220     }
221 
222     if (!seen_header) {
223       seen_header = true;
224       continue;
225     }
226 
227     if (LooksLikeVMSError(lines[i])) {
228       seen_error = true;
229       continue;
230     }
231 
232     std::vector<base::string16> columns;
233     base::SplitString(base::CollapseWhitespace(lines[i], false), ' ', &columns);
234 
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)
238         return false;
239 
240       // Skip the next line.
241       i++;
242 
243       // This refers to the continuation line.
244       if (LooksLikeVMSError(lines[i])) {
245         seen_error = true;
246         continue;
247       }
248 
249       // Join the current and next line and split them into columns.
250       base::SplitString(
251           base::CollapseWhitespace(
252               lines[i - 1] + base::ASCIIToUTF16(" ") + lines[i], false),
253           ' ',
254           &columns);
255     }
256 
257     FtpDirectoryListingEntry entry;
258     if (!ParseVmsFilename(columns[0], &entry.name, &entry.type))
259       return false;
260 
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]))
265         return false;
266       if (!LooksLikeVmsUserIdentificationCode(columns[4]))
267         return false;
268 
269       // Drop the unneeded data, so that the following code can always expect
270       // just four columns.
271       columns.resize(4);
272     }
273 
274     if (columns.size() != 4)
275       return false;
276 
277     if (!ParseVmsFilesize(columns[1], &entry.size))
278       return false;
279     if (entry.type != FtpDirectoryListingEntry::FILE)
280       entry.size = -1;
281     if (!VmsDateListingToTime(columns, &entry.last_modified))
282       return false;
283 
284     entries->push_back(entry);
285   }
286 
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.
290   return seen_error;
291 }
292 
293 }  // namespace net
294