By moving the call to Load() up in SearchProvider::Start(), we are giving a chance...
[chromium-blink-merge.git] / net / ftp / ftp_util.cc
blobefa67f53b5de315042b0421f8b067879a05acd6b
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.
5 #include "net/ftp/ftp_util.h"
7 #include <map>
8 #include <vector>
10 #include "base/i18n/case_conversion.h"
11 #include "base/i18n/char_iterator.h"
12 #include "base/logging.h"
13 #include "base/memory/singleton.h"
14 #include "base/string_number_conversions.h"
15 #include "base/string_piece.h"
16 #include "base/string_split.h"
17 #include "base/string_tokenizer.h"
18 #include "base/string_util.h"
19 #include "base/time.h"
20 #include "base/utf_string_conversions.h"
21 #include "third_party/icu/public/common/unicode/uchar.h"
22 #include "third_party/icu/public/i18n/unicode/datefmt.h"
23 #include "third_party/icu/public/i18n/unicode/dtfmtsym.h"
25 using base::StringPiece16;
27 // For examples of Unix<->VMS path conversions, see the unit test file. On VMS
28 // a path looks differently depending on whether it's a file or directory.
30 namespace net {
32 // static
33 std::string FtpUtil::UnixFilePathToVMS(const std::string& unix_path) {
34 if (unix_path.empty())
35 return std::string();
37 StringTokenizer tokenizer(unix_path, "/");
38 std::vector<std::string> tokens;
39 while (tokenizer.GetNext())
40 tokens.push_back(tokenizer.token());
42 if (unix_path[0] == '/') {
43 // It's an absolute path.
45 if (tokens.empty()) {
46 DCHECK_EQ(1U, unix_path.length());
47 return "[]";
50 if (tokens.size() == 1)
51 return unix_path.substr(1); // Drop the leading slash.
53 std::string result(tokens[0] + ":[");
54 if (tokens.size() == 2) {
55 // Don't ask why, it just works that way on VMS.
56 result.append("000000");
57 } else {
58 result.append(tokens[1]);
59 for (size_t i = 2; i < tokens.size() - 1; i++)
60 result.append("." + tokens[i]);
62 result.append("]" + tokens[tokens.size() - 1]);
63 return result;
66 if (tokens.size() == 1)
67 return unix_path;
69 std::string result("[");
70 for (size_t i = 0; i < tokens.size() - 1; i++)
71 result.append("." + tokens[i]);
72 result.append("]" + tokens[tokens.size() - 1]);
73 return result;
76 // static
77 std::string FtpUtil::UnixDirectoryPathToVMS(const std::string& unix_path) {
78 if (unix_path.empty())
79 return std::string();
81 std::string path(unix_path);
83 if (path[path.length() - 1] != '/')
84 path.append("/");
86 // Reuse logic from UnixFilePathToVMS by appending a fake file name to the
87 // real path and removing it after conversion.
88 path.append("x");
89 path = UnixFilePathToVMS(path);
90 return path.substr(0, path.length() - 1);
93 // static
94 std::string FtpUtil::VMSPathToUnix(const std::string& vms_path) {
95 if (vms_path.empty())
96 return ".";
98 if (vms_path[0] == '/') {
99 // This is not really a VMS path. Most likely the server is emulating UNIX.
100 // Return path as-is.
101 return vms_path;
104 if (vms_path == "[]")
105 return "/";
107 std::string result(vms_path);
108 if (vms_path[0] == '[') {
109 // It's a relative path.
110 ReplaceFirstSubstringAfterOffset(&result, 0, "[.", "");
111 } else {
112 // It's an absolute path.
113 result.insert(0, "/");
114 ReplaceSubstringsAfterOffset(&result, 0, ":[000000]", "/");
115 ReplaceSubstringsAfterOffset(&result, 0, ":[", "/");
117 std::replace(result.begin(), result.end(), '.', '/');
118 std::replace(result.begin(), result.end(), ']', '/');
120 // Make sure the result doesn't end with a slash.
121 if (result.length() && result[result.length() - 1] == '/')
122 result = result.substr(0, result.length() - 1);
124 return result;
127 namespace {
129 // Lazy-initialized map of abbreviated month names.
130 class AbbreviatedMonthsMap {
131 public:
132 static AbbreviatedMonthsMap* GetInstance() {
133 return Singleton<AbbreviatedMonthsMap>::get();
136 // Converts abbreviated month name |text| to its number (in range 1-12).
137 // On success returns true and puts the number in |number|.
138 bool GetMonthNumber(const string16& text, int* number) {
139 // Ignore the case of the month names. The simplest way to handle that
140 // is to make everything lowercase.
141 string16 text_lower(base::i18n::ToLower(text));
143 if (map_.find(text_lower) == map_.end())
144 return false;
146 *number = map_[text_lower];
147 return true;
150 private:
151 friend struct DefaultSingletonTraits<AbbreviatedMonthsMap>;
153 // Constructor, initializes the map based on ICU data. It is much faster
154 // to do that just once.
155 AbbreviatedMonthsMap() {
156 int32_t locales_count;
157 const icu::Locale* locales =
158 icu::DateFormat::getAvailableLocales(locales_count);
160 for (int32_t locale = 0; locale < locales_count; locale++) {
161 UErrorCode status(U_ZERO_ERROR);
163 icu::DateFormatSymbols format_symbols(locales[locale], status);
165 // If we cannot get format symbols for some locale, it's not a fatal
166 // error. Just try another one.
167 if (U_FAILURE(status))
168 continue;
170 int32_t months_count;
171 const icu::UnicodeString* months =
172 format_symbols.getShortMonths(months_count);
174 for (int32_t month = 0; month < months_count; month++) {
175 string16 month_name(months[month].getBuffer(),
176 static_cast<size_t>(months[month].length()));
178 // Ignore the case of the month names. The simplest way to handle that
179 // is to make everything lowercase.
180 month_name = base::i18n::ToLower(month_name);
182 map_[month_name] = month + 1;
184 // Sometimes ICU returns longer strings, but in FTP listings a shorter
185 // abbreviation is used (for example for the Russian locale). Make sure
186 // we always have a map entry for a three-letter abbreviation.
187 map_[month_name.substr(0, 3)] = month + 1;
192 // Maps lowercase month names to numbers in range 1-12.
193 std::map<string16, int> map_;
195 DISALLOW_COPY_AND_ASSIGN(AbbreviatedMonthsMap);
198 } // namespace
200 // static
201 bool FtpUtil::AbbreviatedMonthToNumber(const string16& text, int* number) {
202 return AbbreviatedMonthsMap::GetInstance()->GetMonthNumber(text, number);
205 // static
206 bool FtpUtil::LsDateListingToTime(const string16& month, const string16& day,
207 const string16& rest,
208 const base::Time& current_time,
209 base::Time* result) {
210 base::Time::Exploded time_exploded = { 0 };
212 if (!AbbreviatedMonthToNumber(month, &time_exploded.month)) {
213 // Work around garbage sent by some servers in the same column
214 // as the month. Take just last 3 characters of the string.
215 if (month.length() < 3 ||
216 !AbbreviatedMonthToNumber(month.substr(month.length() - 3),
217 &time_exploded.month)) {
218 return false;
222 if (!base::StringToInt(day, &time_exploded.day_of_month))
223 return false;
224 if (time_exploded.day_of_month > 31)
225 return false;
227 if (!base::StringToInt(rest, &time_exploded.year)) {
228 // Maybe it's time. Does it look like time (HH:MM)?
229 if (rest.length() == 5 && rest[2] == ':') {
230 if (!base::StringToInt(StringPiece16(rest.begin(), rest.begin() + 2),
231 &time_exploded.hour)) {
232 return false;
235 if (!base::StringToInt(StringPiece16(rest.begin() + 3, rest.begin() + 5),
236 &time_exploded.minute)) {
237 return false;
239 } else if (rest.length() == 4 && rest[1] == ':') {
240 // Sometimes it's just H:MM.
241 if (!base::StringToInt(StringPiece16(rest.begin(), rest.begin() + 1),
242 &time_exploded.hour)) {
243 return false;
246 if (!base::StringToInt(StringPiece16(rest.begin() + 2, rest.begin() + 4),
247 &time_exploded.minute)) {
248 return false;
250 } else {
251 return false;
254 // Guess the year.
255 base::Time::Exploded current_exploded;
256 current_time.LocalExplode(&current_exploded);
258 // If it's not possible for the parsed date to be in the current year,
259 // use the previous year.
260 if (time_exploded.month > current_exploded.month ||
261 (time_exploded.month == current_exploded.month &&
262 time_exploded.day_of_month > current_exploded.day_of_month)) {
263 time_exploded.year = current_exploded.year - 1;
264 } else {
265 time_exploded.year = current_exploded.year;
269 // We don't know the time zone of the listing, so just use local time.
270 *result = base::Time::FromLocalExploded(time_exploded);
271 return true;
274 // static
275 bool FtpUtil::WindowsDateListingToTime(const string16& date,
276 const string16& time,
277 base::Time* result) {
278 base::Time::Exploded time_exploded = { 0 };
280 // Date should be in format MM-DD-YY[YY].
281 std::vector<string16> date_parts;
282 base::SplitString(date, '-', &date_parts);
283 if (date_parts.size() != 3)
284 return false;
285 if (!base::StringToInt(date_parts[0], &time_exploded.month))
286 return false;
287 if (!base::StringToInt(date_parts[1], &time_exploded.day_of_month))
288 return false;
289 if (!base::StringToInt(date_parts[2], &time_exploded.year))
290 return false;
291 if (time_exploded.year < 0)
292 return false;
293 // If year has only two digits then assume that 00-79 is 2000-2079,
294 // and 80-99 is 1980-1999.
295 if (time_exploded.year < 80)
296 time_exploded.year += 2000;
297 else if (time_exploded.year < 100)
298 time_exploded.year += 1900;
300 // Time should be in format HH:MM[(AM|PM)]
301 if (time.length() < 5)
302 return false;
304 std::vector<string16> time_parts;
305 base::SplitString(time.substr(0, 5), ':', &time_parts);
306 if (time_parts.size() != 2)
307 return false;
308 if (!base::StringToInt(time_parts[0], &time_exploded.hour))
309 return false;
310 if (!base::StringToInt(time_parts[1], &time_exploded.minute))
311 return false;
312 if (!time_exploded.HasValidValues())
313 return false;
315 if (time.length() > 5) {
316 if (time.length() != 7)
317 return false;
318 string16 am_or_pm(time.substr(5, 2));
319 if (EqualsASCII(am_or_pm, "PM")) {
320 if (time_exploded.hour < 12)
321 time_exploded.hour += 12;
322 } else if (EqualsASCII(am_or_pm, "AM")) {
323 if (time_exploded.hour == 12)
324 time_exploded.hour = 0;
325 } else {
326 return false;
330 // We don't know the time zone of the server, so just use local time.
331 *result = base::Time::FromLocalExploded(time_exploded);
332 return true;
335 // static
336 string16 FtpUtil::GetStringPartAfterColumns(const string16& text, int columns) {
337 base::i18n::UTF16CharIterator iter(&text);
339 // TODO(jshin): Is u_isspace the right function to use here?
340 for (int i = 0; i < columns; i++) {
341 // Skip the leading whitespace.
342 while (!iter.end() && u_isspace(iter.get()))
343 iter.Advance();
345 // Skip the actual text of i-th column.
346 while (!iter.end() && !u_isspace(iter.get()))
347 iter.Advance();
350 string16 result(text.substr(iter.array_pos()));
351 TrimWhitespace(result, TRIM_ALL, &result);
352 return result;
355 } // namespace