    View Javadoc
    1   package org.apache.commons.net.ftp;
    2
    3   /* ====================================================================
    4    * The Apache Software License, Version 1.1
    5    *
    6    * Copyright (c) 2001 The Apache Software Foundation.  All rights
    7    * reserved.
    8    *
    9    * Redistribution and use in source and binary forms, with or without
    10   * modification, are permitted provided that the following conditions
    11   * are met:
    12   *
    13   * 1. Redistributions of source code must retain the above copyright
    14   *    notice, this list of conditions and the following disclaimer.
    15   *
    16   * 2. Redistributions in binary form must reproduce the above copyright
    17   *    notice, this list of conditions and the following disclaimer in
    18   *    the documentation and/or other materials provided with the
    19   *    distribution.
    20   *
    21   * 3. The end-user documentation included with the redistribution,
    22   *    if any, must include the following acknowledgment:
    23   *       "This product includes software developed by the
    24   *        Apache Software Foundation (http://www.apache.org/)."
    25   *    Alternately, this acknowledgment may appear in the software itself,
    26   *    if and wherever such third-party acknowledgments normally appear.
    27   *
    28   * 4. The names "Apache" and "Apache Software Foundation" and
    29   *    "Apache Commons" must not be used to endorse or promote products
    30   *    derived from this software without prior written permission. For
    31   *    written permission, please contact apache@apache.org.
    32   *
    33   * 5. Products derived from this software may not be called "Apache",
    34   *    nor may "Apache" appear in their name, without
    35   *    prior written permission of the Apache Software Foundation.
    36   *
    37   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
    38   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    39   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    40   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
    41   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    42   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    43   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
    44   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    45   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    46   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
    47   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    48   * SUCH DAMAGE.
    49   * ====================================================================
    50   *
    51   * This software consists of voluntary contributions made by many
    52   * individuals on behalf of the Apache Software Foundation.  For more
    53   * information on the Apache Software Foundation, please see
    54   * <http://www.apache.org/>;.
    55   */
    56
    57  import java.io.BufferedReader;
    58  import java.io.IOException;
    59  import java.io.InputStream;
    60  import java.io.InputStreamReader;
    61  import java.util.Calendar;
    62  import java.util.StringTokenizer;
    63  import java.util.Vector;
    64
    65  /****
    66   * DefaultFTPFileListParser is the default implementation of
    67   * <a href="org.apache.commons.net.ftp.FTPFileListParser.html"> FTPFileListParser </a>
    68   * used by <a href="org.apache.commons.net.ftp.FTPClient.html"> FTPClient </a>
    69   * to parse file listings.
    70   * Sometimes you will want to parse unusual listing formats, in which
    71   * case you would create your own implementation of FTPFileListParser and
    72   * if necessary, subclass FTPFile.
    73   * <p>
    74   * <p>
    75   * @author Daniel F. Savarese
    76   * @see FTPFileListParser
    77   * @see FTPFile
    78   * @see FTPClient#listFiles
    79   ***/
    80
    81  public final class DefaultFTPFileListParser implements FTPFileListParser
    82  {
    83
    84
    85      // end is one beyond end
    86      private int __charArrayToInt(char[] arr, int start, int end)
    87      {
    88          int value = 0, decimal;
    89          decimal = 1;
    90          while (end-- > start)
    91          {
    92              value += (decimal * (arr[end] - '0'));
    93              decimal *= 10;
    94          }
    95          return value;
    96      }
    97
    98      private long __charArrayToLong(char[] arr, int start, int end)
    99      {
    100         long value = 0, decimal;
    101         decimal = 1;
    102         while (end-- > start)
    103         {
    104             value += (decimal * (arr[end] - '0'));
    105             decimal *= 10;
    106         }
    107         return value;
    108     }
    109
    110     private int __skipWhitespace(char[] cToken, int start)
    111     {
    112         while (start < cToken.length && Character.isWhitespace(cToken[start]))
    113             ++start;
    114         return start;
    115     }
    116
    117     private int __skipDigits(char[] cToken, int start)
    118     {
    119         while (start < cToken.length && Character.isDigit(cToken[start]))
    120             ++start;
    121         return start;
    122     }
    123
    124     private int __skipNonWhitespace(char[] cToken, int start)
    125     {
    126         while (start < cToken.length && !Character.isWhitespace(cToken[start]))
    127             ++start;
    128         return start;
    129     }
    130
    131     private int __skipNonWhitespaceToLower(char[] cToken, int start)
    132     {
    133         while (start < cToken.length && !Character.isWhitespace(cToken[start]))
    134         {
    135             cToken[start] = Character.toLowerCase(cToken[start]);
    136             ++start;
    137         }
    138         return start;
    139     }
    140
    141
    142     /****
    143      * Parses an FTP server listing entry (a single line) and returns an
    144      * FTPFile instance with the resulting information.  If the entry could
    145      * not be parsed, returns null.
    146      * <p>
    147      * @param entry  A single line of an FTP server listing with the
    148      *                 end of line truncated.
    149      * @return An FTPFile instance representing the file information.  null if
    150      *         the entry could be parsed, returns null.
    151      ***/
    152     public FTPFile parseFTPEntry(String entry)
    153     {
    154         int access, start, end, type, month, year, hour, minutes;
    155         boolean isDevice;
    156         Calendar date;
    157         StringTokenizer tokenizer;
    158         String sToken;
    159         char cToken[];
    160         FTPFile file;
    161
    162         try
    163         {
    164             cToken = entry.toCharArray();
    165
    166             file = new FTPFile();
    167             file.setRawListing(entry);
    168
    169             isDevice = (cToken[0] == 'b' || cToken[0] == 'c');
    170
    171             switch (cToken[0])
    172             {
    173             case 'd':
    174                 type = FTPFile.DIRECTORY_TYPE;
    175                 break;
    176             case 'l':
    177                 type = FTPFile.SYMBOLIC_LINK_TYPE;
    178                 break;
    179             default:
    180                 type = FTPFile.FILE_TYPE;
    181                 break;
    182             }
    183
    184             file.setType(type);
    185
    186             for (access = 0, start = 1; access < 3; access++)
    187             {
    188                 // We use != '-' so we avoid having to check for suid and sticky bits
    189                 file.setPermission(access, FTPFile.READ_PERMISSION,
    190                                    (cToken[start++] != '-'));
    191                 file.setPermission(access, FTPFile.WRITE_PERMISSION,
    192                                    (cToken[start++] != '-'));
    193                 file.setPermission(access, FTPFile.EXECUTE_PERMISSION,
    194                                    (cToken[start++] != '-'));
    195             }
    196
    197             start = __skipWhitespace(cToken, start);
    198             end = __skipDigits(cToken, start);
    199             file.setHardLinkCount(__charArrayToInt(cToken, start, end));
    200
    201             start = __skipWhitespace(cToken, end);
    202             end = __skipNonWhitespace(cToken, start);
    203             // Get user and group
    204             file.setUser(new String(cToken, start, end - start));
    205
    206             start = __skipWhitespace(cToken, end);
    207             end = __skipNonWhitespace(cToken, start);
    208             file.setGroup(new String(cToken, start, end - start));
    209
    210             // Get size, if block or character device, set size to zero and skip
    211             // next two tokens.
    212             if (isDevice)
    213             {
    214                 start = __skipWhitespace(cToken, end);
    215                 end = __skipNonWhitespace(cToken, start);
    216                 start = __skipWhitespace(cToken, end);
    217                 end = __skipNonWhitespace(cToken, start);
    218                 // Don't explicitly set size because it is zero by default
    219             }
    220             else
    221             {
    222                 start = __skipWhitespace(cToken, end);
    223                 end = __skipDigits(cToken, start);
    224                 file.setSize(__charArrayToLong(cToken, start, end));
    225             }
    226
    227             start = __skipWhitespace(cToken, end);
    228             end = __skipNonWhitespaceToLower(cToken, start);
    229
    230             // Get month
    231             switch (cToken[start])
    232             {
    233             case 'a':
    234                 if (cToken[end - 1] == 'r')
    235                     month = 3;
    236                 else
    237                     month = 7;
    238                 break;
    239             case 'd':
    240                 month = 11;
    241                 break;
    242             case 'f':
    243                 month = 1;
    244                 break;
    245             case 'j':
    246                 if (cToken[end - 1] == 'l')
    247                     month = 6;
    248                 else if (cToken[start + 1] == 'a')
    249                     month = 0;
    250                 else
    251                     month = 5;
    252                 break;
    253             case 'm':
    254                 if (cToken[end - 1] == 'y')
    255                     month = 4;
    256                 else
    257                     month = 2;
    258                 break;
    259             case 'n':
    260                 month = 10;
    261                 break;
    262             case 'o':
    263                 month = 9;
    264                 break;
    265             case 's':
    266                 month = 8;
    267                 break;
    268             default:
    269                 month = 0;
    270                 break;
    271             }
    272
    273             // Get day, and store in access
    274             start = __skipWhitespace(cToken, end);
    275             end = __skipDigits(cToken, start);
    276             access = __charArrayToInt(cToken, start, end);
    277
    278             start = __skipWhitespace(cToken, end);
    279             end = __skipDigits(cToken, start);
    280
    281             date = Calendar.getInstance();
    282
    283             try
    284             {
    285                 // If token contains a :, it must be a time, otherwise a year
    286                 if (cToken[end] == ':')
    287                 {
    288                     year = date.get(Calendar.YEAR);
    289                     hour = date.get(Calendar.MONTH);
    290                     if (hour < month)
    291                         --year;
    292                     hour = __charArrayToInt(cToken, start, end);
    293                     start = end + 1;
    294                     end = __skipDigits(cToken, start);
    295                     minutes = __charArrayToInt(cToken, start, end);
    296                 }
    297                 else
    298                 {
    299                     // Have to set minutes or compiler will complain not initialized
    300                     hour = minutes = -1;
    301                     year = __charArrayToInt(cToken, start, end);
    302                 }
    303
    304                 date.clear();
    305                 date.set(Calendar.YEAR, year);
    306                 date.set(Calendar.MONTH, month);
    307                 date.set(Calendar.DATE, access);
    308
    309                 if (hour != -1)
    310                 {
    311                     date.set(Calendar.HOUR, hour);
    312                     date.set(Calendar.MINUTE, minutes);
    313                 }
    314
    315             }
    316             catch (IllegalArgumentException e)
    317             {
    318                 // Do nothing
    319             }
    320
    321             file.setTimestamp(date);
    322
    323             // This is dangerous, but we're going to assume there is only one
    324             // space after the date.  Most servers seem to use that format and
    325             // we need to be able to preserve leading spacesin filenames.
    326             //start = __skipWhitespace(cToken, end);
    327             start = end + 1;
    328             end = __skipNonWhitespace(cToken, start);
    329
    330             if (end >= cToken.length)
    331             {
    332                 file.setName(new String(cToken, start, end - start));
    333                 return file;
    334             }
    335
    336             // Now we have to deal with the possibilities of symbolic links and
    337             // filenames with spaces.  The filename and/or link may contain
    338             // spaces, numbers, or appear like the date entry, group, etc.,
    339
    340             sToken = new String(cToken, start, cToken.length - start);
    341
    342             if (type == FTPFile.SYMBOLIC_LINK_TYPE)
    343             {
    344                 end = sToken.indexOf(" -> ");
    345                 // Give up if no link indicator is present
    346                 if (end == -1)
    347                 {
    348                     file.setName(sToken);
    349                     return file;
    350                 }
    351
    352                 file.setName(sToken.substring(0, end));
    353                 file.setLink(sToken.substring(end + 4));
    354                 return file;
    355             }
    356
    357             // For other cases, just take the entire token
    358
    359             file.setName(sToken);
    360         }
    361         catch (ArrayIndexOutOfBoundsException e)
    362         {
    363             return null;
    364         }
    365         catch (StringIndexOutOfBoundsException e)
    366         {
    367             return null;
    368         }
    369
    370         return file;
    371     }
    372
    373
    374     /****
    375      * Parses an FTP server file listing and converts it into a usable format
    376      * in the form of an array of <code> FTPFile </code> instances.  If the
    377      * file list contains no files, <code> null </code> is returned, otherwise
    378      * an array of <code> FTPFile </code> instances representing the files in
    379      * the directory is returned.
    380      * <p>
    381      * @param listStream The InputStream from which the file list should be
    382      *        read.
    383      * @return The list of file information contained in the given path.  null
    384      *     if the list could not be obtained or if there are no files in
    385      *     the directory.
    386      * @exception IOException  If an I/O error occurs reading the listStream.
    387      ***/
    388     public FTPFile[] parseFileList(InputStream listStream) throws IOException
    389     {
    390         String line;
    391         Vector results;
    392         BufferedReader reader;
    393         FTPFile entry;
    394
    395         reader = new BufferedReader(new InputStreamReader(listStream));
    396
    397         if ((line = reader.readLine()) == null)
    398         {
    399             results = null;
    400         }
    401         else
    402         {
    403             results = new Vector();
    404
    405             // This is to handle a line at the beginning of the listing
    406             // that says "total xx" or "Gemstat xx" or something else.
    407             if (line.toLowerCase().startsWith("total"))
    408                 line = reader.readLine();
    409             else
    410             {
    411                 if ((entry = parseFTPEntry(line)) != null)
    412                     results.addElement(entry);
    413                 line = reader.readLine();
    414             }
    415
    416             while (line != null)
    417             {
    418                 if (line.length() == 0 || (entry = parseFTPEntry(line)) == null)
    419                 {
    420                     results = null;
    421                     break;
    422                 }
    423                 results.addElement(entry);
    424                 line = reader.readLine();
    425             }
    426         }
    427
    428         // Finish reading from stream just in case
    429         if (line != null)
    430             while ((line = reader.readLine()) != null)
    431                 ;
    432
    433         reader.close();
    434
    435         if (results != null)
    436         {
    437             FTPFile[] result;
    438
    439             result = new FTPFile[results.size()];
    440             if (result.length > 0)
    441                 results.copyInto(result);
    442             return result;
    443         }
    444
    445         return null;
    446     }
    447
    448 }

This page was automatically generated by Maven