001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.net.ftp; 019 020import java.io.Serializable; 021import java.time.Instant; 022import java.util.Calendar; 023import java.util.Date; 024import java.util.Formatter; 025import java.util.TimeZone; 026 027/** 028 * The FTPFile class is used to represent information about files stored on an FTP server. 029 * 030 * @see FTPFileEntryParser 031 * @see FTPClient#listFiles 032 */ 033public class FTPFile implements Serializable { 034 035 private static final long serialVersionUID = 9010790363003271996L; 036 037 /** A constant indicating an FTPFile is a file. */ 038 public static final int FILE_TYPE = 0; 039 040 /** A constant indicating an FTPFile is a directory. */ 041 public static final int DIRECTORY_TYPE = 1; 042 043 /** A constant indicating an FTPFile is a symbolic link. */ 044 public static final int SYMBOLIC_LINK_TYPE = 2; 045 046 /** A constant indicating an FTPFile is of unknown type. */ 047 public static final int UNKNOWN_TYPE = 3; 048 049 /** A constant indicating user access permissions. */ 050 public static final int USER_ACCESS = 0; 051 052 /** A constant indicating group access permissions. */ 053 public static final int GROUP_ACCESS = 1; 054 055 /** A constant indicating world access permissions. */ 056 public static final int WORLD_ACCESS = 2; 057 058 /** A constant indicating file/directory read permission. */ 059 public static final int READ_PERMISSION = 0; 060 061 /** A constant indicating file/directory write permission. */ 062 public static final int WRITE_PERMISSION = 1; 063 /** 064 * A constant indicating file execute permission or directory listing permission. 065 */ 066 public static final int EXECUTE_PERMISSION = 2; 067 068 private int type = UNKNOWN_TYPE; 069 070 /** 0 is invalid as a link count. */ 071 private int hardLinkCount; 072 073 /** 0 is valid, so use -1. */ 074 private long size = -1; 075 private String rawListing; 076 private String user = ""; 077 private String group = ""; 078 private String name; 079 private String link; 080 081 // TODO Consider changing internal representation to java.time. 082 private Calendar calendar; 083 084 /** If this is null, then list entry parsing failed. */ 085 private final boolean[][] permissions; // e.g. _permissions[USER_ACCESS][READ_PERMISSION] 086 087 /** Creates an empty FTPFile. */ 088 public FTPFile() { 089 permissions = new boolean[3][3]; 090 } 091 092 /** 093 * Constructor for use by {@link FTPListParseEngine} only. Used to create FTPFile entries for failed parses 094 * 095 * @param rawListing line that could not be parsed. 096 * @since 3.4 097 */ 098 FTPFile(final String rawListing) { 099 this.permissions = null; // flag that entry is invalid 100 this.rawListing = rawListing; 101 } 102 103 private char formatType() { 104 switch (type) { 105 case FILE_TYPE: 106 return '-'; 107 case DIRECTORY_TYPE: 108 return 'd'; 109 case SYMBOLIC_LINK_TYPE: 110 return 'l'; 111 default: 112 return '?'; 113 } 114 } 115 116 /** 117 * Gets the name of the group owning the file. Sometimes this will be a string representation of the group number. 118 * 119 * @return The name of the group owning the file. 120 */ 121 public String getGroup() { 122 return group; 123 } 124 125 /** 126 * Gets the number of hard links to this file. This is not to be confused with symbolic links. 127 * 128 * @return The number of hard links to this file. 129 */ 130 public int getHardLinkCount() { 131 return hardLinkCount; 132 } 133 134 /** 135 * If the FTPFile is a symbolic link, this method returns the name of the file being pointed to by the symbolic link. Otherwise it returns null. 136 * 137 * @return The file pointed to by the symbolic link (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</code> 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 </code> constants) has the given access permission (one of the <code> _PERMISSION </code> 209 * constants) to the file. 210 * 211 * @param access The access group (one of the <code> _ACCESS </code> constants) 212 * @param permission The access permission (one of the <code> _PERMISSION </code> constants) 213 * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range 214 * @return true if {@link #isValid()} is {@code true &&} 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 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 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 True if the file is of type <code>UNKNOWN_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 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 preseverves entries that failed to parse. 263 * 264 * @see FTPClientConfig#setUnparseableEntries(boolean) 265 * @return true if the entry is valid 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 </code> constants) has the given access permission (one of the <code> _PERMISSION </code> 334 * constants) to the file. 335 * 336 * @param access The access group (one of the <code> _ACCESS </code> constants) 337 * @param permission The access permission (one of the <code> _PERMISSION </code> constants) 338 * @param value True if permission is allowed, 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 permissions[access][permission] = value; 343 } 344 345 /** 346 * Sets the original FTP server raw listing from which the FTPFile was created. 347 * 348 * @param rawListing The raw FTP server listing. 349 */ 350 public void setRawListing(final String rawListing) { 351 this.rawListing = rawListing; 352 } 353 354 /** 355 * Sets the file size in bytes. 356 * 357 * @param size The file size in bytes. 358 */ 359 public void setSize(final long size) { 360 this.size = size; 361 } 362 363 /** 364 * 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. 365 * 366 * @param date A Calendar instance representing the file timestamp. 367 */ 368 public void setTimestamp(final Calendar date) { 369 this.calendar = date; 370 } 371 372 /** 373 * Sets the type of the file (<code>DIRECTORY_TYPE</code>, <code>FILE_TYPE</code>, etc.). 374 * 375 * @param type The integer code representing the type of the file. 376 */ 377 public void setType(final int type) { 378 this.type = type; 379 } 380 381 /** 382 * Sets the name of the user owning the file. This may be a string representation of the user number; 383 * 384 * @param user The name of the user owning the file. 385 */ 386 public void setUser(final String user) { 387 this.user = user; 388 } 389 390 /** 391 * Gets a string representation of the FTPFile information. This currently mimics the Unix listing format. This method uses the time zone of the Calendar 392 * entry, which is the server time zone (if one was provided) otherwise it is the local time zone. 393 * <p> 394 * Note: if the instance is not valid {@link #isValid()}, no useful information can be returned. In this case, use {@link #getRawListing()} instead. 395 * </p> 396 * 397 * @return A string representation of the FTPFile information. 398 * @since 3.0 399 */ 400 public String toFormattedString() { 401 return toFormattedString(null); 402 } 403 404 /** 405 * Gets a string representation of the FTPFile information. This currently mimics the Unix listing format. This method allows the Calendar time zone to be 406 * overridden. 407 * <p> 408 * Note: if the instance is not valid {@link #isValid()}, no useful information can be returned. In this case, use {@link #getRawListing()} instead. 409 * </p> 410 * 411 * @param timezone the time zone to use for displaying the time stamp If {@code null}, then use the Calendar entry 412 * 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 * 470 * @return A string representation of the FTPFile information. 471 */ 472 @Override 473 public String toString() { 474 return getRawListing(); 475 } 476 477 private void writeObject(final java.io.ObjectOutputStream out) { 478 throw new UnsupportedOperationException("Serialization is not supported"); 479 } 480 481}