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