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 23namespace net { 24 25// static 26std::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 70std::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 87std::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 115bool 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 158bool 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(¤t_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 220string16 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