    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.net.ftp.parser;
    019import java.text.ParseException;
    020import java.util.List;
    021import java.util.ListIterator;
    022
    023import org.apache.commons.net.ftp.FTPClientConfig;
    024import org.apache.commons.net.ftp.FTPFile;
    025
    026/**
    027 * Implementation FTPFileEntryParser and FTPFileListParser for standard
    028 * Unix Systems.
    029 *
    030 * This class is based on the logic of Daniel Savarese's
    031 * DefaultFTPListParser, but adapted to use regular expressions and to fit the
    032 * new FTPFileEntryParser interface.
    033 * @version $Id: UnixFTPEntryParser.java 1489361 2013-06-04 09:48:36Z sebb $
    034 * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
    035 */
    036public class UnixFTPEntryParser extends ConfigurableFTPFileEntryParserImpl
    037{
    038
    039    static final String DEFAULT_DATE_FORMAT
    040        = "MMM d yyyy"; //Nov 9 2001
    041
    042    static final String DEFAULT_RECENT_DATE_FORMAT
    043        = "MMM d HH:mm"; //Nov 9 20:06
    044
    045    static final String NUMERIC_DATE_FORMAT
    046        = "yyyy-MM-dd HH:mm"; //2001-11-09 20:06
    047
    048    /**
    049     * Some Linux distributions are now shipping an FTP server which formats
    050     * file listing dates in an all-numeric format:
    051     * <code>"yyyy-MM-dd HH:mm</code>.
    052     * This is a very welcome development,  and hopefully it will soon become
    053     * the standard.  However, since it is so new, for now, and possibly
    054     * forever, we merely accomodate it, but do not make it the default.
    055     * <p>
    056     * For now end users may specify this format only via
    057     * <code>UnixFTPEntryParser(FTPClientConfig)</code>.
    058     * Steve Cohen - 2005-04-17
    059     */
    060    public static final FTPClientConfig NUMERIC_DATE_CONFIG =
    061        new FTPClientConfig(
    062                FTPClientConfig.SYST_UNIX,
    063                NUMERIC_DATE_FORMAT,
    064                null, null, null, null);
    065
    066    /**
    067     * this is the regular expression used by this parser.
    068     *
    069     * Permissions:
    070     *    r   the file is readable
    071     *    w   the file is writable
    072     *    x   the file is executable
    073     *    -   the indicated permission is not granted
    074     *    L   mandatory locking occurs during access (the set-group-ID bit is
    075     *        on and the group execution bit is off)
    076     *    s   the set-user-ID or set-group-ID bit is on, and the corresponding
    077     *        user or group execution bit is also on
    078     *    S   undefined bit-state (the set-user-ID bit is on and the user
    079     *        execution bit is off)
    080     *    t   the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and
    081     *        execution is on
    082     *    T   the 1000 bit is turned on, and execution is off (undefined bit-
    083     *        state)
    084     *    e   z/OS external link bit
    085     */
    086    private static final String REGEX =
    087        "([bcdelfmpSs-])"
    088        +"(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s*"
    089        + "(\\d+)\\s+"                                  // link count
    090        + "(?:(\\S+(?:\\s\\S+)*?)\\s+)?"                // owner name (optional spaces)
    091        + "(?:(\\S+(?:\\s\\S+)*)\\s+)?"                 // group name (optional spaces)
    092        + "(\\d+(?:,\\s*\\d+)?)\\s+"                    // size or n,m
    093        /*
    094         * numeric or standard format date:
    095         *   yyyy-mm-dd (expecting hh:mm to follow)
    096         *   MMM [d]d
    097         *   [d]d MMM
    098         *   N.B. use non-space for MMM to allow for languages such as German which use
    099         *   diacritics (e.g. umlaut) in some abbreviations.
    100        */
    101        + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S{3}\\s+\\d{1,2})|(?:\\d{1,2}\\s+\\S{3}))\\s+"
    102        /*
    103           year (for non-recent standard format) - yyyy
    104           or time (for numeric or recent standard format) [h]h:mm
    105        */
    106        + "(\\d+(?::\\d+)?)\\s+"
    107
    108        + "(\\S*)(\\s*.*)"; // the rest
    109
    110
    111    /**
    112     * The default constructor for a UnixFTPEntryParser object.
    113     *
    114     * @exception IllegalArgumentException
    115     * Thrown if the regular expression is unparseable.  Should not be seen
    116     * under normal conditions.  It it is seen, this is a sign that
    117     * <code>REGEX</code> is  not a valid regular expression.
    118     */
    119    public UnixFTPEntryParser()
    120    {
    121        this(null);
    122    }
    123
    124    /**
    125     * This constructor allows the creation of a UnixFTPEntryParser object with
    126     * something other than the default configuration.
    127     *
    128     * @param config The {@link FTPClientConfig configuration} object used to
    129     * configure this parser.
    130     * @exception IllegalArgumentException
    131     * Thrown if the regular expression is unparseable.  Should not be seen
    132     * under normal conditions.  It it is seen, this is a sign that
    133     * <code>REGEX</code> is  not a valid regular expression.
    134     * @since 1.4
    135     */
    136    public UnixFTPEntryParser(FTPClientConfig config)
    137    {
    138        super(REGEX);
    139        configure(config);
    140    }
    141
    142    /**
    143     * Preparse the list to discard "total nnn" lines
    144     */
    145    @Override
    146    public List<String> preParse(List<String> original) {
    147        ListIterator<String> iter = original.listIterator();
    148        while (iter.hasNext()) {
    149            String entry = iter.next();
    150            if (entry.matches("^total \\d+$")) { // NET-389
    151                iter.remove();
    152            }
    153        }
    154        return original;
    155    }
    156
    157    /**
    158     * Parses a line of a unix (standard) FTP server file listing and converts
    159     * it into a usable format in the form of an <code> FTPFile </code>
    160     * instance.  If the file listing line doesn't describe a file,
    161     * <code> null </code> is returned, otherwise a <code> FTPFile </code>
    162     * instance representing the files in the directory is returned.
    163     * <p>
    164     * @param entry A line of text from the file listing
    165     * @return An FTPFile instance corresponding to the supplied entry
    166     */
    167//    @Override
    168    public FTPFile parseFTPEntry(String entry) {
    169        FTPFile file = new FTPFile();
    170        file.setRawListing(entry);
    171        int type;
    172        boolean isDevice = false;
    173
    174        if (matches(entry))
    175        {
    176            String typeStr = group(1);
    177            String hardLinkCount = group(15);
    178            String usr = group(16);
    179            String grp = group(17);
    180            String filesize = group(18);
    181            String datestr = group(19) + " " + group(20);
    182            String name = group(21);
    183            String endtoken = group(22);
    184
    185            try
    186            {
    187                file.setTimestamp(super.parseTimestamp(datestr));
    188            }
    189            catch (ParseException e)
    190            {
    191                 // intentionally do nothing
    192            }
    193
    194            // A 'whiteout' file is an ARTIFICIAL entry in any of several types of
    195            // 'translucent' filesystems, of which a 'union' filesystem is one.
    196
    197            // bcdelfmpSs-
    198            switch (typeStr.charAt(0))
    199            {
    200            case 'd':
    201                type = FTPFile.DIRECTORY_TYPE;
    202                break;
    203            case 'e': // NET-39 => z/OS external link
    204                type = FTPFile.SYMBOLIC_LINK_TYPE;
    205                break;
    206            case 'l':
    207                type = FTPFile.SYMBOLIC_LINK_TYPE;
    208                break;
    209            case 'b':
    210            case 'c':
    211                isDevice = true;
    212                type = FTPFile.FILE_TYPE; // TODO change this if DEVICE_TYPE implemented
    213                break;
    214            case 'f':
    215            case '-':
    216                type = FTPFile.FILE_TYPE;
    217                break;
    218            default: // e.g. ? and w = whiteout
    219                type = FTPFile.UNKNOWN_TYPE;
    220            }
    221
    222            file.setType(type);
    223
    224            int g = 4;
    225            for (int access = 0; access < 3; access++, g += 4)
    226            {
    227                // Use != '-' to avoid having to check for suid and sticky bits
    228                file.setPermission(access, FTPFile.READ_PERMISSION,
    229                                   (!group(g).equals("-")));
    230                file.setPermission(access, FTPFile.WRITE_PERMISSION,
    231                                   (!group(g + 1).equals("-")));
    232
    233                String execPerm = group(g + 2);
    234                if (!execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0)))
    235                {
    236                    file.setPermission(access, FTPFile.EXECUTE_PERMISSION, true);
    237                }
    238                else
    239                {
    240                    file.setPermission(access, FTPFile.EXECUTE_PERMISSION, false);
    241                }
    242            }
    243
    244            if (!isDevice)
    245            {
    246                try
    247                {
    248                    file.setHardLinkCount(Integer.parseInt(hardLinkCount));
    249                }
    250                catch (NumberFormatException e)
    251                {
    252                    // intentionally do nothing
    253                }
    254            }
    255
    256            file.setUser(usr);
    257            file.setGroup(grp);
    258
    259            try
    260            {
    261                file.setSize(Long.parseLong(filesize));
    262            }
    263            catch (NumberFormatException e)
    264            {
    265                // intentionally do nothing
    266            }
    267
    268            if (null == endtoken)
    269            {
    270                file.setName(name);
    271            }
    272            else
    273            {
    274                // oddball cases like symbolic links, file names
    275                // with spaces in them.
    276                name += endtoken;
    277                if (type == FTPFile.SYMBOLIC_LINK_TYPE)
    278                {
    279
    280                    int end = name.indexOf(" -> ");
    281                    // Give up if no link indicator is present
    282                    if (end == -1)
    283                    {
    284                        file.setName(name);
    285                    }
    286                    else
    287                    {
    288                        file.setName(name.substring(0, end));
    289                        file.setLink(name.substring(end + 4));
    290                    }
    291
    292                }
    293                else
    294                {
    295                    file.setName(name);
    296                }
    297            }
    298            return file;
    299        }
    300        return null;
    301    }
    302
    303    /**
    304     * Defines a default configuration to be used when this class is
    305     * instantiated without a {@link  FTPClientConfig  FTPClientConfig}
    306     * parameter being specified.
    307     * @return the default configuration for this parser.
    308     */
    309    @Override
    310    protected FTPClientConfig getDefaultConfiguration() {
    311        return new FTPClientConfig(
    312                FTPClientConfig.SYST_UNIX,
    313                DEFAULT_DATE_FORMAT,
    314                DEFAULT_RECENT_DATE_FORMAT,
    315                null, null, null);
    316    }
    317
    318}