FTPFile.java

  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. package org.apache.commons.net.ftp;

  18. import java.io.ObjectInputStream;
  19. import java.io.ObjectOutputStream;
  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.  * The FTPFile class is used to represent information about files stored on an FTP server.
  28.  *
  29.  * @see FTPFileEntryParser
  30.  * @see FTPClient#listFiles
  31.  */
  32. public class FTPFile implements Serializable {

  33.     private static final long serialVersionUID = 9010790363003271996L;

  34.     /** A constant indicating an FTPFile is a file. */
  35.     public static final int FILE_TYPE = 0;

  36.     /** A constant indicating an FTPFile is a directory. */
  37.     public static final int DIRECTORY_TYPE = 1;

  38.     /** A constant indicating an FTPFile is a symbolic link. */
  39.     public static final int SYMBOLIC_LINK_TYPE = 2;

  40.     /** A constant indicating an FTPFile is of unknown type. */
  41.     public static final int UNKNOWN_TYPE = 3;

  42.     /** A constant indicating user access permissions. */
  43.     public static final int USER_ACCESS = 0;

  44.     /** A constant indicating group access permissions. */
  45.     public static final int GROUP_ACCESS = 1;

  46.     /** A constant indicating world access permissions. */
  47.     public static final int WORLD_ACCESS = 2;

  48.     /** A constant indicating file/directory read permission. */
  49.     public static final int READ_PERMISSION = 0;

  50.     /** A constant indicating file/directory write permission. */
  51.     public static final int WRITE_PERMISSION = 1;

  52.     /** A constant indicating file execute permission or directory listing permission. */
  53.     public static final int EXECUTE_PERMISSION = 2;

  54.     /** Type. */
  55.     private int type = UNKNOWN_TYPE;

  56.     /** 0 is invalid as a link count. */
  57.     private int hardLinkCount;

  58.     /** 0 is valid, so use -1. */
  59.     private long size = -1;

  60.     /** Line that could not be parsed. */
  61.     private String rawListing;

  62.     /** User. */
  63.     private String user = "";

  64.     /** Group. */
  65.     private String group = "";

  66.     /** Name. */
  67.     private String name;

  68.     /** Link. */
  69.     private String link;

  70.     /** TODO Consider changing internal representation to java.time. */
  71.     private Calendar calendar;

  72.     /** If this is null, then list entry parsing failed. */
  73.     private final boolean[][] permissions; // e.g. _permissions[USER_ACCESS][READ_PERMISSION]

  74.     /** Creates an empty FTPFile. */
  75.     public FTPFile() {
  76.         permissions = new boolean[3][3];
  77.     }

  78.     /**
  79.      * Constructor for use by {@link FTPListParseEngine} only. Used to create FTPFile entries for failed parses.
  80.      *
  81.      * @param rawListing line that could not be parsed.
  82.      * @since 3.4
  83.      */
  84.     FTPFile(final String rawListing) {
  85.         this.permissions = null; // flag that entry is invalid
  86.         this.rawListing = rawListing;
  87.     }

  88.     private char formatType() {
  89.         switch (type) {
  90.         case FILE_TYPE:
  91.             return '-';
  92.         case DIRECTORY_TYPE:
  93.             return 'd';
  94.         case SYMBOLIC_LINK_TYPE:
  95.             return 'l';
  96.         default:
  97.             return '?';
  98.         }
  99.     }

  100.     /**
  101.      * Gets the name of the group owning the file. Sometimes this will be a string representation of the group number.
  102.      *
  103.      * @return The name of the group owning the file.
  104.      */
  105.     public String getGroup() {
  106.         return group;
  107.     }

  108.     /**
  109.      * Gets the number of hard links to this file. This is not to be confused with symbolic links.
  110.      *
  111.      * @return The number of hard links to this file.
  112.      */
  113.     public int getHardLinkCount() {
  114.         return hardLinkCount;
  115.     }

  116.     /**
  117.      * If the FTPFile is a symbolic link, this method returns the name of the file being pointed to by the symbolic link.
  118.      * Otherwise, it returns {@code null}.
  119.      *
  120.      * @return The file pointed to by the symbolic link ({@code null} if the FTPFile is not a symbolic link).
  121.      */
  122.     public String getLink() {
  123.         return link;
  124.     }

  125.     /**
  126.      * Gets the name of the file.
  127.      *
  128.      * @return The name of the file.
  129.      */
  130.     public String getName() {
  131.         return name;
  132.     }

  133.     /**
  134.      * Gets the original FTP server raw listing used to initialize the FTPFile.
  135.      *
  136.      * @return The original FTP server raw listing used to initialize the FTPFile.
  137.      */
  138.     public String getRawListing() {
  139.         return rawListing;
  140.     }

  141.     /**
  142.      * Gets the file size in bytes.
  143.      *
  144.      * @return The file size in bytes.
  145.      */
  146.     public long getSize() {
  147.         return size;
  148.     }

  149.     /**
  150.      * Gets the file timestamp. This usually the last modification time.
  151.      *
  152.      * @return A Calendar instance representing the file timestamp.
  153.      */
  154.     public Calendar getTimestamp() {
  155.         return calendar;
  156.     }

  157.     /**
  158.      * Gets the file timestamp. This usually the last modification time.
  159.      *
  160.      * @return A Calendar instance representing the file timestamp.
  161.      * @since 3.9.0
  162.      */
  163.     public Instant getTimestampInstant() {
  164.         return calendar == null ? null : calendar.toInstant();
  165.     }

  166.     /**
  167.      * 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.
  168.      *
  169.      * @return The type of the file.
  170.      */
  171.     public int getType() {
  172.         return type;
  173.     }

  174.     /**
  175.      * Gets the name of the user owning the file. Sometimes this will be a string representation of the user number.
  176.      *
  177.      * @return The name of the user owning the file.
  178.      */
  179.     public String getUser() {
  180.         return user;
  181.     }

  182.     /**
  183.      * Tests if the given access group (one of the {@code _ACCESS} constants) has the given access permission (one of the {@code _PERMISSION}
  184.      * constants) to the file.
  185.      *
  186.      * @param access     The access group (one of the {@code _ACCESS} constants)
  187.      * @param permission The access permission (one of the {@code _PERMISSION} constants)
  188.      * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range
  189.      * @return {@code true} if {@link #isValid()} is {@code true} and the associated permission is set; {@code false} otherwise.
  190.      */
  191.     public boolean hasPermission(final int access, final int permission) {
  192.         if (permissions == null) {
  193.             return false;
  194.         }
  195.         return permissions[access][permission];
  196.     }

  197.     /**
  198.      * Tests if the file is a directory.
  199.      *
  200.      * @return {@code true} if the file is of type {@code DIRECTORY_TYPE}, {@code false} if not.
  201.      */
  202.     public boolean isDirectory() {
  203.         return type == DIRECTORY_TYPE;
  204.     }

  205.     /**
  206.      * Tests if the file is a regular file.
  207.      *
  208.      * @return {@code true} if the file is of type {@code FILE_TYPE}, {@code false} if not.
  209.      */
  210.     public boolean isFile() {
  211.         return type == FILE_TYPE;
  212.     }

  213.     /**
  214.      * Tests if the file is a symbolic link.
  215.      *
  216.      * @return {@code true} if the file is of type {@code SYMBOLIC_LINK_TYPE}, {@code false} if not.
  217.      */
  218.     public boolean isSymbolicLink() {
  219.         return type == SYMBOLIC_LINK_TYPE;
  220.     }

  221.     /**
  222.      * Tests if the type of the file is unknown.
  223.      *
  224.      * @return {@code true} if the file is of type {@code UNKNOWN_TYPE}, {@code false} if not.
  225.      */
  226.     public boolean isUnknown() {
  227.         return type == UNKNOWN_TYPE;
  228.     }

  229.     /**
  230.      * 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.
  231.      *
  232.      * Used in conjunction with list parsing that preserves entries that failed to parse.
  233.      *
  234.      * @see FTPClientConfig#setUnparseableEntries(boolean)
  235.      * @return {@code true} if the entry is valid; {@code false} otherwise
  236.      * @since 3.4
  237.      */
  238.     public boolean isValid() {
  239.         return permissions != null;
  240.     }

  241.     private String permissionToString(final int access) {
  242.         final StringBuilder sb = new StringBuilder(3);
  243.         if (hasPermission(access, READ_PERMISSION)) {
  244.             sb.append('r');
  245.         } else {
  246.             sb.append('-');
  247.         }
  248.         if (hasPermission(access, WRITE_PERMISSION)) {
  249.             sb.append('w');
  250.         } else {
  251.             sb.append('-');
  252.         }
  253.         if (hasPermission(access, EXECUTE_PERMISSION)) {
  254.             sb.append('x');
  255.         } else {
  256.             sb.append('-');
  257.         }
  258.         return sb.toString();
  259.     }

  260.     /**
  261.      * Throws UnsupportedOperationException.
  262.      *
  263.      * @param ignored Ignored.
  264.      */
  265.     private void readObject(final ObjectInputStream ignored) {
  266.         throw new UnsupportedOperationException("Serialization is not supported");
  267.     }

  268.     /**
  269.      * Sets the name of the group owning the file. This may be a string representation of the group number.
  270.      *
  271.      * @param group The name of the group owning the file.
  272.      */
  273.     public void setGroup(final String group) {
  274.         this.group = group;
  275.     }

  276.     /**
  277.      * Sets the number of hard links to this file. This is not to be confused with symbolic links.
  278.      *
  279.      * @param hardLinkCount The number of hard links to this file.
  280.      */
  281.     public void setHardLinkCount(final int hardLinkCount) {
  282.         this.hardLinkCount = hardLinkCount;
  283.     }

  284.     /**
  285.      * If the FTPFile is a symbolic link, use this method to set the name of the file being pointed to by the symbolic link.
  286.      *
  287.      * @param link The file pointed to by the symbolic link.
  288.      */
  289.     public void setLink(final String link) {
  290.         this.link = link;
  291.     }

  292.     /**
  293.      * Sets the name of the file.
  294.      *
  295.      * @param name The name of the file.
  296.      */
  297.     public void setName(final String name) {
  298.         this.name = name;
  299.     }

  300.     /**
  301.      * Sets if the given access group (one of the {@code _ACCESS} constants) has the given access permission (one of the {@code _PERMISSION}
  302.      * constants) to the file.
  303.      *
  304.      * @param access     The access group (one of the {@code _ACCESS} constants)
  305.      * @param permission The access permission (one of the {@code _PERMISSION} constants)
  306.      * @param value      {@code true} if permission is allowed, {@code false} if not.
  307.      * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range
  308.      */
  309.     public void setPermission(final int access, final int permission, final boolean value) {
  310.         // TODO: only allow permission setting if file is valid
  311.         permissions[access][permission] = value;
  312.     }

  313.     /**
  314.      * Sets the original FTP server raw listing from which the FTPFile was created.
  315.      *
  316.      * @param rawListing The raw FTP server listing.
  317.      */
  318.     public void setRawListing(final String rawListing) {
  319.         this.rawListing = rawListing;
  320.     }

  321.     /**
  322.      * Sets the file size in bytes.
  323.      *
  324.      * @param size The file size in bytes.
  325.      */
  326.     public void setSize(final long size) {
  327.         this.size = size;
  328.     }

  329.     /**
  330.      * 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.
  331.      *
  332.      * @param calendar A Calendar instance representing the file timestamp.
  333.      */
  334.     public void setTimestamp(final Calendar calendar) {
  335.         this.calendar = calendar;
  336.     }

  337.     /**
  338.      * Sets the type of the file ({@code DIRECTORY_TYPE}, {@code FILE_TYPE}, etc.).
  339.      *
  340.      * @param type The integer code representing the type of the file.
  341.      */
  342.     public void setType(final int type) {
  343.         this.type = type;
  344.     }

  345.     /**
  346.      * Sets the name of the user owning the file. This may be a string representation of the user number;
  347.      *
  348.      * @param user The name of the user owning the file.
  349.      */
  350.     public void setUser(final String user) {
  351.         this.user = user;
  352.     }

  353.     /**
  354.      * Gets a string representation of the FTPFile information. This currently mimics the Unix listing format. This method uses the time zone of the Calendar
  355.      * entry, which is the server time zone (if one was provided) otherwise it is the local time zone.
  356.      * <p>
  357.      * Note: if the instance is not valid {@link #isValid()}, no useful information can be returned. In this case, use {@link #getRawListing()} instead.
  358.      * </p>
  359.      *
  360.      * @return A string representation of the FTPFile information.
  361.      * @since 3.0
  362.      */
  363.     public String toFormattedString() {
  364.         return toFormattedString(null);
  365.     }

  366.     /**
  367.      * Gets a string representation of the FTPFile information. This currently mimics the Unix listing format. This method allows the Calendar time zone to be
  368.      * overridden.
  369.      * <p>
  370.      * Note: if the instance is not valid {@link #isValid()}, no useful information can be returned. In this case, use {@link #getRawListing()} instead.
  371.      * </p>
  372.      *
  373.      * @param timezone the time zone to use for displaying the time stamp If {@code null}, then use the Calendar ({@link #getTimestamp()}) entry
  374.      * @return A string representation of the FTPFile information.
  375.      * @since 3.4
  376.      */
  377.     public String toFormattedString(final String timezone) {

  378.         if (!isValid()) {
  379.             return "[Invalid: could not parse file entry]";
  380.         }
  381.         final StringBuilder sb = new StringBuilder();
  382.         try (Formatter fmt = new Formatter(sb)) {
  383.             sb.append(formatType());
  384.             sb.append(permissionToString(USER_ACCESS));
  385.             sb.append(permissionToString(GROUP_ACCESS));
  386.             sb.append(permissionToString(WORLD_ACCESS));
  387.             fmt.format(" %4d", Integer.valueOf(getHardLinkCount()));
  388.             fmt.format(" %-8s %-8s", getUser(), getGroup());
  389.             fmt.format(" %8d", Long.valueOf(getSize()));
  390.             Calendar timestamp = getTimestamp();
  391.             if (timestamp != null) {
  392.                 if (timezone != null) {
  393.                     final TimeZone newZone = TimeZone.getTimeZone(timezone);
  394.                     if (!newZone.equals(timestamp.getTimeZone())) {
  395.                         final Date original = timestamp.getTime();
  396.                         final Calendar newStamp = Calendar.getInstance(newZone);
  397.                         newStamp.setTime(original);
  398.                         timestamp = newStamp;
  399.                     }
  400.                 }
  401.                 fmt.format(" %1$tY-%1$tm-%1$td", timestamp);
  402.                 // Only display time units if they are present
  403.                 if (timestamp.isSet(Calendar.HOUR_OF_DAY)) {
  404.                     fmt.format(" %1$tH", timestamp);
  405.                     if (timestamp.isSet(Calendar.MINUTE)) {
  406.                         fmt.format(":%1$tM", timestamp);
  407.                         if (timestamp.isSet(Calendar.SECOND)) {
  408.                             fmt.format(":%1$tS", timestamp);
  409.                             if (timestamp.isSet(Calendar.MILLISECOND)) {
  410.                                 fmt.format(".%1$tL", timestamp);
  411.                             }
  412.                         }
  413.                     }
  414.                     fmt.format(" %1$tZ", timestamp);
  415.                 }
  416.             }
  417.             sb.append(' ');
  418.             sb.append(getName());
  419.         }
  420.         return sb.toString();
  421.     }

  422.     /*
  423.      * Serialization is unnecessary for this class. Reject attempts to do so until such time as the Serializable attribute can be dropped.
  424.      */

  425.     /**
  426.      * Gets a string representation of the FTPFile information.
  427.      * Delegates to {@link #getRawListing()}
  428.      *
  429.      * @see #getRawListing()
  430.      * @return A string representation of the FTPFile information.
  431.      */
  432.     @Override
  433.     public String toString() {
  434.         return getRawListing();
  435.     }

  436.     /**
  437.      * Always throws {@link UnsupportedOperationException}.
  438.      *
  439.      * @param ignored ignored.
  440.      * @throws UnsupportedOperationException Always thrown.
  441.      */
  442.     private void writeObject(final ObjectOutputStream ignored) {
  443.         throw new UnsupportedOperationException("Serialization is not supported");
  444.     }

  445. }