All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sshtools.client.sftp.SftpClient Maven / Gradle / Ivy

package com.sshtools.client.sftp;

/*-
 * #%L
 * Client API
 * %%
 * Copyright (C) 2002 - 2024 JADAPTIVE Limited
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;

import com.sshtools.client.SshClient;
import com.sshtools.client.tasks.FileTransferProgress;
import com.sshtools.common.files.AbstractFile;
import com.sshtools.common.files.AbstractFileFactory;
import com.sshtools.common.files.direct.DirectFileFactory;
import com.sshtools.common.files.direct.NioFileFactory.NioFileFactoryBuilder;
import com.sshtools.common.logger.Log;
import com.sshtools.common.permissions.PermissionDeniedException;
import com.sshtools.common.sftp.GlobSftpFileFilter;
import com.sshtools.common.sftp.PosixPermissions;
import com.sshtools.common.sftp.PosixPermissions.PosixPermissionsBuilder;
import com.sshtools.common.sftp.RegexSftpFileFilter;
import com.sshtools.common.sftp.SftpFileAttributes;
import com.sshtools.common.sftp.SftpFileFilter;
import com.sshtools.common.sftp.SftpStatusException;
import com.sshtools.common.ssh.SshConnection;
import com.sshtools.common.ssh.SshException;
import com.sshtools.common.ssh.SshIOException;
import com.sshtools.common.ssh.components.jce.JCEAlgorithms;
import com.sshtools.common.util.ByteArrayWriter;
import com.sshtools.common.util.EOLProcessor;
import com.sshtools.common.util.FileUtils;
import com.sshtools.common.util.IOUtils;
import com.sshtools.common.util.UnsignedInteger32;
import com.sshtools.common.util.UnsignedInteger64;
import com.sshtools.common.util.Utils;

/**
 * An abstract task that implements an SFTP client.
 */
@SuppressWarnings("removal")
public class SftpClient implements Closeable {
	/**
	 * Default buffer size
	 */
	public static final int DEFAULT_BUFFER_SIZE = 1024000;
	
	public final static class SftpClientBuilder {

		private Optional connection = Optional.empty();
		private Optional> fileFactory = Optional.empty();
		private Optional blockSize = Optional.empty();
		private Optional asyncRequests = Optional.empty();
		private int bufferSize = DEFAULT_BUFFER_SIZE;
		private Optional localHome = Optional.empty();
		private boolean localHomeSandbox;
		private Set customRoots = new LinkedHashSet<>();
		private Optional localPath = Optional.empty();
		private Optional remotePath = Optional.empty();
		
		/**
		 * Create a new {@link SftpClientBuilder}.
		 * 
		 * @return builder
		 */
		public static SftpClientBuilder create() {
			return new SftpClientBuilder(); 
		}

		/**
		 * Adds one or more custom file system root paths. Some devices
		 * have unusual file system roots such as "flash:", customRoots contains these.
		 * If a device uses roots like this, and folder traversal on the device is
		 * required then it must have its root stored in customRoots
		 * 
		 * @param rootPaths root paths
		 * @see SftpClientBuilder#withCustomRoots(String)
		 */
		public SftpClientBuilder withCustomRoots(String... rootPaths) {
			customRoots.addAll(Arrays.asList(rootPaths));
			return this;
		}
		
		/**
		 * Use an existing {@link SshConnection} to create the {@link SftpClient}.
		 * 
		 * @param connection connection
		 * @return this for chaining
		 */
		public SftpClientBuilder withConnection(SshConnection connection) {
			if(connection.isConnected() && !connection.isDisconnecting()) {
				this.connection = Optional.of(connection);
				return this;
			}
			else
				throw new IllegalStateException("Not connected.");
		}
		
		/**
		 * Use an existing {@link SshClient} to create the {@link SftpClient}.
		 * 
		 * @param client client
		 * @return this for chaining
		 */
		public SftpClientBuilder withClient(SshClient client) {
			return withConnection(client.getConnection());
		}
		
		/**
		 * Use a specific {@link AbstractFileFactory} for this client.
		 * 
		 * @param factory factory
		 * @return this for chaining
		 */
		public SftpClientBuilder withFileFactory(AbstractFileFactory fileFactory) {
			this.fileFactory = Optional.of(fileFactory);
			return this;
		}
		
		/**
		 * Whether or not to sandbox the path provided as the Local Home/code>. 
		 * default this is true. Will only be used when {@link #withLocalHome(Path)} or
		 * {@link #withLocalHome(File)} have been used, and {@link #withFileFactory(AbstractFileFactory)} has
		 * not been used.
		 *
		 * @paran sandbox
		 */
		public SftpClientBuilder withLocalHomeSandbox(boolean localHomeSandbox) {
			this.localHomeSandbox = localHomeSandbox;
			return this;
		}
		
		/**
		 * Set to not sandbox the path provided as the Local Home/code>. 
		 * default this is true. Will only be used when {@link #withLocalHome(Path)} or
		 * {@link #withLocalHome(File)} have been used, and {@link #withFileFactory(AbstractFileFactory)} has
		 * not been used.
		 *
		 * @paran sandbox
		 */
		public SftpClientBuilder withWithoutLocalHomeSandbox() {
			return withLocalHomeSandbox(false);
		}
		
		/**
		 * Start off in a specified remote path. Any subsequent relative
		 * remote paths would then be resolved against this path. The 
		 * path may be changed at runtime using {@link SftpClient#cd(String)}.
		 * 
		 * @param path path
		 * @return this for chaining
		 */
		public SftpClientBuilder withRemotePath(String remotePath) {
			this.remotePath = Optional.of(remotePath);
			return this;
		}
		
		/**
		 * Start off in a specified local path. Any subsequent relative
		 * remote paths would then be resolved against this path. The 
		 * path may be changed at runtime using {@link SftpClient#lcd(String)}.
		 * 
		 * @param path path
		 * @return this for chaining
		 */
		public SftpClientBuilder withLocalPath(String localPath) {
			this.localPath = Optional.of(localPath);
			return this;
		}
		
