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