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