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    }