		/**
		 * Use a specific {@link Path} as the home of the local file system. 
		 * Will only be used if {@link #withFileFactory(AbstractFileFactory)} has not 
		 * been used. If not provided, the current user's home directory (i.e. System.getProperty("user.home");)
		 * will be used.
		 * 

* Use of this method will replace the currently configured {@link AbstractFileFactory} if any. * * @param localHome local root path * @return this for chaining */ public SftpClientBuilder withLocalHome(Path localHome) { this.localHome = Optional.of(localHome); return this; } /** * Use a specific {@link Path} as the home of the local file system. * Will only be used if {@link #withFileFactory(AbstractFileFactory)} has not * been used. If not provided, the current user's home directory (i.e. System.getProperty("user.home");) * will be used. *

* Use of this method will replace the currently configured {@link AbstractFileFactory} if any. * * @param localRoot local root path * @return this for chaining */ public SftpClientBuilder withLocalHome(File localRoot) { return withLocalHome(localRoot.toPath()); } /** * Sets the block size used when transferring files, defaults to the optimized * setting of 32768. You should not increase this value as the remote server may * not be able to support higher blocksizes. * * @param blockSize block size */ public SftpClientBuilder withBlockSize(int blockSize) { this.blockSize = blockSize == -1 ? Optional.empty() : Optional.of(blockSize); return this; } /** * Set the size of the buffer which is used to read from the local file system. * This setting is used to optimize the writing of files by allowing for a large * chunk of data to be read in one operation from a local file. The previous * version simply read each block of data before sending however this decreased * performance, this version now reads the file into a temporary buffer in order * to reduce the number of local filesystem reads. This increases performance * and so this setting should be set to the highest value possible. The default * setting is negative which means the entire file will be read into a temporary * buffer. * * @param bufferSize buffer size */ public SftpClientBuilder withBufferSize(int bufferSize) { this.bufferSize = bufferSize; return this; } /** * Set the maximum number of asynchronous requests to use for optimised reads and writes to remote files. * When not provided, this will be calculate from the remote window size and the block size. * * @param asyncRequests max async requests */ public SftpClientBuilder withAsyncRequests(int asyncRequests) { this.asyncRequests = asyncRequests == -1 ? Optional.empty() : Optional.of(asyncRequests); return this; } /** * Build a new {@link SftpClient}. * * @return sftp client * @throws SshException on SSH error * @throws IOException on I/O error * @throws PermissionDeniedException on permissions error */ public SftpClient build() throws SshException, PermissionDeniedException, IOException { return new SftpClient(this); } } private final SftpChannel sftp; private String cwd = ""; private AbstractFile lcwd; private int blocksize; private int asyncRequests; private int buffersize; // Default permissions is determined by default_permissions ^ umask int umask = 0022; boolean applyUmask = false; /** * Instructs the client to use a binary transfer mode when used with * {@link setTransferMode(int)} */ public static final int MODE_BINARY = 1; /** * Instructs the client to use a text transfer mode when used with * {@link setTransferMode(int)}. */ public static final int MODE_TEXT = 2; /** *

* Specifies that the remote server is using \r\n as its newline convention when * used with {@link setRemoteEOL(int)} *

*/ public static final int EOL_CRLF = EOLProcessor.TEXT_CRLF; /** *

* Specifies that the remote server is using \n as its newline convention when * used with {@link setRemoteEOL(int)} *

*/ public static final int EOL_LF = EOLProcessor.TEXT_LF; /** *

* Specifies that the remote server is using \r as its newline convention when * used with {@link setRemoteEOL(int)} *

*/ public static final int EOL_CR = EOLProcessor.TEXT_CR; private int outputEOL = EOL_CRLF; private int inputEOL = EOLProcessor.TEXT_SYSTEM; private boolean stripEOL = false; private boolean forceRemoteEOL; private int transferMode = MODE_BINARY; SftpClient(SftpClientBuilder builder) throws SshException, PermissionDeniedException, IOException { var fileFactory = builder.fileFactory.orElseGet(() -> NioFileFactoryBuilder.create(). withHome(builder.localHome.orElseGet(() -> Paths.get(System.getProperty("user.home")))). withSandbox(builder.localHomeSandbox). build()); this.asyncRequests = builder.asyncRequests.orElse(-1); this.buffersize = builder.bufferSize; this.blocksize = builder.blockSize.orElse(-1); this.sftp = new SftpChannel(builder.connection.orElseThrow(() -> new IllegalStateException("Either an existing connection or an existing client must be provided."))); this.lcwd = fileFactory.getFile(builder.localPath.orElse("")); this.cwd = builder.remotePath.orElse(""); this.customRoots.addAll(builder.customRoots); } @Deprecated(since = "3.1.0", forRemoval = true) public SftpClient(SshConnection con) throws SshException, PermissionDeniedException, IOException { this(con, new DirectFileFactory(new java.io.File(System.getProperty("user.home")))); } @Deprecated(since = "3.1.0", forRemoval = true) public SftpClient(SshConnection con, AbstractFileFactory fileFactory) throws PermissionDeniedException, IOException, SshException { this.blocksize = this.asyncRequests = -1; this.buffersize = DEFAULT_BUFFER_SIZE; this.lcwd = fileFactory.getFile(""); this.cwd = ""; this.sftp = new SftpChannel(con); } @Deprecated(since = "3.1.0", forRemoval = true) public SftpClient(SshClient ssh) throws SshException, PermissionDeniedException, IOException { this(ssh.getConnection()); } @Deprecated(since = "3.1.0", forRemoval = true) public SftpClient(SshClient ssh, AbstractFileFactory fileFactory) throws SshException, PermissionDeniedException, IOException { this(ssh.getConnection(), fileFactory); } /** * Sets the block size used when transferring files, defaults to the optimized * setting of 32768. You should not increase this value as the remote server may * not be able to support higher blocksizes. * * @param blocksize block size */ public void setBlockSize(int blocksize) { if (blocksize < 512) { throw new IllegalArgumentException("Block size must be greater than 512"); } this.blocksize = blocksize; } /** * Returns the instance of the AbstractSftpChannel used by this class * * @return the AbstractSftpChannel instance */ public SftpChannel getSubsystemChannel() { return sftp; } /** *

* Sets the transfer mode for current operations. The valid modes are:
*
* {@link #MODE_BINARY} - Files are transferred in binary mode and no processing * of text files is performed (default mode).
*
* {@link #MODE_TEXT} - For servers supporting version 4+ of the SFTP protocol * files are transferred in text mode. For earlier protocol versions the files * are transfered in binary mode but the client performs processing of text; if * files are written to the remote server the client ensures that the line * endings conform to the remote EOL mode set using {@link setRemoteEOL(int)}. * For files retrieved from the server the EOL policy is based upon System * policy as defined by the "line.seperator" system property. *

* * @param transferMode int */ public void setTransferMode(int transferMode) { if (transferMode != MODE_BINARY && transferMode != MODE_TEXT) throw new IllegalArgumentException("Mode can only be either binary or text"); this.transferMode = transferMode; if (Log.isDebugEnabled()) Log.debug("Transfer mode set to " + (transferMode == MODE_BINARY ? "binary" : "text")); } /** * Strip all line endings in preference of the target system EOL setting. * * @param stripEOL */ public void setStripEOL(boolean stripEOL) { this.stripEOL = stripEOL; } /** *

* When connected to servers running SFTP version 3 (or less) the remote EOL * type needs to be explicitly set because there is no reliable way for the * client to determine the type of EOL for text files. In versions 4+ a * mechanism is provided and this setting is overridden. *

* *

* Valid values for this method are {@link EOL_CRLF} (default), {@link EOL_CR}, * and {@link EOL_LF}. *

* * @param eolMode int */ public void setRemoteEOL(int eolMode) { this.outputEOL = eolMode; if (Log.isDebugEnabled()) Log.debug("Remote EOL set to " + (eolMode == EOL_CRLF ? "CRLF" : (eolMode == EOL_CR ? "CR" : "LF"))); } /** *

* Override the default local system EOL for text mode files. *

* *

* Valid values for this method are {@link EOL_CRLF} (default), {@link EOL_CR}, * and {@link EOL_LF}. *

* * @param eolMode int */ public void setLocalEOL(int eolMode) { this.inputEOL = eolMode; if (Log.isDebugEnabled()) Log.debug("Input EOL set to " + (eolMode == EOL_CRLF ? "CRLF" : (eolMode == EOL_CR ? "CR" : "LF"))); } /** * Override automatic detection of the remote EOL (any SFTP version). USE WITH * CAUTION. * * @param forceRemoteEOL */ public void setForceRemoteEOL(boolean forceRemoteEOL) { this.forceRemoteEOL = forceRemoteEOL; } /** * * @return int */ public int getTransferMode() { return transferMode; } /** * Set the size of the buffer which is used to read from the local file system. * This setting is used to optimize the writing of files by allowing for a large * chunk of data to be read in one operation from a local file. The previous * version simply read each block of data before sending however this decreased * performance, this version now reads the file into a temporary buffer in order * to reduce the number of local filesystem reads. This increases performance * and so this setting should be set to the highest value possible. The default * setting is negative which means the entire file will be read into a temporary * buffer. * * @param buffersize */ public void setBufferSize(int buffersize) { this.buffersize = buffersize; if (Log.isDebugEnabled()) Log.debug("Buffer size set to " + buffersize); } /** * Set the maximum number of asynchronous requests that are outstanding at any * one time. This setting is used to optimize the reading and writing of files * to/from the remote file system when using the get and put methods. The * default for this setting is 100. * * @param asyncRequests aync requests */ public void setMaxAsyncRequests(int asyncRequests) { if (asyncRequests < 1) { throw new IllegalArgumentException("Maximum asynchronous requests must be greater or equal to 1"); } this.asyncRequests = asyncRequests; if (Log.isDebugEnabled()) Log.debug("Max async requests set to " + asyncRequests); } /** * Sets the umask used by this client.
* *
	 * To give yourself full permissions for both files and directories and
	 * prevent the group and other users from having access:
	 * 
	 *   umask(077);
	 * 
	 * This subtracts 077 from the system defaults for files and directories
	 * 666 and 777. Giving a default access permissions for your files of
	 * 600 (rw-------) and for directories of 700 (rwx------).
	 * 
	 * To give all access permissions to the group and allow other users read
	 * and execute permission:
	 * 
	 *   umask(002);
	 * 
	 * This subtracts 002 from the system defaults to give a default access permission
	 * for your files of 664 (rw-rw-r--) and for your directories of 775 (rwxrwxr-x).
	 * 
	 * To give the group and other users all access except write access:
	 * 
	 *   umask(022);
	 * 
	 * This subtracts 022 from the system defaults to give a default access permission
	 * for your files of 644 (rw-r--r--) and for your directories of 755 (rwxr-xr-x).
	 * 
* *
* * @param umask umask * @return the previous umask value */ public int umask(int umask) { applyUmask = true; int old = this.umask; this.umask = umask; if (Log.isDebugEnabled()) Log.debug("umask " + umask); return old; } public SftpHandle openFile(String fileName) throws SftpStatusException, SshException { return openFile(fileName, SftpChannel.OPEN_READ); } public SftpHandle openFile(String fileName, int flags) throws SftpStatusException, SshException { if (transferMode == MODE_TEXT && sftp.getVersion() > 3) { return sftp.openFile(resolveRemotePath(fileName), flags | SftpChannel.OPEN_TEXT); } return sftp.openFile(resolveRemotePath(fileName), flags); } public SftpHandle openDirectory(String path) throws SftpStatusException, SshException { return sftp.openDirectory(path); } public List readDirectory(SftpHandle dir) throws SftpStatusException, SshException { List results = new ArrayList<>(); if (dir.listChildren(results) == -1) { return null; } return results; } /** *

* Changes the working directory on the remote server, or the user's default * directory if null or any empty string is provided as the * directory path. The user's default directory is typically their home * directory but is dependent upon server implementation. *

* * @param dir the new working directory * * @throws IOException if an IO error occurs or the file does not exist * @throws SftpStatusException * @throws SshException */ public void cd(String dir) throws SftpStatusException, SshException { String actual; if (dir == null || dir.equals("")) { actual = sftp.getDefaultDirectory(); } else { actual = resolveRemotePath(dir); actual = sftp.getAbsolutePath(actual); } if (!actual.equals("")) { SftpFileAttributes attr = sftp.getAttributes(actual); if (!attr.isDirectory()) { throw new SftpStatusException(SftpStatusException.SSH_FX_FAILURE, dir + " is not a directory"); } } if (Log.isDebugEnabled()) Log.debug("Changing dir from " + cwd + " to " + (actual.equals("") ? "user default dir" : actual)); cwd = actual; } /** *

* Get the default directory (or HOME directory) *

* * @return String * * @throws SftpStatusException * @throws SshException */ public String getDefaultDirectory() throws SftpStatusException, SshException { return sftp.getDefaultDirectory(); } /** * Change the working directory to the parent directory * * @throws SftpStatusException * @throws SshException */ public void cdup() throws SftpStatusException, SshException { SftpFile cd = sftp.getFile(cwd); SftpFile parent = cd.getParent(); if (parent != null) cwd = parent.getAbsolutePath(); } private AbstractFile resolveLocalPath(String path) throws IOException, PermissionDeniedException { return lcwd.resolveFile(path); } private boolean isWindowsRoot(String path) { // true if path>2 and starts with a letter followed by a ':' followed by // '/' or '\\' return path.length() > 2 && (((path.charAt(0) >= 'a' && path.charAt(0) <= 'z') || (path.charAt(0) >= 'A' && path.charAt(0) <= 'Z')) && path.charAt(1) == ':' && path.charAt(2) == '/' || path.charAt(2) == '\\'); } /** * some devices have unusual file system roots such as "flash:", customRoots * contains these. If a device uses roots like this, and folder traversal on the * device is required then it must have its root stored in customRoots * * TODO: Make unmodifiable at version 3.2.x. */ private Vector customRoots = new Vector(); /** * Add a custom file system root path such as "flash:" * * @param rootPath * @see SftpClientBuilder#withCustomRoots(String) */ @Deprecated(since = "3.1.0", forRemoval = true) public void addCustomRoot(String rootPath) { customRoots.addElement(rootPath); } /** * Remove a custom file system root path such as "flash:" * * @param rootPath * @see SftpClientBuilder#withCustomRoots(String) */ @Deprecated(since = "3.1.0", forRemoval = true) public void removeCustomRoot(String rootPath) { customRoots.removeElement(rootPath); } /** * Tests whether path starts with a custom file system root. * * @param path * @return true if path starts with an element of customRoots, * false otherwise */ private boolean startsWithCustomRoot(String path) { for (Enumeration it = customRoots.elements(); it != null && it.hasMoreElements();) { if (path.startsWith(it.nextElement())) { return true; } } return false; } /** * returns the canonical form of path, if path doesn't start with one of * '/';cwd;a customRoot; or is a WindowsRoot then prepend cwd to path * * @param path * @return canonical form of path * @throws SftpStatusException * @throws SshException */ private String resolveRemotePath(String path) throws SftpStatusException, SshException { verifyConnection(); String actual; if (!path.startsWith("/") && !path.startsWith(cwd) && !isWindowsRoot(path) && !startsWithCustomRoot(path)) { actual = cwd + (cwd.endsWith("/") ? "" : "/") + path; } else { actual = path; } if (!Boolean.getBoolean("maverick.disableSlashRemoval") && !actual.equals("/") && actual.endsWith("/")) { return actual.substring(0, actual.length() - 1); } else { return actual; } } private void verifyConnection() throws SshException { if (sftp.isClosed()) { throw new SshException("The SFTP connection has been closed", SshException.REMOTE_HOST_DISCONNECTED); } } /** *

* Creates a new directory on the remote server. This method will throw an * exception if the directory already exists. To create directories and * disregard any errors use the mkdirs method. *

* * @param dir the name of the new directory * * @throws SftpStatusException * @throws SshException */ public void mkdir(String dir) throws SftpStatusException, SshException { String actual = resolveRemotePath(dir); if (Log.isDebugEnabled()) Log.debug("Creating dir " + dir); SftpFileAttributes attrs = null; try { attrs = sftp.getAttributes(actual); } catch (SftpStatusException ex) { // only create the directory if catch an exception with code file // not found SftpFileAttributes newattrs = new SftpFileAttributes(SftpFileAttributes.SSH_FILEXFER_TYPE_DIRECTORY, sftp.getCharsetEncoding()); ; if (applyUmask) { newattrs.setPermissions(PosixPermissionsBuilder.create().fromBitmask(0777 ^ umask).build()); } sftp.makeDirectory(actual, newattrs); return; } if (Log.isDebugEnabled()) Log.debug("File with name " + dir + " already exists!"); throw new SftpStatusException(SftpStatusException.SSH_FX_FAILURE, (attrs.isDirectory() ? "Directory" : "File") + " already exists named " + dir); } /** *

* Create a directory or set of directories. This method will not fail even if * the directories exist. It is advisable to test whether the directory exists * before attempting an operation by using * stat to return the directories * attributes. *

* * @param dir the path of directories to create. */ public void mkdirs(String dir) throws SftpStatusException, SshException { StringTokenizer tokens = new StringTokenizer(dir, "/"); String path = dir.startsWith("/") ? "/" : ""; while (tokens.hasMoreElements()) { path += (String) tokens.nextElement(); try { stat(path); } catch (SftpStatusException ex) { try { mkdir(path); } catch (SftpStatusException ex2) { if (ex2.getStatus() == SftpStatusException.SSH_FX_PERMISSION_DENIED) throw ex2; } } path += "/"; } } /** * Determine whether the file object is pointing to a symbolic link that is * pointing to a directory. * * @return boolean */ public boolean isDirectoryOrLinkedDirectory(SftpFile file) throws SftpStatusException, SshException { return file.isDirectory() || (file.isLink() && stat(file.getAbsolutePath()).isDirectory()); } /** * Determine if a path exists. * * @return boolean */ public boolean exists(String path) throws SftpStatusException, SshException { try { stat(path); return true; } catch(SftpStatusException sse) { if(sse.getStatus() == SftpStatusException.SSH_FX_NO_SUCH_FILE) { return false; } else throw sse; } } /** *

* Returns the absolute path name of the current remote working directory. *

* * @return the absolute path of the remote working directory. * @throws SshException * @throws SftpStatusException */ public String pwd() throws SftpStatusException, SshException { return getAbsolutePath(cwd); } /** *

* List the contents of the current remote working directory. *

* *

* Returns a list of SftpFile * instances for the current working directory. *

* * @return a list of SftpFile for the current working directory * * @throws SftpStatusException * @throws SshException * */ public SftpFile[] ls() throws SftpStatusException, SshException { return ls(cwd); } /** *

* List the contents remote directory. *

* *

* Returns a list of SftpFile * instances for the remote directory. *

* * @param path the path on the remote server to list * * @return a list of SftpFile for the remote directory * * @throws SftpStatusException * @throws SshException */ public SftpFile[] ls(String path) throws SftpStatusException, SshException { String actual = resolveRemotePath(path); if (Log.isDebugEnabled()) Log.debug("Listing files for " + actual); Vector children = new Vector(); try(SftpHandle file = sftp.openDirectory(actual)) { while (file.listChildren(children) > -1) { ; } } catch (IOException e) { throw new SshException(SshException.INTERNAL_ERROR, e); } SftpFile[] files = new SftpFile[children.size()]; int index = 0; for (Enumeration e = children.elements(); e.hasMoreElements();) { files[index++] = e.nextElement(); } return files; } public SftpFile[] ls(String filter, boolean regexFilter, int maximumFiles) throws SftpStatusException, SshException { return ls("", filter, regexFilter, maximumFiles); } public SftpFile[] ls(String path, String filter, boolean regexFilter, int maximumFiles) throws SftpStatusException, SshException { String actual = resolveRemotePath(path); if (Log.isDebugEnabled()) { Log.debug("Listing files for {} with filter {}"); } ByteArrayWriter msg = new ByteArrayWriter(); try { msg.writeString(actual); msg.writeString(filter); msg.writeBoolean(regexFilter); boolean localFiltering = false; SftpFileFilter f = null; if (localFiltering) { f = regexFilter ? new RegexSftpFileFilter(filter) : new GlobSftpFileFilter(filter); } Vector children = new Vector(); Vector tmp = new Vector(); try(SftpHandle handleObject = openDirectoryHandle(actual, msg)) { int pageCount; do { pageCount = handleObject.listChildren(tmp); if (pageCount > -1) { if (!localFiltering) { if (pageCount > -1 && Log.isDebugEnabled()) { Log.debug("Got page of {} files for {} with filter {}", pageCount, actual, filter, localFiltering); } children.addAll(tmp); } else { if (pageCount > -1 && Log.isDebugEnabled()) { Log.debug("Got page of {} files for {} before local filtering", pageCount, actual, filter, localFiltering); } int count = 0; for (SftpFile t : tmp) { if (f.matches(t.getFilename())) { children.add(t); count++; } } if (pageCount > -1 && Log.isDebugEnabled()) { Log.debug("Got page of {} files for {} after local filtering", count, actual, filter, localFiltering); } } } } while (pageCount > -1 && (maximumFiles == 0 || children.size() < maximumFiles)); } SftpFile[] files = new SftpFile[children.size()]; int index = 0; for (Enumeration e = children.elements(); e.hasMoreElements();) { files[index++] = e.nextElement(); } return files; } catch (IOException e) { throw new SshException(SshException.INTERNAL_ERROR, e); } finally { try { msg.close(); } catch (IOException e) { } } } private SftpHandle openDirectoryHandle(String path, ByteArrayWriter msg) throws SshException, SftpStatusException { SftpFile file = new SftpFile(path, sftp.getAttributes(path), sftp, null); try { return sftp.getHandle( sftp.sendExtensionMessage("[email protected]", msg.toByteArray()), file); } catch (SftpStatusException e) { if (Boolean.getBoolean("maverick.disableLocalFiltering")) { throw new SshException("Remote server does not support server side filtering", SshException.UNSUPPORTED_OPERATION); } return file.handle(sftp.openDirectory(path).getHandle()); } } /** * Return an iterator for the current working directory. * * This method improves memory usage by only getting paged contents of the * directory. * * @return * @throws SftpStatusException * @throws SshException */ public Iterator lsIterator() throws SftpStatusException, SshException { return lsIterator(cwd); } /** * Return an iterator for the path provided. * * This method improves memory usage by only getting paged contents of the * directory. * * @param path * @return * @throws SftpStatusException * @throws SshException */ public Iterator lsIterator(String path) throws SftpStatusException, SshException { return new DirectoryIterator(path); } /** *

* Changes the local working directory. *

* * @param path the path to the new working directory * * @throws SftpStatusException * @throws IOException * @throws PermissionDeniedException */ public void lcd(String path) throws SftpStatusException, IOException, PermissionDeniedException { AbstractFile actual = lcwd.resolveFile(path); if (!actual.isDirectory()) { throw new SftpStatusException(SftpStatusException.SSH_FX_FAILURE, path + " is not a directory"); } lcwd = actual; } /** *

* Returns the absolute path to the local working directory. *

* * @return the absolute path of the local working directory. * @throws PermissionDeniedException * @throws IOException */ public String lpwd() throws IOException, PermissionDeniedException { return lcwd.getAbsolutePath(); } public AbstractFile getCurrentWorkingDirectory() { return lcwd; } /** *

* Download the remote file to the local computer. *

* * @param path the path to the remote file * @param progress * * @return the downloaded file's attributes * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public SftpFileAttributes get(String path, FileTransferProgress progress) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { return get(path, progress, false); } /** *

* Download the remote file to the local computer. *

* * @param path the path to the remote file * @param progress * @param resume attempt to resume a interrupted download * * @return the downloaded file's attributes * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public SftpFileAttributes get(String path, FileTransferProgress progress, boolean resume) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { String localfile; if (path.lastIndexOf("/") > -1) { localfile = path.substring(path.lastIndexOf("/") + 1); } else { localfile = path; } return get(path, localfile, progress, resume); } /** *

* Download the remote file to the local computer * * @param path the path to the remote file * @param resume attempt to resume an interrupted download * * @return the downloaded file's attributes * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public SftpFileAttributes get(String path, boolean resume) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { return get(path, (FileTransferProgress) null, resume); } /** *

* Download the remote file to the local computer * * @param path the path to the remote file * * @return the downloaded file's attributes * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public SftpFileAttributes get(String path) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { return get(path, (FileTransferProgress) null); } /** * Get the target path of a symbolic link. * * @param linkpath * @return String * @throws SshException if the remote SFTP version is < 3 an exception is thrown * as this feature is not supported by previous versions of * the protocol. */ public String getSymbolicLinkTarget(String linkpath) throws SftpStatusException, SshException { return sftp.getSymbolicLinkTarget(linkpath); } /** *

* Download the remote file to the local computer. If the paths provided are not * absolute the current working directory is used. *

* * @param remote the path/name of the remote file * @param local the path/name to place the file on the local computer * @param progress * * @return the downloaded file's attributes * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public SftpFileAttributes get(String remote, String local, FileTransferProgress progress) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { return get(remote, local, progress, false); } /** *

* Download the remote file to the local computer. If the paths provided are not * absolute the current working directory is used. *

* * @param remote the path/name of the remote file * @param local the path/name to place the file on the local computer * @param progress * @param resume attempt to resume an interrupted download * * @return the downloaded file's attributes * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public SftpFileAttributes get(String remote, String local, FileTransferProgress progress, boolean resume) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { // Moved here to ensure that stream is closed in finally OutputStream out = null; SftpFileAttributes attrs = null; // Perform local file operations first, then if it throws an exception // the server hasn't been unnecessarily loaded. AbstractFile localPath = resolveLocalPath(local); if (!localPath.exists()) { AbstractFile parent = localPath.resolveFile(FileUtils.getParentPath(localPath.getAbsolutePath())); parent.createFolder(); } if (localPath.isDirectory()) { localPath = localPath.resolveFile(FileUtils.getFilename(remote)); } // Check that file exists before we create a file stat(remote); long position = 0; try { // if resuming and the local file exists, then open as random access // file and seek to end of the file ready to continue writing if (resume && localPath.exists()) { out = localPath.getOutputStream(true); position = localPath.length(); } else { out = localPath.getOutputStream(); } attrs = get(remote, out, progress, position); return attrs; } catch (IOException ex) { throw new SftpStatusException(SftpStatusException.SSH_FX_FAILURE, "Failed to open outputstream to " + local); } finally { try { if (out != null) out.close(); if (attrs != null) { localPath.setAttributes(attrs); } } catch (Throwable ex) { // NOTE: should we ignore this? } } } public String getRemoteNewline() throws SftpStatusException { return new String(sftp.getCanonicalNewline()); } public int getRemoteEOL() throws SftpStatusException { return getEOL(sftp.getCanonicalNewline()); } public int getEOL(String line) throws SftpStatusException { byte[] nl = line.getBytes(); return getEOL(nl); } public int getEOL(byte[] nl) throws SftpStatusException { switch (nl.length) { case 1: if (nl[0] == '\r') return EOLProcessor.TEXT_CR; else if (nl[0] == '\n') return EOLProcessor.TEXT_LF; else throw new SftpStatusException(SftpStatusException.INVALID_HANDLE, "Unsupported text mode: invalid newline character"); case 2: if (nl[0] == '\r' && nl[1] == '\n') return EOLProcessor.TEXT_CRLF; else throw new SftpStatusException(SftpStatusException.INVALID_HANDLE, "Unsupported text mode: invalid newline characters"); default: throw new SftpStatusException(SftpStatusException.INVALID_HANDLE, "Unsupported text mode: newline length > 2"); } } /** * Download the remote file into the local file. * * @param remote * @param local * @param resume attempt to resume an interrupted download * * @return the downloaded file's attributes * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public SftpFileAttributes get(String remote, String local, boolean resume) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { return get(remote, local, null, resume); } /** * Download the remote file into the local file. * * @param remote * @param local * * @return the downloaded file's attributes * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public SftpFileAttributes get(String remote, String local) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { return get(remote, local, false); } /** *

* Download the remote file writing it to the specified * OutputStream. The OutputStream is closed by this method even if * the operation fails. *

* * @param remote the path/name of the remote file * @param local the OutputStream to write * @param progress * * @return the downloaded file's attributes * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException */ public SftpFileAttributes get(String remote, OutputStream local, FileTransferProgress progress) throws SftpStatusException, SshException, TransferCancelledException { return get(remote, local, progress, 0); } /** * constants for setting the regular expression syntax. */ public static final int NoSyntax = 0; public static final int GlobSyntax = 1; public static final int Perl5Syntax = 2; /** * default regular expression syntax is to not perform regular expression * matching on getFiles() and putFiles() */ private int RegExpSyntax = GlobSyntax; /** * sets the type of regular expression matching to perform on gets and puts * * @param syntax , NoSyntax for no regular expression matching, GlobSyntax for * GlobSyntax, Perl5Syntax for Perl5Syntax */ public void setRegularExpressionSyntax(int syntax) { RegExpSyntax = syntax; } /** * Called by getFileMatches() to do regular expression pattern matching on the * files in 'remote''s parent directory. * * @param remote * @return SftpFile[] * @throws SftpStatusException * @throws SshException */ public SftpFile[] matchRemoteFiles(String remote) throws SftpStatusException, SshException { String actualDir; String actualSearch; int fileSeparatorIndex; if ((fileSeparatorIndex = remote.lastIndexOf("/")) > -1) { actualDir = remote.substring(0, fileSeparatorIndex); actualSearch = remote.length() > fileSeparatorIndex + 1 ? remote.substring(fileSeparatorIndex + 1) : ""; } else { actualDir = cwd; actualSearch = remote; } SftpFile[] files; RegularExpressionMatching matcher; switch (RegExpSyntax) { case GlobSyntax: matcher = new GlobRegExpMatching(); files = ls(actualDir); break; case Perl5Syntax: matcher = new RegExpMatching(); files = ls(actualDir); break; default: matcher = new NoRegExpMatching(); files = new SftpFile[1]; String actual = resolveRemotePath(remote); files[0] = getSubsystemChannel().getFile(actual); } return matcher.matchFilesWithPattern(files, actualSearch); } /** * If RegExpSyntax is set to GlobSyntax or Perl5Syntax then it pattern matches * the files in the remote directory using "remote" as a glob or perl5 Regular * Expression. For each matching file get() is called to copy the file to the * local directory. * *

* If RegExpSyntax is set to NoSyntax then "remote" is treated as a filepath * instead of a regular expression *

* * @param remote * @param progress * @param streamOrFile * @return SftpFile[] of SftpFile's that have been retrieved * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ private SftpFile[] getFileMatches(String remote, String local, FileTransferProgress progress, boolean resume) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { // match with files using remote as regular expression. SftpFile[] matchedFiles = matchRemoteFiles(remote); Vector retrievedFiles = new Vector(); // call get for each matched file, append the files attributes to a // vector to be returned at the end // call the correct get method depending on the get method that called // this for (int i = 0; i < matchedFiles.length; i++) { get(matchedFiles[i].getAbsolutePath(), local, progress, resume); retrievedFiles.addElement(matchedFiles[i]); } SftpFile[] retrievedSftpFiles = new SftpFile[retrievedFiles.size()]; retrievedFiles.copyInto(retrievedSftpFiles); return retrievedSftpFiles; } /** * Called by putFileMatches() to do regular expression pattern matching on the * files in 'local''s parent directory. * * @param local * @return String[] * @throws SftpStatusException * @throws SshException * @throws PermissionDeniedException * @throws IOException */ private String[] matchLocalFiles(String local) throws SftpStatusException, SshException, IOException, PermissionDeniedException { // Resolve the search path as it may not be CWD AbstractFile actualDir; String actualSearch; if (FileUtils.hasParents(local)) { actualDir = resolveLocalPath(FileUtils.getParentPath(local)); actualSearch = FileUtils.getFilename(local); } else { actualDir = lcwd; actualSearch = local; } RegularExpressionMatching matcher; AbstractFile[] files; switch (RegExpSyntax) { case GlobSyntax: matcher = new GlobRegExpMatching(); files = listFiles(actualDir); break; case Perl5Syntax: matcher = new RegExpMatching(); files = listFiles(actualDir); break; default: matcher = new NoRegExpMatching(); files = new AbstractFile[1]; files[0] = lcwd.resolveFile(local); } return matcher.matchFileNamesWithPattern(files, actualSearch); } private AbstractFile[] listFiles(AbstractFile f) throws IOException, PermissionDeniedException { return f.getChildren().toArray(new AbstractFile[0]); } /** * If RegExpSyntax is set to GlobSyntax or Perl5Syntax then it pattern matches * the files in the local directory using "local" as a glob or perl5 Regular * Expression. For each matching file put() is called to copy the file to the * remote directory. * *

* If RegExpSyntax is set to NoSyntax then "local" is treated as a filepath * instead of a regular expression. *

* * @param local * @param progress * @param streamOrFile * * @throws IOException * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException */ private void putFileMatches(String local, String remote, FileTransferProgress progress, boolean resume) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { String remotePath = resolveRemotePath(remote); // Remote must be a valid remote directory SftpFileAttributes attrs = null; try { attrs = stat(remotePath); } catch (SftpStatusException ex) { throw new SftpStatusException(ex.getStatus(), "Remote path '" + remote + "' does not exist. It must be a valid directory and must already exist!"); } if (!attrs.isDirectory()) throw new SftpStatusException(SftpStatusException.SSH_FX_NO_SUCH_PATH, "Remote path '" + remote + "' is not a directory!"); String[] matchedFiles = matchLocalFiles(local); if (Log.isDebugEnabled()) { Log.debug("Matched {} files for {}", matchedFiles.length, local); } for (int i = 0; i < matchedFiles.length; i++) { try { put(matchedFiles[i], remotePath, progress, resume); } catch (SftpStatusException ex) { throw new SftpStatusException(ex.getStatus(), "Failed to put " + matchedFiles[i] + " to " + remote + " [" + ex.getMessage() + "]"); } } } /** *

* Download the remote file writing it to the specified * OutputStream. The OutputStream is closed by this method even if * the operation fails. *

* * @param remote the path/name of the remote file * @param local the OutputStream to write * @param progress * @param position the position within the file to start reading from * * @return the downloaded file's attributes * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException */ public SftpFileAttributes get(String remote, OutputStream local, FileTransferProgress progress, long position) throws SftpStatusException, SshException, TransferCancelledException { String remotePath = resolveRemotePath(remote); SftpFileAttributes attrs = sftp.getAttributes(remotePath); if (position > attrs.getSize().longValue()) { throw new SftpStatusException(SftpStatusException.INVALID_RESUME_STATE, "The local file size is greater than the remote file"); } if (progress != null) { progress.started(attrs.getSize().longValue() - position, remotePath); } SftpHandle file; if (transferMode == MODE_TEXT && sftp.getVersion() > 3) { file = sftp.openFile(remotePath, SftpChannel.OPEN_READ | SftpChannel.OPEN_TEXT); } else { file = sftp.openFile(remotePath, SftpChannel.OPEN_READ); } try { if (transferMode == MODE_TEXT) { // Default text mode handling for versions 3- of the SFTP // protocol int inputStyle = outputEOL; int outputStyle = (stripEOL ? EOLProcessor.TEXT_ALL : inputEOL); byte[] nl = null; if (sftp.getVersion() <= 3 && sftp.getExtension("[email protected]") != null) { nl = sftp.getExtension("[email protected]"); } else if (sftp.getVersion() > 3) { nl = sftp.getCanonicalNewline(); } // Setup text mode correctly if were using version 4+ of the // SFTP protocol if (nl != null && !forceRemoteEOL) { inputStyle = getEOL(new String(nl)); } local = EOLProcessor.createOutputStream(inputStyle, outputStyle, local); } file.performOptimizedRead(remotePath, attrs.getSize().longValue(), blocksize, local, asyncRequests, progress, position); } catch (IOException ex) { throw new SftpStatusException(SftpStatusException.SSH_FX_FAILURE, "Failed to open text conversion outputstream"); } catch (TransferCancelledException tce) { throw tce; } finally { try { local.close(); } catch (Throwable t) { } try { file.close(); } catch (IOException ex) { } } if (progress != null) { progress.completed(); } return attrs; } /** * Create an InputStream for reading a remote file. * * @param remotefile * @param position * @return InputStream * @throws SftpStatusException * @throws SshException */ public InputStream getInputStream(String remotefile, long position) throws SftpStatusException, SshException { String remotePath = resolveRemotePath(remotefile); sftp.getAttributes(remotePath); return new SftpFileInputStream(sftp.openFile(remotePath, SftpChannel.OPEN_READ), position); } /** * Create an InputStream for reading a remote file. * * @param remotefile * @return InputStream * @throws SftpStatusException * @throws SshException */ public InputStream getInputStream(String remotefile) throws SftpStatusException, SshException { return getInputStream(remotefile, 0); } /** * Download the remote file into an OutputStream. * * @param remote * @param local * @param position the position from which to start reading the remote file * * @return the downloaded file's attributes * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException */ public SftpFileAttributes get(String remote, OutputStream local, long position) throws SftpStatusException, SshException, TransferCancelledException { return get(remote, local, null, position); } /** * Download the remote file into an OutputStream. * * @param remote * @param local * * @return the downloaded file's attributes * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException */ public SftpFileAttributes get(String remote, OutputStream local) throws SftpStatusException, SshException, TransferCancelledException { return get(remote, local, null, 0); } /** *

* Returns the state of the SFTP client. The client is closed if the underlying * session channel is closed. Invoking the quit method of this * object will close the underlying session channel. *

* * @return true if the client is still connected, otherwise false */ public boolean isClosed() { return sftp.isClosed(); } /** *

* Upload a file to the remote computer. *

* * @param local the path/name of the local file * @param progress * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public void put(String local, FileTransferProgress progress, boolean resume) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { AbstractFile f = resolveLocalPath(local); put(local, f.getName(), progress, resume); } /** *

* Upload a file to the remote computer. *

* * @param local the path/name of the local file * @param progress * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public void put(String local, FileTransferProgress progress) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { put(local, progress, false); } /** * Upload a file to the remote computer * * @param local * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public void put(String local) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { put(local, false); } /** * Upload a file to the remote computer * * @param local * @param resume attempt to resume after an interrupted transfer * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public void put(String local, boolean resume) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { put(local, (FileTransferProgress) null, resume); } /** *

* Upload a file to the remote computer. If the paths provided are not absolute * the current working directory is used. *

* * @param local the path/name of the local file * @param remote the path/name of the destination file * @param progress * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public void put(String local, String remote, FileTransferProgress progress) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { put(local, remote, progress, false); } public void append(InputStream in, String remote) throws SftpStatusException, SshException, TransferCancelledException { put(in, remote, null, -1, -1); } @Deprecated public void append(InputStream in, String remote, FileTransferProgress progress) throws SftpStatusException, SshException, TransferCancelledException { append(in, remote, progress, -1); } public void append(InputStream in, String remote, FileTransferProgress progress, long length) throws SftpStatusException, SshException, TransferCancelledException { put(in, remote, progress, -1, length); } /** *

* Upload a file to the remote computer. If the paths provided are not absolute * the current working directory is used. *

* * @param local the path/name of the local file * @param remote the path/name of the destination file * @param progress * @param resume attempt to resume after an interrupted transfer * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public void put(String local, String remote, FileTransferProgress progress, boolean resume) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { AbstractFile localPath = resolveLocalPath(local); InputStream in = localPath.getInputStream(); // File f = new File(local); long position = 0; long length = localPath.length(); SftpFileAttributes attrs = null; try { attrs = stat(remote); if (attrs.isDirectory()) { remote += (remote.endsWith("/") ? "" : "/") + localPath.getName(); attrs = stat(remote); } } catch (SftpStatusException ex) { resume = false; } if (resume) { if (localPath.length() <= attrs.getSize().longValue()) { try { in.close(); } catch (IOException e) { } throw new SftpStatusException(SftpStatusException.INVALID_RESUME_STATE, "The remote file size is greater than the local file"); } try { position = attrs.getSize().longValue(); in.skip(position); } catch (IOException ex) { try { in.close(); } catch (IOException e) { } throw new SftpStatusException(SftpStatusException.SSH_FX_NO_SUCH_FILE, ex.getMessage()); } } try { put(in, remote, progress, position, length); } finally { try { in.close(); } catch (IOException e) { } } } public void append(String local, String remote) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { append(local, remote, null); } @Deprecated public void append(String local, String remote, FileTransferProgress progress) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { append(local, remote, progress, - 1); } public void append(String local, String remote, FileTransferProgress progress, long length) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { AbstractFile localPath = resolveLocalPath(local); String remotePath = resolveRemotePath(remote); stat(remotePath); InputStream in = localPath.getInputStream(); try { append(in, remotePath, progress, length); } finally { try { in.close(); } catch (IOException e) { } } } /** * Upload a file to the remote computer * * @param local * @param remote * @param resume attempt to resume after an interrupted transfer * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public void put(String local, String remote, boolean resume) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { put(local, remote, null, resume); } /** * Upload a file to the remote computer * * @param local * @param remote * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException * @throws IOException */ public void put(String local, String remote) throws SftpStatusException, SshException, TransferCancelledException, IOException, PermissionDeniedException { put(local, remote, null, false); } /** *

* Upload a file to the remote computer reading from the specified * InputStream. The InputStream is closed, even if the operation fails. * The {@link FileTransferProgress} will be indeterminate, i.e {@link FileTransferProgress#started(long, String)} will * be called with a length of -1, as it is not possible to generically determine the length of * an {@link InputStream}. It is recommended you use {@link #put(InputStream, String, FileTransferProgress, long, long)} instead * to provide the size when known. *

* * @param in the InputStream being read * @param remote the path/name of the destination file * @param progress * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException */ public void put(InputStream in, String remote, FileTransferProgress progress) throws SftpStatusException, SshException, TransferCancelledException { put(in, remote, progress, 0, -1); } @Deprecated public void put(InputStream in, String remote, FileTransferProgress progress, long position) throws SftpStatusException, SshException, TransferCancelledException { put(in, remote, progress, position, -1); } /** *

* Upload a file to the remote computer reading from the specified * InputStream. The InputStream is closed, even if the operation fails. * The {@link FileTransferProgress} will be indeterminate if you pass a length of -1. * to provide the size when known. *

* * @param in the InputStream being read * @param remote the path/name of the destination file * @param progress progress * @param position position to start at * @param length the number of bytes that will be transferred or -1 if unknown. * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException */ public void put(InputStream in, String remote, FileTransferProgress progress, long position, long length) throws SftpStatusException, SshException, TransferCancelledException { String remotePath = resolveRemotePath(remote); SftpFileAttributes attrs = null; if (transferMode == MODE_TEXT) { // Default text mode handling for versions 3- of the SFTP protocol int inputStyle = (stripEOL ? EOLProcessor.TEXT_ALL : inputEOL); int outputStyle = outputEOL; byte[] nl = null; if (sftp.getVersion() <= 3 && sftp.getExtension("[email protected]") != null) { nl = sftp.getExtension("[email protected]"); } else if (sftp.getVersion() > 3) { nl = sftp.getCanonicalNewline(); } // Setup text mode correctly if were using version 4+ of the // SFTP protocol if (nl != null & !forceRemoteEOL) { outputStyle = getEOL(nl); } try { in = EOLProcessor.createInputStream(inputStyle, outputStyle, in); } catch (IOException ex) { throw new SshException("Failed to create EOL processing stream", SshException.INTERNAL_ERROR); } } attrs = new SftpFileAttributes(SftpFileAttributes.SSH_FILEXFER_TYPE_REGULAR, "UTF-8"); if (applyUmask) { attrs.setPermissions(PosixPermissionsBuilder.create().fromBitmask(0666 ^ umask).build()); } try { if (position > 0) { if (transferMode == MODE_TEXT && sftp.getVersion() > 3) { throw new SftpStatusException(SftpStatusException.SSH_FX_OP_UNSUPPORTED, "Resume on text mode files is not supported"); } internalPut(length, in, remotePath, progress, position, SftpChannel.OPEN_WRITE, attrs); } else { if (position == 0) { if (transferMode == MODE_TEXT && sftp.getVersion() > 3) { internalPut(length, in, remotePath, progress, position, SftpChannel.OPEN_CREATE | SftpChannel.OPEN_TRUNCATE | SftpChannel.OPEN_WRITE | SftpChannel.OPEN_TEXT, attrs); } else { internalPut(length, in, remotePath, progress, position, SftpChannel.OPEN_CREATE | SftpChannel.OPEN_TRUNCATE | SftpChannel.OPEN_WRITE, attrs); } } else { /** * Negative position means append */ if (transferMode == MODE_TEXT && sftp.getVersion() > 3) { internalPut(length, in, remotePath, progress, position, SftpChannel.OPEN_WRITE | SftpChannel.OPEN_TEXT | SftpChannel.OPEN_APPEND, attrs); } else { internalPut(length, in, remotePath, progress, position, SftpChannel.OPEN_WRITE | SftpChannel.OPEN_APPEND, attrs); } } } } catch(IOException ioe) { throw new SshException(ioe); } } private void internalPut(long length, InputStream in, String remotePath, FileTransferProgress progress, long position, int flags, SftpFileAttributes attrs) throws SftpStatusException, SshException, TransferCancelledException, IOException { try(SftpHandle handle = sftp.openFile(remotePath, flags, attrs)) { if (progress != null) { progress.started(length, remotePath); } handle.performOptimizedWrite(remotePath, blocksize, asyncRequests, in, buffersize, progress, position < 0 ? 0 : position); } catch (SftpStatusException e) { Log.error("SFTP status exception during transfer [" + e.getStatus() + "]", e); throw e; } catch (SshException e) { Log.error("SSH exception during transfer [" + e.getReason() + "]", e); if (e.getCause() != null) { Log.error("SSH exception cause", e.getCause()); } throw e; } catch (TransferCancelledException e) { Log.error("Transfer cancelled", e); throw e; } finally { try { in.close(); } catch (Throwable t) { } } if (progress != null) { progress.completed(); } } /** * Create an OutputStream for writing to a remote file. * * @param remotefile * @return OutputStream * @throws SftpStatusException * @throws SshException */ public OutputStream getOutputStream(String remotefile) throws SftpStatusException, SshException { String remotePath = resolveRemotePath(remotefile); return new SftpFileOutputStream(sftp.openFile(remotePath, SftpChannel.OPEN_CREATE | SftpChannel.OPEN_TRUNCATE | SftpChannel.OPEN_WRITE)); } /** * Upload the contents of an InputStream to the remote computer. * * @param in * @param remote * @param position * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException */ public void put(InputStream in, String remote, long position) throws SftpStatusException, SshException, TransferCancelledException { put(in, remote, null, position, -1); } /** * Upload the contents of an InputStream to the remote computer. * * @param in * @param remote * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException */ public void put(InputStream in, String remote) throws SftpStatusException, SshException, TransferCancelledException { put(in, remote, null, 0, -1); } /** *

* Sets the user ID to owner for the file or directory. *

* * @param uid numeric user id of the new owner * @param path the path to the remote file/directory * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * */ public void chown(String uid, String path) throws SftpStatusException, SshException { String actual = resolveRemotePath(path); SftpFileAttributes attrs = sftp.getAttributes(actual); SftpFileAttributes newAttrs = new SftpFileAttributes(attrs.getType(), sftp.getCharsetEncoding()); newAttrs.setUID(uid); if (sftp.getVersion() <= 3) { newAttrs.setGID(attrs.getGID()); } sftp.setAttributes(actual, newAttrs); } /** * Set the attributes of a remote path. * * @param path * @param attrs * @throws SftpStatusException * @throws SshException */ public void setAttributes(String path, SftpFileAttributes attrs) throws SftpStatusException, SshException { sftp.setAttributes(resolveRemotePath(path), attrs); } /** *

* Sets the user ID to owner for the file or directory. *

* * @param uid numeric user id of the new owner * @param path the path to the remote file/directory * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * */ public void chown(String uid, String gid, String path) throws SftpStatusException, SshException { String actual = resolveRemotePath(path); SftpFileAttributes attrs = sftp.getAttributes(actual); SftpFileAttributes newAttrs = new SftpFileAttributes(attrs.getType(), sftp.getCharsetEncoding()); newAttrs.setUID(uid); newAttrs.setGID(gid); sftp.setAttributes(actual, newAttrs); } /** *

* Sets the group ID for the file or directory. *

* * @param gid the numeric group id for the new group * @param path the path to the remote file/directory * * @throws SftpStatusException * @throws SshException */ public void chgrp(String gid, String path) throws SftpStatusException, SshException { String actual = resolveRemotePath(path); SftpFileAttributes attrs = sftp.getAttributes(actual); SftpFileAttributes newAttrs = new SftpFileAttributes(attrs.getType(), sftp.getCharsetEncoding()); newAttrs.setGID(gid); if (sftp.getVersion() <= 3) { newAttrs.setUID(attrs.getUID()); } sftp.setAttributes(actual, newAttrs); } /** *

* Changes the access permissions or modes of the specified file or directory. *

* *

* Modes determine who can read, change or execute a file. *

* * @param permissions the absolute mode of the file/directory * @param path the path to the file/directory on the remote server * * @see PosixPermissions * @see PosixPermissionsBuilder * @throws SftpStatusException * @throws SshException */ public void chmod(PosixPermissions permissions, String path) throws SftpStatusException, SshException { chmod(permissions.asInt(), path); } /** *

* Changes the access permissions or modes of the specified file or directory. *

* *

* Modes determine who can read, change or execute a file. *

*
* *
	 * Absolute modes are octal numbers specifying the complete list of
	 * attributes for the files; you specify attributes by OR'ing together
	 * these bits.
	 * 
	 * 0400       Individual read
	 * 0200       Individual write
	 * 0100       Individual execute (or list directory)
	 * 0040       Group read
	 * 0020       Group write
	 * 0010       Group execute
	 * 0004       Other read
	 * 0002       Other write
	 * 0001       Other execute
	 * 
* *
*

* Now deprecated, it is recommended {@link PosixPermissions} and {@link PosixPermissionsBuilder} be * used instead. *

* * @param permissions the absolute mode of the file/directory * @param path the path to the file/directory on the remote server * * @throws SftpStatusException * @throws SshException */ @Deprecated(since = "3.1.0") public void chmod(int permissions, String path) throws SftpStatusException, SshException { String actual = resolveRemotePath(path); sftp.changePermissions(actual, permissions); } /** * Sets the umask for this client.
*
* *
	 * To give yourself full permissions for both files and directories and
	 * prevent the group and other users from having access:
	 * 
	 *   umask("077");
	 * 
	 * This subtracts 077 from the system defaults for files and directories
	 * 666 and 777. Giving a default access permissions for your files of
	 * 600 (rw-------) and for directories of 700 (rwx------).
	 * 
	 * To give all access permissions to the group and allow other users read
	 * and execute permission:
	 * 
	 *   umask("002");
	 * 
	 * This subtracts 002 from the system defaults to give a default access permission
	 * for your files of 664 (rw-rw-r--) and for your directories of 775 (rwxrwxr-x).
	 * 
	 * To give the group and other users all access except write access:
	 * 
	 *   umask("022");
	 * 
	 * This subtracts 022 from the system defaults to give a default access permission
	 * for your files of 644 (rw-r--r--) and for your directories of 755 (rwxr-xr-x).
	 * 
* *
* * @param umask * @throws SshException */ public void umask(String umask) throws SshException { try { this.umask = Integer.parseInt(umask, 8); applyUmask = true; } catch (NumberFormatException ex) { throw new SshException("umask must be 4 digit octal number e.g. 0022", SshException.BAD_API_USAGE); } } /** * Rename a file on the remote computer, optionally using posix semantics that * allow files to be renamed even if the destination path exists. The server * must support [email protected] SFTP extension in order to use the * posix operation. * * @param oldpath * @param newpath * @param posix * @throws IOException * @throws SftpStatusException * @throws SshException */ public void rename(String oldpath, String newpath, boolean posix) throws IOException, SftpStatusException, SshException { if (posix) { ByteArrayWriter msg = new ByteArrayWriter(); try { msg.writeString(resolveRemotePath(oldpath)); msg.writeString(resolveRemotePath(newpath)); sftp.getOKRequestStatus( sftp.sendExtensionMessage("[email protected]", msg.toByteArray()), newpath); } finally { msg.close(); } } else { rename(oldpath, newpath); } } public void copyRemoteFile(String sourceFile, String destinationFile, boolean overwriteDestination) throws SftpStatusException, SshException, IOException { ByteArrayWriter msg = new ByteArrayWriter(); try { msg.writeString(resolveRemotePath(sourceFile)); msg.writeString(resolveRemotePath(destinationFile)); msg.writeBoolean(overwriteDestination); sftp.getOKRequestStatus(sftp.sendExtensionMessage("copy-file", msg.toByteArray()), destinationFile); } finally { msg.close(); } } /** * Copy remotely from one file to another (when supported by extension). * * @param sourceFile source file * @param fromOffset from offset * @param length length * @param destinationFile destination file * @param toOffset to offset * @throws SftpStatusException on SFTP error * @throws SshException on SSH error * @throws IOException on IO error * @deprecated * @see SftpHandle#copyTo(SftpHandle, UnsignedInteger64, UnsignedInteger64, UnsignedInteger64) */ @Deprecated(since = "3.1.0", forRemoval = true) public void copyRemoteData(SftpFile sourceFile, UnsignedInteger64 fromOffset, UnsignedInteger64 length, SftpFile destinationFile, UnsignedInteger64 toOffset) throws SftpStatusException, SshException, IOException { try(var srcHandle = sourceFile.openFile(SftpChannel.OPEN_READ)) { try(var destHandle = destinationFile.openFile(SftpChannel.OPEN_WRITE | SftpChannel.OPEN_CREATE)) { srcHandle.copyTo(destHandle, fromOffset, length, toOffset); } } } /** *

* Rename a file on the remote computer. *

* * @param oldpath the old path * @param newpath the new path * * @throws SftpStatusException * @throws SshException */ public void rename(String oldpath, String newpath) throws SftpStatusException, SshException { String from = resolveRemotePath(oldpath); String to = resolveRemotePath(newpath); SftpFileAttributes attrs = null; try { attrs = sftp.getAttributes(to); } catch (SftpStatusException ex) { sftp.renameFile(from, to); return; } if (attrs != null && attrs.isDirectory()) { sftp.renameFile(from, FileUtils.checkEndsWithSlash(to) + FileUtils.lastPathElement(from)); } else { throw new SftpStatusException(SftpStatusException.SSH_FX_FILE_ALREADY_EXISTS, newpath + " already exists on the remote filesystem"); } } /** *

* Remove a file or directory from the remote computer. *

* * @param path the path of the remote file/directory * * @throws SftpStatusException * @throws SshException */ public void rm(String path) throws SftpStatusException, SshException { String actual = resolveRemotePath(path); try { SftpFileAttributes attrs = sftp.getAttributes(actual); if (attrs.isDirectory()) { sftp.removeDirectory(actual); } else { sftp.removeFile(actual); } } catch(SftpStatusException sse) { if(sse.getStatus() == SftpStatusException.SSH_FX_NO_SUCH_FILE) { try { SftpFileAttributes linkAttrs = statLink(path); if(linkAttrs.isLink()) { sftp.removeFile(actual); } else throw sse; } catch(SftpStatusException sse2) { throw sse; // Intentional, actually throw original } } else throw sse; } } /** * Remove a file or directory on the remote computer with options to force * deletion of existing files and recursion. * * @param path * @param force * @param recurse * * @throws SftpStatusException * @throws SshException */ public void rm(String path, boolean force, boolean recurse) throws SftpStatusException, SshException { String actual = resolveRemotePath(path); SftpFileAttributes attrs = sftp.getAttributes(actual); SftpFile file; if (attrs.isDirectory()) { SftpFile[] list = ls(path); if (!force && (list.length > 0)) { throw new SftpStatusException(SftpStatusException.SSH_FX_FAILURE, "You cannot delete non-empty directory, use force=true to overide"); } for (int i = 0; i < list.length; i++) { file = list[i]; if (file.isDirectory() && !file.getFilename().equals(".") && !file.getFilename().equals("..")) { if (recurse) { rm(file.getAbsolutePath(), force, recurse); } else { throw new SftpStatusException(SftpStatusException.SSH_FX_FAILURE, "Directory has contents, cannot delete without recurse=true"); } } else if (file.isFile() || file.isLink()) { sftp.removeFile(file.getAbsolutePath()); } } sftp.removeDirectory(actual); } else { sftp.removeFile(actual); } } /** * Remove a directory, will fail if the directory has contents. * * @param path * * @throws SftpStatusException * @throws SshException */ public void rmdir(String path) throws SftpStatusException, SshException { String actual = resolveRemotePath(path); SftpFileAttributes attrs = sftp.getAttributes(actual); if (attrs.isDirectory()) { sftp.removeDirectory(actual); } else { throw new SftpStatusException(SftpStatusException.SSH_FX_NOT_A_DIRECTORY, "File is not a directory"); } } /** *

* Create a symbolic link on the remote computer. *

* * @param path the path to the existing file * @param link the new link * * @throws SftpStatusException * @throws SshException */ public void symlink(String path, String link) throws SftpStatusException, SshException { relativeSymlink(resolveRemotePath(path), link); } /** *

* Create a symbolic link on the remote computer. *

* * @param path the path to the existing file * @param link the new link * * @throws SftpStatusException * @throws SshException */ public void relativeSymlink(String path, String link) throws SftpStatusException, SshException { String actualLink = resolveRemotePath(link); sftp.createSymbolicLink(actualLink, path); } /** *

* Returns the attributes of the file from the remote computer. *

* * @param path the path of the file on the remote computer * * @return the attributes * * @throws SftpStatusException * @throws SshException */ public SftpFileAttributes stat(String path) throws SftpStatusException, SshException { String actual = resolveRemotePath(path); return sftp.getAttributes(actual); } /** *

* Returns the attributes of the link from the remote computer. *

* * @param path the path of the file on the remote computer * * @return the attributes * * @throws SftpStatusException * @throws SshException */ public SftpFileAttributes statLink(String path) throws SftpStatusException, SshException { String actual = resolveRemotePath(path); return sftp.getLinkAttributes(actual); } /** * Get the absolute path for a file. * * @param path * * @return String * * @throws SftpStatusException * @throws SshException */ public String getAbsolutePath(String path) throws SftpStatusException, SshException { String actual = resolveRemotePath(path); return sftp.getAbsolutePath(actual); } /** * Verify a local and remote file. Requires a minimum SFTP version of 5 and/or * support of the "md5-hash" extension * * @param localFile * @param remoteFile * @return * @throws SftpStatusException * @throws SshException * @throws IOException * @throws PermissionDeniedException */ public boolean verifyFiles(String localFile, String remoteFile) throws SftpStatusException, SshException, IOException, PermissionDeniedException { return verifyFiles(localFile, remoteFile, 0, 0); } public boolean verifyFiles(String localFile, String remoteFile, RemoteHash algorithm) throws SftpStatusException, SshException, IOException, PermissionDeniedException { return verifyFiles(localFile, remoteFile, 0, 0, algorithm); } /** * Verify a local and remote file. Requires a minimum SFTP version of 5 and/or * support of the "md5-hash" extension. * * @param localFile * @param remoteFile * @param offset * @param length * @return * @throws SftpStatusException * @throws SshException * @throws IOException * @throws PermissionDeniedException */ public boolean verifyFiles(String localFile, String remoteFile, long offset, long length) throws SftpStatusException, SshException, IOException, PermissionDeniedException { return verifyFiles(localFile, remoteFile, offset, length, RemoteHash.md5); } public boolean verifyFiles(String localFile, String remoteFile, long offset, long length, RemoteHash algorithm) throws SftpStatusException, SshException, IOException, PermissionDeniedException { AbstractFile local = resolveLocalPath(localFile); if (!local.exists()) { throw new IOException("Local file " + localFile + " does not exist!"); } try { MessageDigest md = null; switch (algorithm) { case md5: md = MessageDigest.getInstance(JCEAlgorithms.JCE_MD5); break; case sha1: md = MessageDigest.getInstance(JCEAlgorithms.JCE_SHA1); break; case sha256: md = MessageDigest.getInstance(JCEAlgorithms.JCE_SHA256); break; case sha512: md = MessageDigest.getInstance(JCEAlgorithms.JCE_SHA512); break; } byte[] remoteHash = getRemoteHash(remoteFile, offset, length, algorithm); if (Log.isDebugEnabled()) { Log.debug("Remote hash for {} is {}", remoteFile, Utils.bytesToHex(remoteHash)); } try(var dis = new DigestInputStream(local.getInputStream(), md)) { if (offset > 0) { dis.skip(offset); } if (length > 0) { IOUtils.copy(dis, OutputStream.nullOutputStream(), length); } else { IOUtils.copy(dis, OutputStream.nullOutputStream()); } } byte[] localHash = md.digest(); if (Log.isDebugEnabled()) { Log.debug("Local hash for {} is {}", localFile, Utils.bytesToHex(localHash)); } return Arrays.equals(remoteHash, localHash); } catch (NoSuchAlgorithmException e1) { throw new SshException(SshException.INTERNAL_ERROR, e1); } catch (IOException e1) { throw new SshException(SshException.INTERNAL_ERROR, e1); } } @Deprecated public byte[] getRemoteHash(String remoteFile) throws IOException, SftpStatusException, SshException { return getRemoteHash(remoteFile, 0, 0, new byte[0]); } @Deprecated public byte[] getRemoteHash(String remoteFile, long offset, long length, byte[] quickCheck) throws IOException, SftpStatusException, SshException { ByteArrayWriter msg = new ByteArrayWriter(); try { msg.writeString(resolveRemotePath(remoteFile)); msg.writeUINT64(offset); msg.writeUINT64(length); msg.writeBinaryString(quickCheck); SftpMessage resp = sftp.getExtensionResponse( sftp.sendExtensionMessage("md5-hash", msg.toByteArray()), remoteFile); resp.readString(); return resp.readBinaryString(); } finally { msg.close(); } } @Deprecated public byte[] getRemoteHash(byte[] handle) throws IOException, SftpStatusException, SshException { return getRemoteHash(handle, 0, 0, new byte[0]); } @Deprecated public byte[] getRemoteHash(byte[] handle, long offset, long length, byte[] quickCheck) throws IOException, SftpStatusException, SshException { return doMD5HashHandle(handle, offset, length, quickCheck); } public byte[] getRemoteHash(byte[] handle, RemoteHash algorithm) throws IOException, SftpStatusException, SshException { return getRemoteHash(handle, 0, 0, algorithm); } public byte[] getRemoteHash(byte[] handle, long offset, long length, RemoteHash algorithm) throws IOException, SftpStatusException, SshException { return doCheckHashHandle(handle, offset, length, algorithm); } public byte[] getRemoteHash(String path, RemoteHash algorithm) throws IOException, SftpStatusException, SshException { return getRemoteHash(path, 0, 0, algorithm); } public byte[] getRemoteHash(String path, long offset, long length, RemoteHash algorithm) throws IOException, SftpStatusException, SshException { String actual = resolveRemotePath(path); return doCheckFileHandle(actual, offset, length, algorithm); } protected byte[] doCheckHashHandle(byte[] handle, long offset, long length, RemoteHash algorithm) throws IOException, SftpStatusException, SshException { ByteArrayWriter msg = new ByteArrayWriter(); try { msg.writeBinaryString(handle); msg.writeString(algorithm.name()); msg.writeUINT64(offset); msg.writeUINT64(length); msg.writeInt(0L); SftpHandle h = sftp.getBestHandle(handle); return processCheckFileResponse( sftp.getExtensionResponse(sftp.sendExtensionMessage("check-file-handle", msg.toByteArray()), h.getFile().getAbsolutePath()), algorithm); } finally { msg.close(); } } protected byte[] doCheckFileHandle(String path, long offset, long length, RemoteHash algorithm) throws IOException, SftpStatusException, SshException { ByteArrayWriter msg = new ByteArrayWriter(); try { msg.writeString(path); msg.writeString(algorithm.name()); msg.writeUINT64(offset); msg.writeUINT64(length); msg.writeInt(0L); return processCheckFileResponse( sftp.getExtensionResponse(sftp.sendExtensionMessage("check-file-name", msg.toByteArray()), path), algorithm); } finally { msg.close(); } } protected byte[] processCheckFileResponse(SftpMessage resp, RemoteHash algorithm) throws IOException { String processedAlgorithm = resp.readString(); if (!processedAlgorithm.equals(algorithm.name())) { throw new IOException("Remote server returned a hash in an unsupported algorithm"); } int hashLength; switch (algorithm) { case md5: hashLength = 16; break; case sha1: hashLength = 20; break; case sha256: hashLength = 32; break; case sha512: hashLength = 64; break; default: throw new IOException("Unsupported hash algorihm " + processedAlgorithm); } byte[] hash = new byte[hashLength]; if (resp.available() < hash.length) { throw new IOException("Unexpected hash length returned by remote server"); } resp.readFully(hash); return hash; } protected byte[] doMD5HashHandle(byte[] handle, long offset, long length, byte[] quickCheck) throws IOException, SftpStatusException, SshException { ByteArrayWriter msg = new ByteArrayWriter(); try { msg.writeBinaryString(handle); msg.writeUINT64(offset); msg.writeUINT64(length); msg.writeBinaryString(quickCheck); SftpHandle h = sftp.getBestHandle(handle); SftpMessage resp = sftp .getExtensionResponse(sftp.sendExtensionMessage("md5-hash-handle", msg.toByteArray()), h.getFile().getAbsolutePath()); resp.readString(); return resp.readBinaryString(); } finally { msg.close(); } } /** *

* Close the SFTP client. *

* */ public void quit() throws SshException { sftp.close(); } /** *

* Close the SFTP client. *

* */ public void exit() throws SshException { sftp.close(); } /** * Copy the contents of a local directory into a remote directory. * * @param localdir the path to the local directory * @param remotedir the remote directory which will receive the contents * @param recurse recurse through child folders * @param sync synchronize the directories by removing files on the remote * server that do not exist locally * @param commit actually perform the operation. If false a * DirectoryOperation * will be returned so that the operation can be evaluated and * no actual files will be created/transfered. * @param progress * * @return DirectoryOperation * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException */ public DirectoryOperation putLocalDirectory(String localdir, String remotedir, boolean recurse, boolean sync, boolean commit, FileTransferProgress progress) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { DirectoryOperation op = new DirectoryOperation(); AbstractFile local = resolveLocalPath(localdir); remotedir = resolveRemotePath(remotedir); remotedir += (remotedir.endsWith("/") ? "" : "/"); // Setup the remote directory if were committing if (commit) { try { sftp.getAttributes(remotedir); } catch (SftpStatusException ex) { mkdirs(remotedir); } } // List the local files and verify against the remote server AbstractFile[] sources = listFiles(local); for (AbstractFile source : sources) { if (source.isDirectory() && !source.getName().equals(".") && !source.getName().equals("..")) { if (recurse) { // File f = new File(local, source.getName()); op.addDirectoryOperation(putLocalDirectory(source.getAbsolutePath(), remotedir + source.getName(), recurse, sync, commit, progress), source); } } else if (source.isFile()) { boolean newFile = false; boolean unchangedFile = false; try { SftpFileAttributes attrs = sftp.getAttributes(remotedir + source.getName()); unchangedFile = ((source.length() == attrs.getSize().longValue()) && ((source.lastModified() / 1000) == attrs.getModifiedTime().longValue())); System.out.println(source.getName() + " is " + (unchangedFile ? "unchanged" : "changed")); } catch (SftpStatusException ex) { System.out.println(source.getName() + " is new"); newFile = true; } try { if (commit && !unchangedFile) { // BPS - Added // !unChangedFile test. // Why would want to // copy that has been // determined to be // unchanged? put(source.getAbsolutePath(), remotedir + source.getName(), progress); SftpFileAttributes attrs = sftp.getAttributes(remotedir + source.getName()); attrs.setTimes(new UnsignedInteger64(source.lastModified() / 1000), new UnsignedInteger64(source.lastModified() / 1000)); sftp.setAttributes(remotedir + source.getName(), attrs); } if (unchangedFile) { op.addUnchangedFile(source); } else if (!newFile) { op.addUpdatedFile(source); } else { op.addNewFile(source); } } catch (SftpStatusException ex) { op.addFailedTransfer(source, ex); } } } if (sync) { // List the contents of the new remote directory and remove any // files/directories that were not updated try { SftpFile[] files = ls(remotedir); SftpFile file; AbstractFile f; for (int i = 0; i < files.length; i++) { file = files[i]; // Create a local file object to test for its existence f = local.resolveFile(file.getFilename()); if (!op.containsFile(f) && !file.getFilename().equals(".") && !file.getFilename().equals("..")) { op.addDeletedFile(file); if (commit) { if (file.isDirectory()) { // Recurse through the directory, deleting stuff recurseMarkForDeletion(file, op); if (commit) { rm(file.getAbsolutePath(), true, true); } } else if (file.isFile()) { rm(file.getAbsolutePath()); } } } } } catch (SftpStatusException ex2) { // Ignore since if it does not exist we cant delete it } } // Return the operation details return op; } private String[] getChildNames(AbstractFile local) throws IOException, PermissionDeniedException { List children = new ArrayList<>(); for (AbstractFile child : local.getChildren()) { children.add(child.getName()); } return children.toArray(new String[0]); } private void recurseMarkForDeletion(SftpFile file, DirectoryOperation op) throws SftpStatusException, SshException { SftpFile[] list = ls(file.getAbsolutePath()); op.addDeletedFile(file); for (int i = 0; i < list.length; i++) { file = list[i]; if (file.isDirectory() && !file.getFilename().equals(".") && !file.getFilename().equals("..")) { recurseMarkForDeletion(file, op); } else if (file.isFile()) { op.addDeletedFile(file); } } } private void recurseMarkForDeletion(AbstractFile file, DirectoryOperation op) throws SftpStatusException, SshException, IOException, PermissionDeniedException { String[] list = getChildNames(file); op.addDeletedFile(file); if (list != null) { for (int i = 0; i < list.length; i++) { file = file.resolveFile(list[i]); if (file.isDirectory() && !file.getName().equals(".") && !file.getName().equals("..")) { recurseMarkForDeletion(file, op); } else if (file.isFile()) { op.addDeletedFile(file); } } } } /** * Format a String with the details of the file.
* *
	 * -rwxr-xr-x   1 mjos     staff      348911 Mar 25 14:29 t-filexfer
	 * 
* *
* * @param file * @throws SftpStatusException * @throws SshException * @return String */ public static String formatLongname(SftpFile file) throws SftpStatusException, SshException { return formatLongname(file.getAttributes(), file.getFilename()); } /** * Format a String with the details of the file.
* *
	 * -rwxr-xr-x   1 mjos     staff      348911 Mar 25 14:29 t-filexfer
	 * 
* *
* * @param attrs * @param filename * @return String */ public static String formatLongname(SftpFileAttributes attrs, String filename) { return String.format("%9s %d %-9s %-9s %10d %12s %s", attrs.toPermissionsString(), attrs.linkCount(), attrs.bestUsername(), attrs.bestGroup(), attrs.size().longValue(), getModTimeString(attrs.lastModifiedTime()), filename); } private static String getModTimeString(FileTime mtime) { if (mtime == null) { return ""; } SimpleDateFormat df; long mt = mtime.toMillis(); long now = System.currentTimeMillis(); if ((now - mt) > (6 * 30 * 24 * 60 * 60 * 1000L)) { df = new SimpleDateFormat("MMM dd yyyy"); } else { df = new SimpleDateFormat("MMM dd hh:mm"); } return df.format(new Date(mt)); } /** * Copy the contents of a remote directory to a local directory * * @param remotedir the remote directory whose contents will be copied. * @param localdir the local directory to where the contents will be copied * @param recurse recurse into child folders * @param sync synchronized the directories by removing files and * directories that do not exist on the remote server. * @param commit actually perform the operation. If false the * operation will be processed and a * DirectoryOperation * will be returned without actually transfering any files. * @param progress * * @return DirectoryOperation * * @throws IOException * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException */ public DirectoryOperation getRemoteDirectory(String remotedir, String localdir, boolean recurse, boolean sync, boolean commit, FileTransferProgress progress) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { // Create an operation object to hold the information DirectoryOperation op = new DirectoryOperation(); // Record the previous working directoies String pwd = pwd(); // String lpwd = lpwd(); cd(remotedir); // Setup the local cwd String base = remotedir; if (base.endsWith("/")) base = base.substring(0, base.length() - 1); int idx = base.lastIndexOf('/'); if (idx != -1) { base = base.substring(idx + 1); } AbstractFile local = resolveLocalPath(localdir); if (!local.exists() && commit) { local.createFolder(); } SftpFile[] files = ls(); SftpFile file; AbstractFile f; for (int i = 0; i < files.length; i++) { file = files[i]; if (file.isDirectory() && !file.getFilename().equals(".") && !file.getFilename().equals("..")) { if (recurse) { f = local.resolveFile(file.getFilename()); op.addDirectoryOperation(getRemoteDirectory(file.getFilename(), local.getAbsolutePath() + "/" + file.getFilename(), recurse, sync, commit, progress), f); } } else if (file.isFile()) { f = local.resolveFile(file.getFilename()); if (f.exists() && (f.length() == file.getAttributes().getSize().longValue()) && ((f.lastModified() / 1000) == file.getAttributes().getModifiedTime().longValue())) { if (commit) { op.addUnchangedFile(f); } else { op.addUnchangedFile(file); } continue; } try { if (f.exists()) { if (commit) { op.addUpdatedFile(f); } else { op.addUpdatedFile(file); } } else { if (commit) { op.addNewFile(f); } else { op.addNewFile(file); } } if (commit) { // Get the file get(file.getFilename(), f.getAbsolutePath(), progress); } } catch (SftpStatusException ex) { op.addFailedTransfer(f, ex); } } } if (sync) { // List the contents of the new local directory and remove any // files/directories that were not updated String[] contents = getChildNames(local); AbstractFile f2; if (contents != null) { for (int i = 0; i < contents.length; i++) { f2 = local.resolveFile(contents[i]); if (!op.containsFile(f2)) { op.addDeletedFile(f2); if (f2.isDirectory() && !f2.getName().equals(".") && !f2.getName().equals("..")) { recurseMarkForDeletion(f2, op); if (commit) { f2.delete(true); } } else if (commit) { f2.delete(false); } } } } } cd(pwd); return op; } /** *

* Download the remote files to the local computer *

* *

* When RegExpSyntax is set to NoSyntax the getFiles() methods act identically * to the get() methods except for a different return type. *

* *

* When RegExpSyntax is set to GlobSyntax or Perl5Syntax, getFiles() treats * 'remote' as a regular expression, and gets all the files in 'remote''s parent * directory that match the pattern. The default parent directory of remote is * the remote cwd unless 'remote' contains file seperators(/). *

* *

* Examples can be found in SftpConnect.java * *

* Code Example:

* *
	 * // change reg exp syntax from default SftpClient.NoSyntax (no reg exp matching)
	 * // to SftpClient.GlobSyntax
	 * sftp.setRegularExpressionSyntax(SftpClient.GlobSyntax);
	 * // get all .doc files with 'rfc' in their names, in the 'docs/unsorted/' folder
	 * // relative to the remote cwd, and copy them to the local cwd.
	 * sftp.getFiles("docs/unsorted/*rfc*.doc");
	 * 
* *
*

* * @param remote the regular expression path to the remote file * * @return the downloaded files' attributes * * @throws IOException * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException */ public SftpFile[] getFiles(String remote) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { return getFiles(remote, (FileTransferProgress) null); } /** *

* Download the remote files to the local computer * * @param remote the regular expression path to the remote file * @param resume attempt to resume an interrupted download * * @return the downloaded files' attributes * * @throws IOException * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException */ public SftpFile[] getFiles(String remote, boolean resume) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { return getFiles(remote, (FileTransferProgress) null, resume); } /** *

* Download the remote files to the local computer. *

* * @param remote the regular expression path to the remote file * @param progress * * @return SftpFile[] * * @throws IOException * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException */ public SftpFile[] getFiles(String remote, FileTransferProgress progress) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { return getFiles(remote, progress, false); } /** *

* Download the remote files to the local computer. *

* * @param remote the regular expression path to the remote file * @param progress * @param resume attempt to resume a interrupted download * * @return SftpFile[] * * @throws IOException * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException */ public SftpFile[] getFiles(String remote, FileTransferProgress progress, boolean resume) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { return getFiles(remote, lcwd.getAbsolutePath(), progress, resume); } /** * Download the remote files into the local file. * * @param remote * @param local * * @return SftpFile[] * * @throws IOException * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException */ public SftpFile[] getFiles(String remote, String local) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { return getFiles(remote, local, false); } /** * Download the remote files into the local file. * * @param remote * @param local * @param resume attempt to resume an interrupted download * * @return SftpFile[] * * @throws IOException * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException */ public SftpFile[] getFiles(String remote, String local, boolean resume) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { return getFiles(remote, local, null, resume); } /** *

* Download the remote file to the local computer. If the paths provided are not * absolute the current working directory is used. *

* * @param remote the regular expression path/name of the remote files * @param local the path/name to place the file on the local computer * @param progress * * @return SftpFile[] * * @throws SftpStatusException * @throws IOException * @throws SshException * @throws TransferCancelledException * @throws PermissionDeniedException */ public SftpFile[] getFiles(String remote, String local, FileTransferProgress progress, boolean resume) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { return getFileMatches(remote, local, progress, resume); } /** *

* Upload the contents of an InputStream to the remote computer. *

* *

* When RegExpSyntax is set to NoSyntax the putFiles() methods act identically * to the put() methods except for a different return type. *

* *

* When RegExpSyntax is set to GlobSyntax or Perl5Syntax, putFiles() treats * 'local' as a regular expression, and gets all the files in 'local''s parent * directory that match the pattern. The default parent directory of local is * the local cwd unless 'local' contains file seperators. *

* *

* Examples can be found in SftpConnect.java * *

* Code Example:

* *
	 * // change reg exp syntax from default SftpClient.NoSyntax (no reg exp matching)
	 * // to SftpClient.GlobSyntax
	 * sftp.setRegularExpressionSyntax(SftpClient.GlobSyntax);
	 * // put all .doc files with 'rfc' in their names, in the 'docs/unsorted/' folder
	 * // relative to the local cwd, and copy them to the remote cwd.
	 * sftp.putFiles("docs/unsorted/*rfc*.doc");
	 * 
* *
*

* * @param local * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws IOException * @throws PermissionDeniedException */ public void putFiles(String local) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { putFiles(local, false); } /** * Upload files to the remote computer * * @param local * @param resume attempt to resume after an interrupted transfer * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws IOException * @throws PermissionDeniedException */ public void putFiles(String local, boolean resume) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { putFiles(local, (FileTransferProgress) null, resume); } /** *

* Upload files to the remote computer *

* * @param local the regular expression path/name of the local files * @param progress * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws IOException * @throws PermissionDeniedException */ public void putFiles(String local, FileTransferProgress progress) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { putFiles(local, progress, false); } /** *

* Upload files to the remote computer *

* * @param local the regular expression path/name of the local files * @param progress * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws IOException * @throws PermissionDeniedException */ public void putFiles(String local, FileTransferProgress progress, boolean resume) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { putFiles(local, pwd(), progress, resume); } /** * Upload files to the remote computer * * @param local * @param remote * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws IOException * @throws PermissionDeniedException */ public void putFiles(String local, String remote) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { putFiles(local, remote, null, false); } /** * Upload files to the remote computer * * @param local * @param remote * @param resume attempt to resume after an interrupted transfer * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws IOException * @throws PermissionDeniedException */ public void putFiles(String local, String remote, boolean resume) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { putFiles(local, remote, null, resume); } /** *

* Upload files to the remote computer. If the paths provided are not absolute * the current working directory is used. *

* * @param local the regular expression path/name of the local files * @param remote the path/name of the destination file * @param progress * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws IOException * @throws PermissionDeniedException */ public void putFiles(String local, String remote, FileTransferProgress progress) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { putFiles(local, remote, progress, false); } /** * make local copies of some of the variables, then call putfilematches, which * calls "put" on each file that matches the regexp local. * * @param local the regular expression path/name of the local files * @param remote the path/name of the destination file * @param progress * @param resume attempt to resume after an interrupted transfer * * @throws SftpStatusException * @throws SshException * @throws TransferCancelledException * @throws IOException * @throws PermissionDeniedException */ public void putFiles(String local, String remote, FileTransferProgress progress, boolean resume) throws IOException, SftpStatusException, SshException, TransferCancelledException, PermissionDeniedException { putFileMatches(local, remote, progress, resume); } /** * A simple wrapper class to provide an OutputStream to a RandomAccessFile * * */ static class RandomAccessFileOutputStream extends OutputStream { RandomAccessFile file; RandomAccessFileOutputStream(RandomAccessFile file) { this.file = file; } public void write(int b) throws IOException { file.write(b); } public void write(byte[] buf, int off, int len) throws IOException { file.write(buf, off, len); } public void close() throws IOException { file.close(); } } class DirectoryIterator implements Iterator { SftpHandle currentFolder; Vector currentPage = new Vector(); Iterator currentIterator; DirectoryIterator(String path) throws SftpStatusException, SshException { String actual = resolveRemotePath(path); if (Log.isDebugEnabled()) Log.debug("Listing files for " + actual); currentFolder = sftp.openDirectory(actual); try { getNextPage(); } catch (EOFException e) { } } private void getNextPage() throws SftpStatusException, SshException, EOFException { currentPage.clear(); int ret = currentFolder.listChildren(currentPage); if (ret == -1) { currentIterator = null; throw new EOFException(); } currentIterator = currentPage.iterator(); } @Override public boolean hasNext() { if (currentIterator != null && currentIterator.hasNext()) { return true; } return false; } @Override public SftpFile next() { if (currentIterator == null) { throw new NoSuchElementException(); } SftpFile ret = null; if (currentIterator.hasNext()) { ret = currentIterator.next(); } if (!currentIterator.hasNext()) { try { getNextPage(); } catch (EOFException e) { if (ret == null) { throw new NoSuchElementException(); } } catch (SftpStatusException | SshException e) { throw new NoSuchElementException(e.getMessage()); } if (ret == null) { ret = currentIterator.next(); } } return ret; } } public boolean isConnected() { return sftp.isClosed(); } public void hardlink(String src, String dst) throws SshException, SftpStatusException { try (ByteArrayWriter msg = new ByteArrayWriter()) { msg.writeString(src); msg.writeString(dst); SftpChannel channel = getSubsystemChannel(); UnsignedInteger32 requestId = channel.sendExtensionMessage("[email protected]", msg.toByteArray()); channel.getOKRequestStatus(requestId, dst); } catch (IOException e) { throw new SshException(e); } } public String getHomeDirectory(String username) throws SshException, SftpStatusException { try (ByteArrayWriter msg = new ByteArrayWriter()) { msg.writeString(username); SftpChannel channel = getSubsystemChannel(); UnsignedInteger32 requestId = channel.sendExtensionMessage("home-directory", msg.toByteArray()); return channel.getSingleFileResponse(channel.getResponse(requestId), "SSH_FXP_NAME", "", requestId).getAbsolutePath(); } catch (IOException e) { throw new SshException(e); } } public String makeTemporaryFolder() throws SshException, SftpStatusException { SftpChannel channel = getSubsystemChannel(); UnsignedInteger32 requestId = channel.sendExtensionMessage("make-temp-folder", null); return channel.getSingleFileResponse(channel.getResponse(requestId), "SSH_FXP_NAME", "", requestId).getAbsolutePath(); } public String getTemporaryFolder() throws SshException, SftpStatusException { SftpChannel channel = getSubsystemChannel(); UnsignedInteger32 requestId = channel.sendExtensionMessage("get-temp-folder", null); return channel.getSingleFileResponse(channel.getResponse(requestId), "SSH_FXP_NAME", "", requestId).getAbsolutePath(); } public StatVfs statVFS(String path) throws SshException, SftpStatusException { try (ByteArrayWriter msg = new ByteArrayWriter()) { msg.writeString(path); SftpChannel channel = getSubsystemChannel(); UnsignedInteger32 requestId = channel.sendExtensionMessage("[email protected]", msg.toByteArray()); SftpMessage response = channel.getResponse(requestId); if (response.getType() == SftpChannel.SSH_FXP_STATUS) { sftp.processStatusResponse(response, path, requestId); throw new IllegalStateException("Received unexpected SSH_FX_OK in status response!"); } else { return new StatVfs(response); } } catch (IOException e) { throw new SshException(e); } } public String getHome() throws SftpStatusException, SshException { return getAbsolutePath(""); } @Override public void close() throws IOException { try { this.quit(); } catch (SshException e) { throw new SshIOException(e); } } public FileVisitResult visit(String path, FileVisitor visitor) throws SshException, SftpStatusException { SftpFileAttributes attrs = stat(path); SftpFile file = new SftpFile(path, attrs, sftp, null); try { if (attrs.isDirectory()) { FileVisitResult preVisitResult = visitor.preVisitDirectory(file, fileToBasicAttributes(file)); try { if (preVisitResult != FileVisitResult.CONTINUE) return preVisitResult; for (SftpFile child : ls(path)) { if (child.isLink() || child.isFile()) { FileVisitResult fileVisitResult = visitor.visitFile(child, fileToBasicAttributes(child)); if (fileVisitResult != FileVisitResult.CONTINUE && fileVisitResult != FileVisitResult.SKIP_SUBTREE) return fileVisitResult; } else if (child.isDirectory() && !child.getFilename().equals(".") && !child.getFilename().equals("..")) { switch (visit(child.getAbsolutePath(), visitor)) { case SKIP_SIBLINGS: break; case TERMINATE: return FileVisitResult.TERMINATE; default: continue; } } } FileVisitResult postVisitResult = visitor.postVisitDirectory(file, null); if (postVisitResult != FileVisitResult.CONTINUE && postVisitResult != FileVisitResult.SKIP_SUBTREE) return postVisitResult; } catch (SftpStatusException ioe) { FileVisitResult postVisitResult = visitor.postVisitDirectory(file, new IOException(ioe)); if (postVisitResult != FileVisitResult.CONTINUE && postVisitResult != FileVisitResult.SKIP_SUBTREE) return postVisitResult; } } else { FileVisitResult fileVisitResult = visitor.visitFile(file, fileToBasicAttributes(file)); if (fileVisitResult != FileVisitResult.CONTINUE && fileVisitResult != FileVisitResult.SKIP_SUBTREE) return fileVisitResult; } } catch (IOException ioe) { throw new SshException(ioe); } return FileVisitResult.CONTINUE; } private BasicFileAttributes fileToBasicAttributes(SftpFile file) { var attrs = checkAttributes(file); return new BasicFileAttributes() { @Override public FileTime creationTime() { return attrs.createTimeOr().orElse(FileTime.fromMillis(0)); } @Override public Object fileKey() { return attrs; } @Override public boolean isDirectory() { return attrs.isDirectory(); } @Override public boolean isOther() { return !attrs.isDirectory() && !attrs.isFile() && !attrs.isLink(); } @Override public boolean isRegularFile() { return attrs.isFile(); } @Override public boolean isSymbolicLink() { return attrs.isLink(); } @Override public FileTime lastAccessTime() { return attrs.lastAccessTime(); } @Override public FileTime lastModifiedTime() { return attrs.lastModifiedTime(); } @Override public long size() { return attrs.size().longValue(); } }; } private SftpFileAttributes checkAttributes(SftpFile file) { try { return file.getAttributes(); } catch (SftpStatusException | SshException e) { throw new IllegalStateException(e.getMessage(), e); } } }