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    }