    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    import java.util.List;
    021    import java.util.ListIterator;
    022
    023    import org.apache.commons.net.ftp.FTPClientConfig;
    024    import 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 1299238 2012-03-10 17:12:28Z sebb $
    034     * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
    035     */
    036    public 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        public FTPFile parseFTPEntry(String entry) {
    168            FTPFile file = new FTPFile();
    169            file.setRawListing(entry);
    170            int type;
    171            boolean isDevice = false;
    172
    173            if (matches(entry))
    174            {
    175                String typeStr = group(1);
    176                String hardLinkCount = group(15);
    177                String usr = group(16);
    178                String grp = group(17);
    179                String filesize = group(18);
    180                String datestr = group(19) + " " + group(20);
    181                String name = group(21);
    182                String endtoken = group(22);
    183
    184                try
    185                {
    186                    file.setTimestamp(super.parseTimestamp(datestr));
    187                }
    188                catch (ParseException e)
    189                {
    190                     // intentionally do nothing
    191                }
    192
    193                // A 'whiteout' file is an ARTIFICIAL entry in any of several types of
    194                // 'translucent' filesystems, of which a 'union' filesystem is one.
    195
    196                // bcdelfmpSs-
    197                switch (typeStr.charAt(0))
    198                {
    199                case 'd':
    200                    type = FTPFile.DIRECTORY_TYPE;
    201                    break;
    202                case 'e': // NET-39 => z/OS external link
    203                    type = FTPFile.SYMBOLIC_LINK_TYPE;
    204                    break;
    205                case 'l':
    206                    type = FTPFile.SYMBOLIC_LINK_TYPE;
    207                    break;
    208                case 'b':
    209                case 'c':
    210                    isDevice = true;
    211                    type = FTPFile.FILE_TYPE; // TODO change this if DEVICE_TYPE implemented
    212                    break;
    213                case 'f':
    214                case '-':
    215                    type = FTPFile.FILE_TYPE;
    216                    break;
    217                default: // e.g. ? and w = whiteout
    218                    type = FTPFile.UNKNOWN_TYPE;
    219                }
    220
    221                file.setType(type);
    222
    223                int g = 4;
    224                for (int access = 0; access < 3; access++, g += 4)
    225                {
    226                    // Use != '-' to avoid having to check for suid and sticky bits
    227                    file.setPermission(access, FTPFile.READ_PERMISSION,
    228                                       (!group(g).equals("-")));
    229                    file.setPermission(access, FTPFile.WRITE_PERMISSION,
    230                                       (!group(g + 1).equals("-")));
    231
    232                    String execPerm = group(g + 2);
    233                    if (!execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0)))
    234                    {
    235                        file.setPermission(access, FTPFile.EXECUTE_PERMISSION, true);
    236                    }
    237                    else
    238                    {
    239                        file.setPermission(access, FTPFile.EXECUTE_PERMISSION, false);
    240                    }
    241                }
    242
    243                if (!isDevice)
    244                {
    245                    try
    246                    {
    247                        file.setHardLinkCount(Integer.parseInt(hardLinkCount));
    248                    }
    249                    catch (NumberFormatException e)
    250                    {
    251                        // intentionally do nothing
    252                    }
    253                }
    254
    255                file.setUser(usr);
    256                file.setGroup(grp);
    257
    258                try
    259                {
    260                    file.setSize(Long.parseLong(filesize));
    261                }
    262                catch (NumberFormatException e)
    263                {
    264                    // intentionally do nothing
    265                }
    266
    267                if (null == endtoken)
    268                {
    269                    file.setName(name);
    270                }
    271                else
    272                {
    273                    // oddball cases like symbolic links, file names
    274                    // with spaces in them.
    275                    name += endtoken;
    276                    if (type == FTPFile.SYMBOLIC_LINK_TYPE)
    277                    {
    278
    279                        int end = name.indexOf(" -> ");
    280                        // Give up if no link indicator is present
    281                        if (end == -1)
    282                        {
    283                            file.setName(name);
    284                        }
    285                        else
    286                        {
    287                            file.setName(name.substring(0, end));
    288                            file.setLink(name.substring(end + 4));
    289                        }
    290
    291                    }
    292                    else
    293                    {
    294                        file.setName(name);
    295                    }
    296                }
    297                return file;
    298            }
    299            return null;
    300        }
    301
    302        /**
    303         * Defines a default configuration to be used when this class is
    304         * instantiated without a {@link  FTPClientConfig  FTPClientConfig}
    305         * parameter being specified.
    306         * @return the default configuration for this parser.
    307         */
    308        @Override
    309        protected FTPClientConfig getDefaultConfiguration() {
    310            return new FTPClientConfig(
    311                    FTPClientConfig.SYST_UNIX,
    312                    DEFAULT_DATE_FORMAT,
    313                    DEFAULT_RECENT_DATE_FORMAT,
    314                    null, null, null);
    315        }
    316
    317    }