Home | History | Annotate | Download | only in ftp
      1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.  Use of this
      2 // source code is governed by a BSD-style license that can be found in the
      3 // LICENSE file.
      4 
      5 #include "net/ftp/ftp_directory_listing_parser_ls.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/string_util.h"
     10 #include "net/ftp/ftp_util.h"
     11 
     12 namespace {
     13 
     14 bool LooksLikeUnixPermission(const string16& text) {
     15   if (text.length() != 3)
     16     return false;
     17 
     18   // Meaning of the flags:
     19   // r - file is readable
     20   // w - file is writable
     21   // x - file is executable
     22   // s or S - setuid/setgid bit set
     23   // t or T - "sticky" bit set
     24   return ((text[0] == 'r' || text[0] == '-') &&
     25           (text[1] == 'w' || text[1] == '-') &&
     26           (text[2] == 'x' || text[2] == 's' || text[2] == 'S' ||
     27            text[2] == 't' || text[2] == 'T' || text[2] == '-'));
     28 }
     29 
     30 bool LooksLikeUnixPermissionsListing(const string16& text) {
     31   if (text.length() < 10)
     32     return false;
     33 
     34   if (text[0] != 'b' && text[0] != 'c' && text[0] != 'd' &&
     35       text[0] != 'l' && text[0] != 'p' && text[0] != 's' &&
     36       text[0] != '-')
     37     return false;
     38 
     39   return (LooksLikeUnixPermission(text.substr(1, 3)) &&
     40           LooksLikeUnixPermission(text.substr(4, 3)) &&
     41           LooksLikeUnixPermission(text.substr(7, 3)) &&
     42           (text.substr(10).empty() || text.substr(10) == ASCIIToUTF16("+")));
     43 }
     44 
     45 bool DetectColumnOffset(const std::vector<string16>& columns, int* offset) {
     46   base::Time time;
     47 
     48   if (columns.size() >= 8 &&
     49       net::FtpUtil::LsDateListingToTime(columns[5], columns[6], columns[7],
     50                                         &time)) {
     51     // Standard listing, exactly like ls -l.
     52     *offset = 2;
     53     return true;
     54   }
     55 
     56   if (columns.size() >= 7 &&
     57       net::FtpUtil::LsDateListingToTime(columns[4], columns[5], columns[6],
     58                                         &time)) {
     59     // wu-ftpd listing, no "number of links" column.
     60     *offset = 1;
     61     return true;
     62   }
     63 
     64   if (columns.size() >= 6 &&
     65       net::FtpUtil::LsDateListingToTime(columns[3], columns[4], columns[5],
     66                                         &time)) {
     67     // Xplain FTP Server listing for folders, like this:
     68     // drwxr-xr-x               folder        0 Jul 17  2006 online
     69     *offset = 0;
     70     return true;
     71   }
     72 
     73   // Unrecognized listing style.
     74   return false;
     75 }
     76 
     77 }  // namespace
     78 
     79 namespace net {
     80 
     81 FtpDirectoryListingParserLs::FtpDirectoryListingParserLs()
     82     : received_nonempty_line_(false),
     83       received_total_line_(false) {
     84 }
     85 
     86 bool FtpDirectoryListingParserLs::ConsumeLine(const string16& line) {
     87   if (line.empty() && !received_nonempty_line_) {
     88     // Allow empty lines only at the beginning of the listing. For example VMS
     89     // systems in Unix emulation mode add an empty line before the first listing
     90     // entry.
     91     return true;
     92   }
     93   received_nonempty_line_ = true;
     94 
     95   std::vector<string16> columns;
     96   SplitString(CollapseWhitespace(line, false), ' ', &columns);
     97 
     98   // Some FTP servers put a "total n" line at the beginning of the listing
     99   // (n is an integer). Allow such a line, but only once, and only if it's
    100   // the first non-empty line. Do not match the word exactly, because it may be
    101   // in different languages (at least English and German have been seen in the
    102   // field).
    103   if (columns.size() == 2 && !received_total_line_) {
    104     received_total_line_ = true;
    105 
    106     int total_number;
    107     if (!StringToInt(columns[1], &total_number))
    108       return false;
    109     if (total_number < 0)
    110       return false;
    111 
    112     return true;
    113   }
    114 
    115   int column_offset;
    116   if (!DetectColumnOffset(columns, &column_offset))
    117     return false;
    118 
    119   // We may receive file names containing spaces, which can make the number of
    120   // columns arbitrarily large. We will handle that later. For now just make
    121   // sure we have all the columns that should normally be there.
    122   if (columns.size() < 7U + column_offset)
    123     return false;
    124 
    125   if (!LooksLikeUnixPermissionsListing(columns[0]))
    126     return false;
    127 
    128   FtpDirectoryListingEntry entry;
    129   if (columns[0][0] == 'l') {
    130     entry.type = FtpDirectoryListingEntry::SYMLINK;
    131   } else if (columns[0][0] == 'd') {
    132     entry.type = FtpDirectoryListingEntry::DIRECTORY;
    133   } else {
    134     entry.type = FtpDirectoryListingEntry::FILE;
    135   }
    136 
    137   if (!StringToInt64(columns[2 + column_offset], &entry.size))
    138     return false;
    139   if (entry.size < 0)
    140     return false;
    141   if (entry.type != FtpDirectoryListingEntry::FILE)
    142     entry.size = -1;
    143 
    144   if (!FtpUtil::LsDateListingToTime(columns[3 + column_offset],
    145                                     columns[4 + column_offset],
    146                                     columns[5 + column_offset],
    147                                     &entry.last_modified)) {
    148     return false;
    149   }
    150 
    151   entry.name = FtpUtil::GetStringPartAfterColumns(line, 6 + column_offset);
    152   if (entry.type == FtpDirectoryListingEntry::SYMLINK) {
    153     string16::size_type pos = entry.name.rfind(ASCIIToUTF16(" -> "));
    154     if (pos == string16::npos)
    155       return false;
    156     entry.name = entry.name.substr(0, pos);
    157   }
    158 
    159   entries_.push(entry);
    160   return true;
    161 }
    162 
    163 bool FtpDirectoryListingParserLs::OnEndOfInput() {
    164   return true;
    165 }
    166 
    167 bool FtpDirectoryListingParserLs::EntryAvailable() const {
    168   return !entries_.empty();
    169 }
    170 
    171 FtpDirectoryListingEntry FtpDirectoryListingParserLs::PopEntry() {
    172   FtpDirectoryListingEntry entry = entries_.front();
    173   entries_.pop();
    174   return entry;
    175 }
    176 
    177 }  // namespace net
    178