    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}