// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/ftp/ftp_util.h" #include #include "base/logging.h" #include "base/string_number_conversions.h" #include "base/string_tokenizer.h" #include "base/string_util.h" #include "base/time.h" #include "base/utf_string_conversions.h" // For examples of Unix<->VMS path conversions, see the unit test file. On VMS // a path looks differently depending on whether it's a file or directory. namespace net { // static std::string FtpUtil::UnixFilePathToVMS(const std::string& unix_path) { if (unix_path.empty()) return std::string(); StringTokenizer tokenizer(unix_path, "/"); std::vector tokens; while (tokenizer.GetNext()) tokens.push_back(tokenizer.token()); if (unix_path[0] == '/') { // It's an absolute path. if (tokens.empty()) { DCHECK_EQ(1U, unix_path.length()); return "[]"; } if (tokens.size() == 1) return unix_path.substr(1); // Drop the leading slash. std::string result(tokens[0] + ":["); if (tokens.size() == 2) { // Don't ask why, it just works that way on VMS. result.append("000000"); } else { result.append(tokens[1]); for (size_t i = 2; i < tokens.size() - 1; i++) result.append("." + tokens[i]); } result.append("]" + tokens[tokens.size() - 1]); return result; } if (tokens.size() == 1) return unix_path; std::string result("["); for (size_t i = 0; i < tokens.size() - 1; i++) result.append("." + tokens[i]); result.append("]" + tokens[tokens.size() - 1]); return result; } // static std::string FtpUtil::UnixDirectoryPathToVMS(const std::string& unix_path) { if (unix_path.empty()) return std::string(); std::string path(unix_path); if (path[path.length() - 1] != '/') path.append("/"); // Reuse logic from UnixFilePathToVMS by appending a fake file name to the // real path and removing it after conversion. path.append("x"); path = UnixFilePathToVMS(path); return path.substr(0, path.length() - 1); } // static std::string FtpUtil::VMSPathToUnix(const std::string& vms_path) { if (vms_path.empty()) return "."; if (vms_path == "[]") return "/"; std::string result(vms_path); if (vms_path[0] == '[') { // It's a relative path. ReplaceFirstSubstringAfterOffset(&result, 0, "[.", ""); } else { // It's an absolute path. result.insert(0, "/"); ReplaceSubstringsAfterOffset(&result, 0, ":[000000]", "/"); ReplaceSubstringsAfterOffset(&result, 0, ":[", "/"); } std::replace(result.begin(), result.end(), '.', '/'); std::replace(result.begin(), result.end(), ']', '/'); // Make sure the result doesn't end with a slash. if (result.length() && result[result.length() - 1] == '/') result = result.substr(0, result.length() - 1); return result; } // static bool FtpUtil::ThreeLetterMonthToNumber(const string16& text, int* number) { const static char* months[] = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; for (size_t i = 0; i < arraysize(months); i++) { if (LowerCaseEqualsASCII(text, months[i])) { *number = i + 1; return true; } } // Special cases for directory listings in German (other three-letter month // abbreviations are the same as in English). Note that we don't need to do // a case-insensitive compare here. Only "ls -l" style listings may use // localized month names, and they will always start capitalized. Also, // converting non-ASCII characters to lowercase would be more complicated. if (text == UTF8ToUTF16("M\xc3\xa4r")) { // The full month name is M-(a-umlaut)-rz (March), which is M-(a-umlaut)r // when abbreviated. *number = 3; return true; } if (text == ASCIIToUTF16("Mai")) { *number = 5; return true; } if (text == ASCIIToUTF16("Okt")) { *number = 10; return true; } if (text == ASCIIToUTF16("Dez")) { *number = 12; return true; } return false; } // static bool FtpUtil::LsDateListingToTime(const string16& month, const string16& day, const string16& rest, const base::Time& current_time, base::Time* result) { base::Time::Exploded time_exploded = { 0 }; if (!ThreeLetterMonthToNumber(month, &time_exploded.month)) return false; if (!base::StringToInt(day, &time_exploded.day_of_month)) return false; if (!base::StringToInt(rest, &time_exploded.year)) { // Maybe it's time. Does it look like time (HH:MM)? if (rest.length() == 5 && rest[2] == ':') { if (!base::StringToInt(rest.substr(0, 2), &time_exploded.hour)) return false; if (!base::StringToInt(rest.substr(3, 2), &time_exploded.minute)) return false; } else if (rest.length() == 4 && rest[1] == ':') { // Sometimes it's just H:MM. if (!base::StringToInt(rest.substr(0, 1), &time_exploded.hour)) return false; if (!base::StringToInt(rest.substr(2, 2), &time_exploded.minute)) return false; } else { return false; } // Guess the year. base::Time::Exploded current_exploded; current_time.LocalExplode(¤t_exploded); // If it's not possible for the parsed date to be in the current year, // use the previous year. if (time_exploded.month > current_exploded.month || (time_exploded.month == current_exploded.month && time_exploded.day_of_month > current_exploded.day_of_month)) { time_exploded.year = current_exploded.year - 1; } else { time_exploded.year = current_exploded.year; } } // We don't know the time zone of the listing, so just use local time. *result = base::Time::FromLocalExploded(time_exploded); return true; } // static string16 FtpUtil::GetStringPartAfterColumns(const string16& text, int columns) { size_t pos = 0; for (int i = 0; i < columns; i++) { // Skip the leading whitespace. while (pos < text.length() && isspace(text[pos])) pos++; // Skip the actual text of i-th column. while (pos < text.length() && !isspace(text[pos])) pos++; } string16 result(text.substr(pos)); TrimWhitespace(result, TRIM_ALL, &result); return result; } } // namespace