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}