001    /*
002     * Copyright 2001-2005 The Apache Software Foundation
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.apache.commons.net.ftp.parser;
017    import java.text.ParseException;
018    
019    import org.apache.commons.net.ftp.FTPClientConfig;
020    import org.apache.commons.net.ftp.FTPFile;
021    
022    /**
023     * Implementation FTPFileEntryParser and FTPFileListParser for standard
024     * Unix Systems.
025     *
026     * This class is based on the logic of Daniel Savarese's
027     * DefaultFTPListParser, but adapted to use regular expressions and to fit the
028     * new FTPFileEntryParser interface.
029     * @version $Id: UnixFTPEntryParser.java 161712 2005-04-18 02:57:04Z scohen $
030     * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
031     */
032    public class UnixFTPEntryParser extends ConfigurableFTPFileEntryParserImpl
033    {
034        /**
035         * months abbreviations looked for by this parser.  Also used
036         * to determine which month is matched by the parser
037         */
038        private static final String DEFAULT_MONTHS =
039            "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)";
040        
041        static final String DEFAULT_DATE_FORMAT 
042                    = "MMM d yyyy"; //Nov 9 2001
043        
044        static final String DEFAULT_RECENT_DATE_FORMAT 
045                    = "MMM d HH:mm"; //Nov 9 20:06
046    
047        static final String NUMERIC_DATE_FORMAT 
048                    = "yyyy-MM-dd HH:mm"; //2001-11-09 20:06
049    
050        /**
051         * Some Linux distributions are now shipping an FTP server which formats
052         * file listing dates in an all-numeric format: 
053         * <code>"yyyy-MM-dd HH:mm</code>.  
054         * This is a very welcome development,  and hopefully it will soon become 
055         * the standard.  However, since it is so new, for now, and possibly 
056         * forever, we merely accomodate it, but do not make it the default.
057         * <p>
058         * For now end users may specify this format only via 
059         * <code>UnixFTPEntryParser(FTPClientConfig)</code>.
060         * Steve Cohen - 2005-04-17
061         */
062        public static final FTPClientConfig NUMERIC_DATE_CONFIG =
063            new FTPClientConfig(
064                    FTPClientConfig.SYST_UNIX,
065                    NUMERIC_DATE_FORMAT,
066                    null, null, null, null);
067    
068        /**
069         * this is the regular expression used by this parser.
070         *
071         * Permissions:
072         *    r   the file is readable
073         *    w   the file is writable
074         *    x   the file is executable
075         *    -   the indicated permission is not granted
076         *    L   mandatory locking occurs during access (the set-group-ID bit is
077         *        on and the group execution bit is off)
078         *    s   the set-user-ID or set-group-ID bit is on, and the corresponding
079         *        user or group execution bit is also on
080         *    S   undefined bit-state (the set-user-ID bit is on and the user
081         *        execution bit is off)
082         *    t   the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and
083         *        execution is on
084         *    T   the 1000 bit is turned on, and execution is off (undefined bit-
085         *        state)
086         */
087        private static final String REGEX =
088            "([bcdlfmpSs-])"
089            +"(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+"
090            + "(\\d+)\\s+"
091            + "(\\S+)\\s+"
092            + "(?:(\\S+)\\s+)?"
093            + "(\\d+)\\s+"
094            
095            /*
096              numeric or standard format date
097            */
098            + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S+\\s+\\S+))\\s+"
099                    
100            /* 
101               year (for non-recent standard format) 
102                       or time (for numeric or recent standard format  
103                    */
104                    + "(\\d+(?::\\d+)?)\\s+"
105            
106                    + "(\\S*)(\\s*.*)";
107    
108    
109        /**
110         * The default constructor for a UnixFTPEntryParser object.
111         *
112         * @exception IllegalArgumentException
113         * Thrown if the regular expression is unparseable.  Should not be seen
114         * under normal conditions.  It it is seen, this is a sign that
115         * <code>REGEX</code> is  not a valid regular expression.
116         */
117        public UnixFTPEntryParser()
118        {
119            this(null);
120        }
121    
122        /**
123         * This constructor allows the creation of a UnixFTPEntryParser object with
124         * something other than the default configuration.
125         *
126         * @param config The {@link FTPClientConfig configuration} object used to 
127         * configure this parser.
128         * @exception IllegalArgumentException
129         * Thrown if the regular expression is unparseable.  Should not be seen
130         * under normal conditions.  It it is seen, this is a sign that
131         * <code>REGEX</code> is  not a valid regular expression.
132         * @since 1.4
133         */
134        public UnixFTPEntryParser(FTPClientConfig config)
135        {
136            super(REGEX);
137            configure(config);
138        }
139    
140    
141        /**
142         * Parses a line of a unix (standard) FTP server file listing and converts
143         * it into a usable format in the form of an <code> FTPFile </code>
144         * instance.  If the file listing line doesn't describe a file,
145         * <code> null </code> is returned, otherwise a <code> FTPFile </code>
146         * instance representing the files in the directory is returned.
147         * <p>
148         * @param entry A line of text from the file listing
149         * @return An FTPFile instance corresponding to the supplied entry
150         */
151            public FTPFile parseFTPEntry(String entry) {
152            FTPFile file = new FTPFile();
153            file.setRawListing(entry);
154            int type;
155            boolean isDevice = false;
156    
157            if (matches(entry))
158            {
159                String typeStr = group(1);
160                String hardLinkCount = group(15);
161                String usr = group(16);
162                String grp = group(17);
163                String filesize = group(18);
164                String datestr = group(19) + " " + group(20);
165                String name = group(21);
166                String endtoken = group(22);
167    
168                try
169                {
170                    file.setTimestamp(super.parseTimestamp(datestr));
171                }
172                catch (ParseException e)
173                {
174                    return null;  // this is a parsing failure too.
175                }
176                
177                
178                // bcdlfmpSs-
179                switch (typeStr.charAt(0))
180                {
181                case 'd':
182                    type = FTPFile.DIRECTORY_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        protected FTPClientConfig getDefaultConfiguration() {
287            return new FTPClientConfig(
288                    FTPClientConfig.SYST_UNIX,
289                    DEFAULT_DATE_FORMAT,
290                    DEFAULT_RECENT_DATE_FORMAT,
291                    null, null, null);
292        }
293        
294        
295        
296    
297    }