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 }