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    *      http://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.Serializable;
21  import java.time.Instant;
22  import java.util.Calendar;
23  import java.util.Date;
24  import java.util.Formatter;
25  import java.util.TimeZone;
26  
27  /**
28   * The FTPFile class is used to represent information about files stored on an FTP server.
29   *
30   * @see FTPFileEntryParser
31   * @see FTPClient#listFiles
32   */
33  public class FTPFile implements Serializable {
34  
35      private static final long serialVersionUID = 9010790363003271996L;
36  
37      /** A constant indicating an FTPFile is a file. */
38      public static final int FILE_TYPE = 0;
39  
40      /** A constant indicating an FTPFile is a directory. */
41      public static final int DIRECTORY_TYPE = 1;
42  
43      /** A constant indicating an FTPFile is a symbolic link. */
44      public static final int SYMBOLIC_LINK_TYPE = 2;
45  
46      /** A constant indicating an FTPFile is of unknown type. */
47      public static final int UNKNOWN_TYPE = 3;
48  
49      /** A constant indicating user access permissions. */
50      public static final int USER_ACCESS = 0;
51  
52      /** A constant indicating group access permissions. */
53      public static final int GROUP_ACCESS = 1;
54  
55      /** A constant indicating world access permissions. */
56      public static final int WORLD_ACCESS = 2;
57  
58      /** A constant indicating file/directory read permission. */
59      public static final int READ_PERMISSION = 0;
60  
61      /** A constant indicating file/directory write permission. */
62      public static final int WRITE_PERMISSION = 1;
63  
64      /** A constant indicating file execute permission or directory listing permission. */
65      public static final int EXECUTE_PERMISSION = 2;
66  
67      private int type = UNKNOWN_TYPE;
68  
69      /** 0 is invalid as a link count. */
70      private int hardLinkCount;
71  
72      /** 0 is valid, so use -1. */
73      private long size = -1;
74      private String rawListing;
75      private String user = "";
76      private String group = "";
77      private String name;
78      private String link;
79  
80      // TODO Consider changing internal representation to java.time.
81      private Calendar calendar;
82  
83      /** If this is null, then list entry parsing failed. */
84      private final boolean[][] permissions; // e.g. _permissions[USER_ACCESS][READ_PERMISSION]
85  
86      /** Creates an empty FTPFile. */
87      public FTPFile() {
88          permissions = new boolean[3][3];
89      }
90  
91      /**
92       * Constructor for use by {@link FTPListParseEngine} only. Used to create FTPFile entries for failed parses
93       *
94       * @param rawListing line that could not be parsed.
95       * @since 3.4
96       */
97      FTPFile(final String rawListing) {
98          this.permissions = null; // flag that entry is invalid
99          this.rawListing = rawListing;
100     }
101 
102     private char formatType() {
103         switch (type) {
104         case FILE_TYPE:
105             return '-';
106         case DIRECTORY_TYPE:
107             return 'd';
108         case SYMBOLIC_LINK_TYPE:
109             return 'l';
110         default:
111             return '?';
112         }
113     }
114 
115     /**
116      * Gets the name of the group owning the file. Sometimes this will be a string representation of the group number.
117      *
118      * @return The name of the group owning the file.
119      */
120     public String getGroup() {
121         return group;
122     }
123 
124     /**
125      * Gets the number of hard links to this file. This is not to be confused with symbolic links.
126      *
127      * @return The number of hard links to this file.
128      */
129     public int getHardLinkCount() {
130         return hardLinkCount;
131     }
132 
133     /**
134      * If the FTPFile is a symbolic link, this method returns the name of the file being pointed to by the symbolic link.
135      * Otherwise, it returns {@code null}.
136      *
137      * @return The file pointed to by the symbolic link ({@code 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} 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} constants) has the given access permission (one of the {@code _PERMISSION}
209      * constants) to the file.
210      *
211      * @param access     The access group (one of the {@code _ACCESS} constants)
212      * @param permission The access permission (one of the {@code _PERMISSION} constants)
213      * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range
214      * @return {@code true} if {@link #isValid()} is {@code true} and 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 {@code 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 {@code 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 {@code true} if the file is of type {@code SYMBOLIC_LINK_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 {@code 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 preserves entries that failed to parse.
263      *
264      * @see FTPClientConfig#setUnparseableEntries(boolean)
265      * @return {@code true} if the entry is valid; {@code false} otherwise
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} constants) has the given access permission (one of the {@code _PERMISSION}
334      * constants) to the file.
335      *
336      * @param access     The access group (one of the {@code _ACCESS} constants)
337      * @param permission The access permission (one of the {@code _PERMISSION} constants)
338      * @param value      {@code true} if permission is allowed, {@code 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         // TODO: only allow permission setting if file is valid
343         permissions[access][permission] = value;
344     }
345 
346     /**
347      * Sets the original FTP server raw listing from which the FTPFile was created.
348      *
349      * @param rawListing The raw FTP server listing.
350      */
351     public void setRawListing(final String rawListing) {
352         this.rawListing = rawListing;
353     }
354 
355     /**
356      * Sets the file size in bytes.
357      *
358      * @param size The file size in bytes.
359      */
360     public void setSize(final long size) {
361         this.size = size;
362     }
363 
364     /**
365      * 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.
366      *
367      * @param date A Calendar instance representing the file timestamp.
368      */
369     public void setTimestamp(final Calendar date) {
370         this.calendar = date;
371     }
372 
373     /**
374      * Sets the type of the file ({@code DIRECTORY_TYPE}, {@code FILE_TYPE}, etc.).
375      *
376      * @param type The integer code representing the type of the file.
377      */
378     public void setType(final int type) {
379         this.type = type;
380     }
381 
382     /**
383      * Sets the name of the user owning the file. This may be a string representation of the user number;
384      *
385      * @param user The name of the user owning the file.
386      */
387     public void setUser(final String user) {
388         this.user = user;
389     }
390 
391     /**
392      * Gets a string representation of the FTPFile information. This currently mimics the Unix listing format. This method uses the time zone of the Calendar
393      * entry, which is the server time zone (if one was provided) otherwise it is the local time zone.
394      * <p>
395      * Note: if the instance is not valid {@link #isValid()}, no useful information can be returned. In this case, use {@link #getRawListing()} instead.
396      * </p>
397      *
398      * @return A string representation of the FTPFile information.
399      * @since 3.0
400      */
401     public String toFormattedString() {
402         return toFormattedString(null);
403     }
404 
405     /**
406      * Gets a string representation of the FTPFile information. This currently mimics the Unix listing format. This method allows the Calendar time zone to be
407      * overridden.
408      * <p>
409      * Note: if the instance is not valid {@link #isValid()}, no useful information can be returned. In this case, use {@link #getRawListing()} instead.
410      * </p>
411      *
412      * @param timezone the time zone to use for displaying the time stamp If {@code null}, then use the Calendar ({@link #getTimestamp()}) entry
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      * Delegates to {@link #getRawListing()}
470      *
471      * @see #getRawListing()
472      * @return A string representation of the FTPFile information.
473      */
474     @Override
475     public String toString() {
476         return getRawListing();
477     }
478 
479     private void writeObject(final java.io.ObjectOutputStream out) {
480         throw new UnsupportedOperationException("Serialization is not supported");
481     }
482 
483 }