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;
019import java.io.Serializable;
020import java.util.Calendar;
021import java.util.Date;
022import java.util.Formatter;
023import java.util.TimeZone;
024
025/***
026 * The FTPFile class is used to represent information about files stored
027 * on an FTP server.
028 *
029 * @see FTPFileEntryParser
030 * @see FTPClient#listFiles
031 ***/
032
033public class FTPFile implements Serializable
034{
035    private static final long serialVersionUID = 9010790363003271996L;
036
037    /** A constant indicating an FTPFile is a file. ***/
038    public static final int FILE_TYPE = 0;
039    /** A constant indicating an FTPFile is a directory. ***/
040    public static final int DIRECTORY_TYPE = 1;
041    /** A constant indicating an FTPFile is a symbolic link. ***/
042    public static final int SYMBOLIC_LINK_TYPE = 2;
043    /** A constant indicating an FTPFile is of unknown type. ***/
044    public static final int UNKNOWN_TYPE = 3;
045
046    /** A constant indicating user access permissions. ***/
047    public static final int USER_ACCESS = 0;
048    /** A constant indicating group access permissions. ***/
049    public static final int GROUP_ACCESS = 1;
050    /** A constant indicating world access permissions. ***/
051    public static final int WORLD_ACCESS = 2;
052
053    /** A constant indicating file/directory read permission. ***/
054    public static final int READ_PERMISSION = 0;
055    /** A constant indicating file/directory write permission. ***/
056    public static final int WRITE_PERMISSION = 1;
057    /**
058     * A constant indicating file execute permission or directory listing
059     * permission.
060     ***/
061    public static final int EXECUTE_PERMISSION = 2;
062
063    private int _type, _hardLinkCount;
064    private long _size;
065    private String _rawListing, _user, _group, _name, _link;
066    private Calendar _date;
067    // If this is null, then list entry parsing failed
068    private final boolean[] _permissions[]; // e.g. _permissions[USER_ACCESS][READ_PERMISSION]
069
070    /*** Creates an empty FTPFile. ***/
071    public FTPFile()
072    {
073        _permissions = new boolean[3][3];
074        _type = UNKNOWN_TYPE;
075        // init these to values that do not occur in listings
076        // so can distinguish which fields are unset
077        _hardLinkCount = 0; // 0 is invalid as a link count
078        _size = -1; // 0 is valid, so use -1
079        _user = "";
080        _group = "";
081        _date = null;
082        _name = null;
083    }
084
085    /**
086     * Constructor for use by {@link FTPListParseEngine} only.
087     * Used to create FTPFile entries for failed parses
088     * @param rawListing line that could not be parsed.
089     * @since 3.4
090     */
091    FTPFile(String rawListing)
092    {
093        _permissions = null; // flag that entry is invalid
094        _rawListing = rawListing;
095        _type = UNKNOWN_TYPE;
096        // init these to values that do not occur in listings
097        // so can distinguish which fields are unset
098        _hardLinkCount = 0; // 0 is invalid as a link count
099        _size = -1; // 0 is valid, so use -1
100        _user = "";
101        _group = "";
102        _date = null;
103        _name = null;
104    }
105
106
107    /***
108     * Set the original FTP server raw listing from which the FTPFile was
109     * created.
110     *
111     * @param rawListing  The raw FTP server listing.
112     ***/
113    public void setRawListing(String rawListing)
114    {
115        _rawListing = rawListing;
116    }
117
118    /***
119     * Get the original FTP server raw listing used to initialize the FTPFile.
120     *
121     * @return The original FTP server raw listing used to initialize the
122     *         FTPFile.
123     ***/
124    public String getRawListing()
125    {
126        return _rawListing;
127    }
128
129
130    /***
131     * Determine if the file is a directory.
132     *
133     * @return True if the file is of type <code>DIRECTORY_TYPE</code>, false if
134     *         not.
135     ***/
136    public boolean isDirectory()
137    {
138        return (_type == DIRECTORY_TYPE);
139    }
140
141    /***
142     * Determine if the file is a regular file.
143     *
144     * @return True if the file is of type <code>FILE_TYPE</code>, false if
145     *         not.
146     ***/
147    public boolean isFile()
148    {
149        return (_type == FILE_TYPE);
150    }
151
152    /***
153     * Determine if the file is a symbolic link.
154     *
155     * @return True if the file is of type <code>UNKNOWN_TYPE</code>, false if
156     *         not.
157     ***/
158    public boolean isSymbolicLink()
159    {
160        return (_type == SYMBOLIC_LINK_TYPE);
161    }
162
163    /***
164     * Determine if the type of the file is unknown.
165     *
166     * @return True if the file is of type <code>UNKNOWN_TYPE</code>, false if
167     *         not.
168     ***/
169    public boolean isUnknown()
170    {
171        return (_type == UNKNOWN_TYPE);
172    }
173
174    /**
175     * Used to indicate whether an entry is valid or not.
176     * If the entry is invalid, only the {@link #getRawListing()} method will be useful.
177     * Other methods may fail.
178     *
179     * Used in conjunction with list parsing that preseverves entries that failed to parse.
180     * @see FTPClientConfig#setUnparseableEntries(boolean)
181     * @return true if the entry is valid
182     * @since 3.4
183     */
184    public boolean isValid() {
185        return (_permissions != null);
186    }
187
188    /***
189     * Set the type of the file (<code>DIRECTORY_TYPE</code>,
190     * <code>FILE_TYPE</code>, etc.).
191     *
192     * @param type  The integer code representing the type of the file.
193     ***/
194    public void setType(int type)
195    {
196        _type = type;
197    }
198
199
200    /***
201     * Return the type of the file (one of the <code>_TYPE</code> constants),
202     * e.g., if it is a directory, a regular file, or a symbolic link.
203     *
204     * @return The type of the file.
205     ***/
206    public int getType()
207    {
208        return _type;
209    }
210
211
212    /***
213     * Set the name of the file.
214     *
215     * @param name  The name of the file.
216     ***/
217    public void setName(String name)
218    {
219        _name = name;
220    }
221
222    /***
223     * Return the name of the file.
224     *
225     * @return The name of the file.
226     ***/
227    public String getName()
228    {
229        return _name;
230    }
231
232
233    /**
234     * Set the file size in bytes.
235     * @param size The file size in bytes.
236     */
237    public void setSize(long size)
238    {
239        _size = size;
240    }
241
242
243    /***
244     * Return the file size in bytes.
245     *
246     * @return The file size in bytes.
247     ***/
248    public long getSize()
249    {
250        return _size;
251    }
252
253
254    /***
255     * Set the number of hard links to this file.  This is not to be
256     * confused with symbolic links.
257     *
258     * @param links  The number of hard links to this file.
259     ***/
260    public void setHardLinkCount(int links)
261    {
262        _hardLinkCount = links;
263    }
264
265
266    /***
267     * Return the number of hard links to this file.  This is not to be
268     * confused with symbolic links.
269     *
270     * @return The number of hard links to this file.
271     ***/
272    public int getHardLinkCount()
273    {
274        return _hardLinkCount;
275    }
276
277
278    /***
279     * Set the name of the group owning the file.  This may be
280     * a string representation of the group number.
281     *
282     * @param group The name of the group owning the file.
283     ***/
284    public void setGroup(String group)
285    {
286        _group = group;
287    }
288
289
290    /***
291     * Returns the name of the group owning the file.  Sometimes this will be
292     * a string representation of the group number.
293     *
294     * @return The name of the group owning the file.
295     ***/
296    public String getGroup()
297    {
298        return _group;
299    }
300
301
302    /***
303     * Set the name of the user owning the file.  This may be
304     * a string representation of the user number;
305     *
306     * @param user The name of the user owning the file.
307     ***/
308    public void setUser(String user)
309    {
310        _user = user;
311    }
312
313    /***
314     * Returns the name of the user owning the file.  Sometimes this will be
315     * a string representation of the user number.
316     *
317     * @return The name of the user owning the file.
318     ***/
319    public String getUser()
320    {
321        return _user;
322    }
323
324
325    /***
326     * If the FTPFile is a symbolic link, use this method to set the name of the
327     * file being pointed to by the symbolic link.
328     *
329     * @param link  The file pointed to by the symbolic link.
330     ***/
331    public void setLink(String link)
332    {
333        _link = link;
334    }
335
336
337    /***
338     * If the FTPFile is a symbolic link, this method returns the name of the
339     * file being pointed to by the symbolic link.  Otherwise it returns null.
340     *
341     * @return The file pointed to by the symbolic link (null if the FTPFile
342     *         is not a symbolic link).
343     ***/
344    public String getLink()
345    {
346        return _link;
347    }
348
349
350    /***
351     * Set the file timestamp.  This usually the last modification time.
352     * The parameter is not cloned, so do not alter its value after calling
353     * this method.
354     *
355     * @param date A Calendar instance representing the file timestamp.
356     ***/
357    public void setTimestamp(Calendar date)
358    {
359        _date = date;
360    }
361
362
363    /***
364     * Returns the file timestamp.  This usually the last modification time.
365     *
366     * @return A Calendar instance representing the file timestamp.
367     ***/
368    public Calendar getTimestamp()
369    {
370        return _date;
371    }
372
373
374    /***
375     * Set if the given access group (one of the <code> _ACCESS </code>
376     * constants) has the given access permission (one of the
377     * <code> _PERMISSION </code> constants) to the file.
378     *
379     * @param access The access group (one of the <code> _ACCESS </code>
380     *               constants)
381     * @param permission The access permission (one of the
382     *               <code> _PERMISSION </code> constants)
383     * @param value  True if permission is allowed, false if not.
384     * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range
385     ***/
386    public void setPermission(int access, int permission, boolean value)
387    {
388        _permissions[access][permission] = value;
389    }
390
391
392    /***
393     * Determines if the given access group (one of the <code> _ACCESS </code>
394     * constants) has the given access permission (one of the
395     * <code> _PERMISSION </code> constants) to the file.
396     *
397     * @param access The access group (one of the <code> _ACCESS </code>
398     *               constants)
399     * @param permission The access permission (one of the
400     *               <code> _PERMISSION </code> constants)
401     * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range
402     * @return true if {@link #isValid()} is {@code true &&} the associated permission is set;
403     * {@code false} otherwise.
404     ***/
405    public boolean hasPermission(int access, int permission)
406    {
407        if (_permissions == null) {
408            return false;
409        }
410        return _permissions[access][permission];
411    }
412
413    /***
414     * Returns a string representation of the FTPFile information.
415     *
416     * @return A string representation of the FTPFile information.
417     */
418    @Override
419    public String toString()
420    {
421        return getRawListing();
422    }
423
424    /***
425     * Returns a string representation of the FTPFile information.
426     * This currently mimics the Unix listing format.
427     * This method uses the timezone of the Calendar entry, which is
428     * the server time zone (if one was provided) otherwise it is
429     * the local time zone.
430     * <p>
431     * Note: if the instance is not valid {@link #isValid()}, no useful
432     * information can be returned. In this case, use {@link #getRawListing()}
433     * instead.
434     *
435     * @return A string representation of the FTPFile information.
436     * @since 3.0
437     */
438    public String toFormattedString()
439    {
440        return toFormattedString(null);
441    }
442
443    /**
444     * Returns a string representation of the FTPFile information.
445     * This currently mimics the Unix listing format.
446     * This method allows the Calendar time zone to be overridden.
447     * <p>
448     * Note: if the instance is not valid {@link #isValid()}, no useful
449     * information can be returned. In this case, use {@link #getRawListing()}
450     * instead.
451     * @param timezone the timezone to use for displaying the time stamp
452     * If {@code null}, then use the Calendar entry timezone
453     * @return A string representation of the FTPFile information.
454     * @since 3.4
455     */
456    public String toFormattedString(final String timezone)
457    {
458
459        if (!isValid()) {
460            return "[Invalid: could not parse file entry]";
461        }
462        StringBuilder sb = new StringBuilder();
463        Formatter fmt = new Formatter(sb);
464        sb.append(formatType());
465        sb.append(permissionToString(USER_ACCESS));
466        sb.append(permissionToString(GROUP_ACCESS));
467        sb.append(permissionToString(WORLD_ACCESS));
468        fmt.format(" %4d", Integer.valueOf(getHardLinkCount()));
469        fmt.format(" %-8s %-8s", getUser(), getGroup());
470        fmt.format(" %8d", Long.valueOf(getSize()));
471        Calendar timestamp = getTimestamp();
472        if (timestamp != null) {
473            if (timezone != null) {
474                TimeZone newZone = TimeZone.getTimeZone(timezone);
475                if (!newZone.equals(timestamp.getTimeZone())){
476                    Date original = timestamp.getTime();
477                    Calendar newStamp = Calendar.getInstance(newZone);
478                    newStamp.setTime(original);
479                    timestamp = newStamp;
480                }
481            }
482            fmt.format(" %1$tY-%1$tm-%1$td", timestamp);
483            // Only display time units if they are present
484            if (timestamp.isSet(Calendar.HOUR_OF_DAY)) {
485                fmt.format(" %1$tH", timestamp);
486                if (timestamp.isSet(Calendar.MINUTE)) {
487                    fmt.format(":%1$tM", timestamp);
488                    if (timestamp.isSet(Calendar.SECOND)) {
489                        fmt.format(":%1$tS", timestamp);
490                        if (timestamp.isSet(Calendar.MILLISECOND)) {
491                            fmt.format(".%1$tL", timestamp);
492                        }
493                    }
494                }
495                fmt.format(" %1$tZ", timestamp);
496            }
497        }
498        sb.append(' ');
499        sb.append(getName());
500        fmt.close();
501        return sb.toString();
502    }
503
504    private char formatType(){
505        switch(_type) {
506            case FILE_TYPE:
507                return '-';
508            case DIRECTORY_TYPE:
509                return 'd';
510            case SYMBOLIC_LINK_TYPE:
511                return 'l';
512            default:
513                return '?';
514        }
515    }
516
517    private String permissionToString(int access ){
518        StringBuilder sb = new StringBuilder();
519        if (hasPermission(access, READ_PERMISSION)) {
520            sb.append('r');
521        } else {
522            sb.append('-');
523        }
524        if (hasPermission(access, WRITE_PERMISSION)) {
525            sb.append('w');
526        } else {
527            sb.append('-');
528        }
529        if (hasPermission(access, EXECUTE_PERMISSION)) {
530            sb.append('x');
531        } else {
532            sb.append('-');
533        }
534        return sb.toString();
535    }
536}