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}