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