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 }