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 1243400 2012-02-13 03:02:29Z 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    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}