    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    public static final int EXECUTE_PERMISSION = 2;
    066
    067    private int type = UNKNOWN_TYPE;
    068
    069    /** 0 is invalid as a link count. */
    070    private int hardLinkCount;
    071
    072    /** 0 is valid, so use -1. */
    073    private long size = -1;
    074    private String rawListing;
    075    private String user = "";
    076    private String group = "";
    077    private String name;
    078    private String link;
    079
    080    // TODO Consider changing internal representation to java.time.
    081    private Calendar calendar;
    082
    083    /** If this is null, then list entry parsing failed. */
    084    private final boolean[][] permissions; // e.g. _permissions[USER_ACCESS][READ_PERMISSION]
    085
    086    /** Creates an empty FTPFile. */
    087    public FTPFile() {
    088        permissions = new boolean[3][3];
    089    }
    090
    091    /**
    092     * Constructor for use by {@link FTPListParseEngine} only. Used to create FTPFile entries for failed parses
    093     *
    094     * @param rawListing line that could not be parsed.
    095     * @since 3.4
    096     */
    097    FTPFile(final String rawListing) {
    098        this.permissions = null; // flag that entry is invalid
    099        this.rawListing = rawListing;
    100    }
    101
    102    private char formatType() {
    103        switch (type) {
    104        case FILE_TYPE:
    105            return '-';
    106        case DIRECTORY_TYPE:
    107            return 'd';
    108        case SYMBOLIC_LINK_TYPE:
    109            return 'l';
    110        default:
    111            return '?';
    112        }
    113    }
    114
    115    /**
    116     * Gets the name of the group owning the file. Sometimes this will be a string representation of the group number.
    117     *
    118     * @return The name of the group owning the file.
    119     */
    120    public String getGroup() {
    121        return group;
    122    }
    123
    124    /**
    125     * Gets the number of hard links to this file. This is not to be confused with symbolic links.
    126     *
    127     * @return The number of hard links to this file.
    128     */
    129    public int getHardLinkCount() {
    130        return hardLinkCount;
    131    }
    132
    133    /**
    134     * If the FTPFile is a symbolic link, this method returns the name of the file being pointed to by the symbolic link.
    135     * Otherwise, it returns {@code null}.
    136     *
    137     * @return The file pointed to by the symbolic link ({@code 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} 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} constants) has the given access permission (one of the {@code _PERMISSION}
    209     * constants) to the file.
    210     *
    211     * @param access     The access group (one of the {@code _ACCESS} constants)
    212     * @param permission The access permission (one of the {@code _PERMISSION} constants)
    213     * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range
    214     * @return {@code true} if {@link #isValid()} is {@code true} and 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 {@code 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 {@code 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 {@code true} if the file is of type {@code SYMBOLIC_LINK_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 {@code 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 preserves entries that failed to parse.
    263     *
    264     * @see FTPClientConfig#setUnparseableEntries(boolean)
    265     * @return {@code true} if the entry is valid; {@code false} otherwise
    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} constants) has the given access permission (one of the {@code _PERMISSION}
    334     * constants) to the file.
    335     *
    336     * @param access     The access group (one of the {@code _ACCESS} constants)
    337     * @param permission The access permission (one of the {@code _PERMISSION} constants)
    338     * @param value      {@code true} if permission is allowed, {@code 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        // TODO: only allow permission setting if file is valid
    343        permissions[access][permission] = value;
    344    }
    345
    346    /**
    347     * Sets the original FTP server raw listing from which the FTPFile was created.
    348     *
    349     * @param rawListing The raw FTP server listing.
    350     */
    351    public void setRawListing(final String rawListing) {
    352        this.rawListing = rawListing;
    353    }
    354
    355    /**
    356     * Sets the file size in bytes.
    357     *
    358     * @param size The file size in bytes.
    359     */
    360    public void setSize(final long size) {
    361        this.size = size;
    362    }
    363
    364    /**
    365     * 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.
    366     *
    367     * @param date A Calendar instance representing the file timestamp.
    368     */
    369    public void setTimestamp(final Calendar date) {
    370        this.calendar = date;
    371    }
    372
    373    /**
    374     * Sets the type of the file ({@code DIRECTORY_TYPE}, {@code FILE_TYPE}, etc.).
    375     *
    376     * @param type The integer code representing the type of the file.
    377     */
    378    public void setType(final int type) {
    379        this.type = type;
    380    }
    381
    382    /**
    383     * Sets the name of the user owning the file. This may be a string representation of the user number;
    384     *
    385     * @param user The name of the user owning the file.
    386     */
    387    public void setUser(final String user) {
    388        this.user = user;
    389    }
    390
    391    /**
    392     * Gets a string representation of the FTPFile information. This currently mimics the Unix listing format. This method uses the time zone of the Calendar
    393     * entry, which is the server time zone (if one was provided) otherwise it is the local time zone.
    394     * <p>
    395     * Note: if the instance is not valid {@link #isValid()}, no useful information can be returned. In this case, use {@link #getRawListing()} instead.
    396     * </p>
    397     *
    398     * @return A string representation of the FTPFile information.
    399     * @since 3.0
    400     */
    401    public String toFormattedString() {
    402        return toFormattedString(null);
    403    }
    404
    405    /**
    406     * Gets a string representation of the FTPFile information. This currently mimics the Unix listing format. This method allows the Calendar time zone to be
    407     * overridden.
    408     * <p>
    409     * Note: if the instance is not valid {@link #isValid()}, no useful information can be returned. In this case, use {@link #getRawListing()} instead.
    410     * </p>
    411     *
    412     * @param timezone the time zone to use for displaying the time stamp If {@code null}, then use the Calendar ({@link #getTimestamp()}) entry
    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     * Delegates to {@link #getRawListing()}
    470     *
    471     * @see #getRawListing()
    472     * @return A string representation of the FTPFile information.
    473     */
    474    @Override
    475    public String toString() {
    476        return getRawListing();
    477    }
    478
    479    private void writeObject(final java.io.ObjectOutputStream out) {
    480        throw new UnsupportedOperationException("Serialization is not supported");
    481    }
    482
    483}