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}