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 }