View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.ftp;
19  
20  import java.io.ObjectInputStream;
21  import java.io.ObjectOutputStream;
22  import java.io.Serializable;
23  import java.time.Instant;
24  import java.util.Calendar;
25  import java.util.Date;
26  import java.util.Formatter;
27  import java.util.TimeZone;
28  
29  /**
30   * The FTPFile class is used to represent information about files stored on an FTP server.
31   *
32   * @see FTPFileEntryParser
33   * @see FTPClient#listFiles
34   */
35  public class FTPFile implements Serializable {
36  
37      private static final long serialVersionUID = 9010790363003271996L;
38  
39      /** A constant indicating an FTPFile is a file. */
40      public static final int FILE_TYPE = 0;
41  
42      /** A constant indicating an FTPFile is a directory. */
43      public static final int DIRECTORY_TYPE = 1;
44  
45      /** A constant indicating an FTPFile is a symbolic link. */
46      public static final int SYMBOLIC_LINK_TYPE = 2;
47  
48      /** A constant indicating an FTPFile is of unknown type. */
49      public static final int UNKNOWN_TYPE = 3;
50  
51      /** A constant indicating user access permissions. */
52      public static final int USER_ACCESS = 0;
53  
54      /** A constant indicating group access permissions. */
55      public static final int GROUP_ACCESS = 1;
56  
57      /** A constant indicating world access permissions. */
58      public static final int WORLD_ACCESS = 2;
59  
60      /** A constant indicating file/directory read permission. */
61      public static final int READ_PERMISSION = 0;
62  
63      /** A constant indicating file/directory write permission. */
64      public static final int WRITE_PERMISSION = 1;
65  
66      /** A constant indicating file execute permission or directory listing permission. */
67      public static final int EXECUTE_PERMISSION = 2;
68  
69      /** Type. */
70      private int type = UNKNOWN_TYPE;
71  
72      /** 0 is invalid as a link count. */
73      private int hardLinkCount;
74  
75      /** 0 is valid, so use -1. */
76      private long size = -1;
77  
78      /** Line that could not be parsed. */
79      private String rawListing;
80  
81      /** User. */
82      private String user = "";
83  
84      /** Group. */
85      private String group = "";
86  
87      /** Name. */
88      private String name;
89  
90      /** Link. */
91      private String link;
92  
93      /** TODO Consider changing internal representation to java.time. */
94      private Calendar calendar;
95  
96      /** If this is null, then list entry parsing failed. */
97      private final boolean[][] permissions; // e.g. _permissions[USER_ACCESS][READ_PERMISSION]
98  
99      /** 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 }