Home | History | Annotate | Download | only in ftp
      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
     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
     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
     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
    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
    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
    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