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 658518 2008-05-21 01:04:30Z 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+"
088            + "(?:(\\S+(?:\\s\\S+)*?)\\s+)?"                // owner name (optional spaces)
089            + "(?:(\\S+(?:\\s\\S+)*)\\s+)?"                 // group name (optional spaces)
090            + "(\\d+(?:,\\s*\\d+)?)\\s+"
091            
092            /*
093              numeric or standard format date
094            */
095            + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S+\\s+\\S+))\\s+"
096            
097            /* 
098               year (for non-recent standard format) 
099               or time (for numeric or recent standard format  
100            */
101            + "(\\d+(?::\\d+)?)\\s+"
102            
103            + "(\\S*)(\\s*.*)";
104    
105    
106        /**
107         * The default constructor for a UnixFTPEntryParser object.
108         *
109         * @exception IllegalArgumentException
110         * Thrown if the regular expression is unparseable.  Should not be seen
111         * under normal conditions.  It it is seen, this is a sign that
112         * <code>REGEX</code> is  not a valid regular expression.
113         */
114        public UnixFTPEntryParser()
115        {
116            this(null);
117        }
118    
119        /**
120         * This constructor allows the creation of a UnixFTPEntryParser object with
121         * something other than the default configuration.
122         *
123         * @param config The {@link FTPClientConfig configuration} object used to 
124         * configure this parser.
125         * @exception IllegalArgumentException
126         * Thrown if the regular expression is unparseable.  Should not be seen
127         * under normal conditions.  It it is seen, this is a sign that
128         * <code>REGEX</code> is  not a valid regular expression.
129         * @since 1.4
130         */
131        public UnixFTPEntryParser(FTPClientConfig config)
132        {
133            super(REGEX);
134            configure(config);
135        }
136    
137    
138        /**
139         * Parses a line of a unix (standard) FTP server file listing and converts
140         * it into a usable format in the form of an <code> FTPFile </code>
141         * instance.  If the file listing line doesn't describe a file,
142         * <code> null </code> is returned, otherwise a <code> FTPFile </code>
143         * instance representing the files in the directory is returned.
144         * <p>
145         * @param entry A line of text from the file listing
146         * @return An FTPFile instance corresponding to the supplied entry
147         */
148        public FTPFile parseFTPEntry(String entry) {
149            FTPFile file = new FTPFile();
150            file.setRawListing(entry);
151            int type;
152            boolean isDevice = false;
153    
154            if (matches(entry))
155            {
156                String typeStr = group(1);
157                String hardLinkCount = group(15);
158                String usr = group(16);
159                String grp = group(17);
160                String filesize = group(18);
161                String datestr = group(19) + " " + group(20);
162                String name = group(21);
163                String endtoken = group(22);
164    
165                try
166                {
167                    file.setTimestamp(super.parseTimestamp(datestr));
168                }
169                catch (ParseException e)
170                {
171                     // intentionally do nothing
172                }
173                
174                
175                // bcdlfmpSs-
176                switch (typeStr.charAt(0))
177                {
178                case 'd':
179                    type = FTPFile.DIRECTORY_TYPE;
180                    break;
181                case 'e':
182                    type = FTPFile.SYMBOLIC_LINK_TYPE;
183                    break;
184                case 'l':
185                    type = FTPFile.SYMBOLIC_LINK_TYPE;
186                    break;
187                case 'b':
188                case 'c':
189                    isDevice = true;
190                    // break; - fall through
191                case 'f':
192                case '-':
193                    type = FTPFile.FILE_TYPE;
194                    break;
195                default:
196                    type = FTPFile.UNKNOWN_TYPE;
197                }
198    
199                file.setType(type);
200    
201                int g = 4;
202                for (int access = 0; access < 3; access++, g += 4)
203                {
204                    // Use != '-' to avoid having to check for suid and sticky bits
205                    file.setPermission(access, FTPFile.READ_PERMISSION,
206                                       (!group(g).equals("-")));
207                    file.setPermission(access, FTPFile.WRITE_PERMISSION,
208                                       (!group(g + 1).equals("-")));
209    
210                    String execPerm = group(g + 2);
211                    if (!execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0)))
212                    {
213                        file.setPermission(access, FTPFile.EXECUTE_PERMISSION, true);
214                    }
215                    else
216                    {
217                        file.setPermission(access, FTPFile.EXECUTE_PERMISSION, false);
218                    }
219                }
220    
221                if (!isDevice)
222                {
223                    try
224                    {
225                        file.setHardLinkCount(Integer.parseInt(hardLinkCount));
226                    }
227                    catch (NumberFormatException e)
228                    {
229                        // intentionally do nothing
230                    }
231                }
232    
233                file.setUser(usr);
234                file.setGroup(grp);
235    
236                try
237                {
238                    file.setSize(Long.parseLong(filesize));
239                }
240                catch (NumberFormatException e)
241                {
242                    // intentionally do nothing
243                }
244                
245                if (null == endtoken)
246                {
247                    file.setName(name);
248                }
249                else
250                {
251                    // oddball cases like symbolic links, file names
252                    // with spaces in them.
253                    name += endtoken;
254                    if (type == FTPFile.SYMBOLIC_LINK_TYPE)
255                    {
256    
257                        int end = name.indexOf(" -> ");
258                        // Give up if no link indicator is present
259                        if (end == -1)
260                        {
261                            file.setName(name);
262                        }
263                        else
264                        {
265                            file.setName(name.substring(0, end));
266                            file.setLink(name.substring(end + 4));
267                        }
268    
269                    }
270                    else
271                    {
272                        file.setName(name);
273                    }
274                }
275                return file;
276            }
277            return null;
278        }
279    
280        /**
281         * Defines a default configuration to be used when this class is
282         * instantiated without a {@link  FTPClientConfig  FTPClientConfig}
283         * parameter being specified.
284         * @return the default configuration for this parser.
285         */
286        @Override
287        protected FTPClientConfig getDefaultConfiguration() {
288            return new FTPClientConfig(
289                    FTPClientConfig.SYST_UNIX,
290                    DEFAULT_DATE_FORMAT,
291                    DEFAULT_RECENT_DATE_FORMAT,
292                    null, null, null);
293        }
294        
295    }