• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
3 // found in the LICENSE file.
4 
5 #include "net/ftp/ftp_util.h"
6 
7 #include <vector>
8 
9 #include "base/i18n/char_iterator.h"
10 #include "base/logging.h"
11 #include "base/string_number_conversions.h"
12 #include "base/string_tokenizer.h"
13 #include "base/string_util.h"
14 #include "base/time.h"
15 #include "base/utf_string_conversions.h"
16 #include "unicode/datefmt.h"
17 #include "unicode/dtfmtsym.h"
18 #include "unicode/uchar.h"
19 
20 // For examples of Unix<->VMS path conversions, see the unit test file. On VMS
21 // a path looks differently depending on whether it's a file or directory.
22 
23 namespace net {
24 
25 // static
UnixFilePathToVMS(const std::string & unix_path)26 std::string FtpUtil::UnixFilePathToVMS(const std::string& unix_path) {
27   if (unix_path.empty())
28     return std::string();
29 
30   StringTokenizer tokenizer(unix_path, "/");
31   std::vector<std::string> tokens;
32   while (tokenizer.GetNext())
33     tokens.push_back(tokenizer.token());
34 
35   if (unix_path[0] == '/') {
36     // It's an absolute path.
37 
38     if (tokens.empty()) {
39       DCHECK_EQ(1U, unix_path.length());
40       return "[]";
41     }
42 
43     if (tokens.size() == 1)
44       return unix_path.substr(1);  // Drop the leading slash.
45 
46     std::string result(tokens[0] + ":[");
47     if (tokens.size() == 2) {
48       // Don't ask why, it just works that way on VMS.
49       result.append("000000");
50     } else {
51       result.append(tokens[1]);
52       for (size_t i = 2; i < tokens.size() - 1; i++)
53         result.append("." + tokens[i]);
54     }
55     result.append("]" + tokens[tokens.size() - 1]);
56     return result;
57   }
58 
59   if (tokens.size() == 1)
60     return unix_path;
61 
62   std::string result("[");
63   for (size_t i = 0; i < tokens.size() - 1; i++)
64     result.append("." + tokens[i]);
65   result.append("]" + tokens[tokens.size() - 1]);
66   return result;
67 }
68 
69 // static
UnixDirectoryPathToVMS(const std::string & unix_path)70 std::string FtpUtil::UnixDirectoryPathToVMS(const std::string& unix_path) {
71   if (unix_path.empty())
72     return std::string();
73 
74   std::string path(unix_path);
75 
76   if (path[path.length() - 1] != '/')
77     path.append("/");
78 
79   // Reuse logic from UnixFilePathToVMS by appending a fake file name to the
80   // real path and removing it after conversion.
81   path.append("x");
82   path = UnixFilePathToVMS(path);
83   return path.substr(0, path.length() - 1);
84 }
85 
86 // static
VMSPathToUnix(const std::string & vms_path)87 std::string FtpUtil::VMSPathToUnix(const std::string& vms_path) {
88   if (vms_path.empty())
89     return ".";
90 
91   if (vms_path == "[]")
92     return "/";
93 
94   std::string result(vms_path);
95   if (vms_path[0] == '[') {
96     // It's a relative path.
97     ReplaceFirstSubstringAfterOffset(&result, 0, "[.", "");
98   } else {
99     // It's an absolute path.
100     result.insert(0, "/");
101     ReplaceSubstringsAfterOffset(&result, 0, ":[000000]", "/");
102     ReplaceSubstringsAfterOffset(&result, 0, ":[", "/");
103   }
104   std::replace(result.begin(), result.end(), '.', '/');
105   std::replace(result.begin(), result.end(), ']', '/');
106 
107   // Make sure the result doesn't end with a slash.
108   if (result.length() && result[result.length() - 1] == '/')
109     result = result.substr(0, result.length() - 1);
110 
111   return result;
112 }
113 
114 // static
AbbreviatedMonthToNumber(const string16 & text,int * number)115 bool FtpUtil::AbbreviatedMonthToNumber(const string16& text, int* number) {
116   icu::UnicodeString unicode_text(text.data(), text.size());
117 
118   int32_t locales_count;
119   const icu::Locale* locales =
120       icu::DateFormat::getAvailableLocales(locales_count);
121 
122   // Some FTP servers localize the date listings. To guess the locale,
123   // we loop over all available ones.
124   for (int32_t locale = 0; locale < locales_count; locale++) {
125     UErrorCode status(U_ZERO_ERROR);
126 
127     icu::DateFormatSymbols format_symbols(locales[locale], status);
128 
129     // If we cannot get format symbols for some locale, it's not a fatal error.
130     // Just try another one.
131     if (U_FAILURE(status))
132       continue;
133 
134     int32_t months_count;
135     const icu::UnicodeString* months =
136         format_symbols.getShortMonths(months_count);
137 
138     // Loop over all abbreviated month names in given locale.
139     // An alternative solution (to parse |text| in given locale) is more
140     // lenient, and may accept more than we want even with setLenient(false).
141     for (int32_t month = 0; month < months_count; month++) {
142       // Compare (case-insensitive), but just first three characters. Sometimes
143       // ICU returns longer strings (for example for Russian locale), and in FTP
144       // listings they are abbreviated to just three characters.
145       // Note: ICU may also return strings shorter than three characters,
146       // and those also should be accepted.
147       if (months[month].caseCompare(0, 3, unicode_text, 0) == 0) {
148         *number = month + 1;
149         return true;
150       }
151     }
152   }
153 
154   return false;
155 }
156 
157 // static
LsDateListingToTime(const string16 & month,const string16 & day,const string16 & rest,const base::Time & current_time,base::Time * result)158 bool FtpUtil::LsDateListingToTime(const string16& month, const string16& day,
159                                   const string16& rest,
160                                   const base::Time& current_time,
161                                   base::Time* result) {
162   base::Time::Exploded time_exploded = { 0 };
163 
164   if (!AbbreviatedMonthToNumber(month, &time_exploded.month))
165     return false;
166 
167   if (!base::StringToInt(day, &time_exploded.day_of_month))
168     return false;
169   if (time_exploded.day_of_month > 31)
170     return false;
171 
172   if (!base::StringToInt(rest, &time_exploded.year)) {
173     // Maybe it's time. Does it look like time (HH:MM)?
174     if (rest.length() == 5 && rest[2] == ':') {
175       if (!base::StringToInt(rest.begin(),
176                              rest.begin() + 2,
177                              &time_exploded.hour))
178         return false;
179 
180       if (!base::StringToInt(rest.begin() + 3,
181                              rest.begin() + 5,
182                              &time_exploded.minute))
183         return false;
184     } else if (rest.length() == 4 && rest[1] == ':') {
185       // Sometimes it's just H:MM.
186       if (!base::StringToInt(rest.begin(),
187                              rest.begin() + 1,
188                              &time_exploded.hour))
189         return false;
190 
191       if (!base::StringToInt(rest.begin() + 2,
192                              rest.begin() + 4,
193                              &time_exploded.minute))
194         return false;
195     } else {
196       return false;
197     }
198 
199     // Guess the year.
200     base::Time::Exploded current_exploded;
201     current_time.LocalExplode(&current_exploded);
202 
203     // If it's not possible for the parsed date to be in the current year,
204     // use the previous year.
205     if (time_exploded.month > current_exploded.month ||
206         (time_exploded.month == current_exploded.month &&
207          time_exploded.day_of_month > current_exploded.day_of_month)) {
208       time_exploded.year = current_exploded.year - 1;
209     } else {
210       time_exploded.year = current_exploded.year;
211     }
212   }
213 
214   // We don't know the time zone of the listing, so just use local time.
215   *result = base::Time::FromLocalExploded(time_exploded);
216   return true;
217 }
218 
219 // static
GetStringPartAfterColumns(const string16 & text,int columns)220 string16 FtpUtil::GetStringPartAfterColumns(const string16& text, int columns) {
221   base::i18n::UTF16CharIterator iter(&text);
222 
223   // TODO(jshin): Is u_isspace the right function to use here?
224   for (int i = 0; i < columns; i++) {
225     // Skip the leading whitespace.
226     while (!iter.end() && u_isspace(iter.get()))
227       iter.Advance();
228 
229     // Skip the actual text of i-th column.
230     while (!iter.end() && !u_isspace(iter.get()))
231       iter.Advance();
232   }
233 
234   string16 result(text.substr(iter.array_pos()));
235   TrimWhitespace(result, TRIM_ALL, &result);
236   return result;
237 }
238 
239 }  // namespace
240