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(final String rawListing)
092    {
093        this.permissions = null; // flag that entry is invalid
094        this.rawListing = rawListing;
095        this.type = UNKNOWN_TYPE;
096        // init these to values that do not occur in listings
097        // so can distinguish which fields are unset
098        this.hardLinkCount = 0; // 0 is invalid as a link count
099        this.size = -1; // 0 is valid, so use -1
100        this.user = "";
101        this.group = "";
102        this.date = null;
103        this.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(final String rawListing)
114    {
115        this.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(final int type)
195    {
196        this.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(final String name)
218    {
219        this.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(final long size)
238    {
239        this.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(final int links)
261    {
262        this.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(final String group)
285    {
286        this.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(final String user)
309    {
310        this.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(final String link)
332    {
333        this.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(final Calendar date)
358    {
359        this.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(final int access, final int permission, final 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(final int access, final 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        final StringBuilder sb = new StringBuilder();
463        try (final 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                    final TimeZone newZone = TimeZone.getTimeZone(timezone);
475                    if (!newZone.equals(timestamp.getTimeZone())) {
476                        final Date original = timestamp.getTime();
477                        final 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        }
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(final int access ){
518        final 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}