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

org.vngx.jsch.ChannelSftp Maven / Gradle / Ivy

/*
 * Copyright (c) 2002-2010 Atsuhiko Yamanaka, JCraft,Inc.  All rights reserved.
 * Copyright (c) 2010-2011 Michael Laudati, N1 Concepts LLC.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The names of the authors may not be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
 * INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.vngx.jsch;

import static org.vngx.jsch.constants.ConnectionProtocol.*;
import static org.vngx.jsch.constants.SftpProtocol.*;

import org.vngx.jsch.exception.JSchException;
import org.vngx.jsch.exception.SftpException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Implementation of ChannelSession for opening a SFTP channel
 * which runs as a subsystem for "sftp".
 *
 * This protocol provides secure file transfer (and more generally file system
 * access). It is designed so that it could be used to implement a secure remote
 * file system service as well as a secure file transfer service.
 *
 * In general, this protocol follows a simple request-response model. Each
 * request and response contains a sequence number and multiple requests may be
 * pending simultaneously.  There are a relatively large number of different
 * request messages, but a small number of possible response messages.  Each
 * request has one or more response messages that may be returned in result
 * (e.g., a read either returns data or reports error status).
 *
 * The SSH File Transfer Protocol has changed over time, before it's
 * standardization.  The following is a description of the incompatible changes
 * between different versions.
 *   10.1 Changes between versions 3 and 2
 *		- The SSH_FXP_READLINK and SSH_FXP_SYMLINK messages were added.
 *		- The SSH_FXP_EXTENDED and SSH_FXP_EXTENDED_REPLY messages were added.
 *		- The SSH_FXP_STATUS message was changed to include fields `error
 *			message' and `language tag'.
 *   10.2 Changes between versions 2 and 1
 *		- The SSH_FXP_RENAME message was added.
 *   10.3 Changes between versions 1 and 0
 *		- Implementation changes, no actual protocol changes.
 *
 * TODO For performance, since SFTP channel is not thread-safe/synchronized,
 * instance variables should be used to cache state to reduce the amount of
 * redundant traffic and object creation; for instance, a single header instance
 * could be reused across a the entire SFTP session.
 *
 * 

Note: This class is not thread-safe and should be externally synchronized. * * @author Atsuhiko Yamanaka * @author Michael Laudati */ public final class ChannelSftp extends ChannelSession { /** Version of SFTP client sent to server during initialization. */ private static final int CLIENT_VERSION = 3; /** Maximum message length in bytes. */ private static final int MAX_MSG_LENGTH = 256 * 1024; /** Constant boolean to indicate if file separator is the back slash. */ private static final boolean FS_IS_BS = (byte) File.separatorChar == '\\'; /** Constant string for character set for UTF-8. */ private static final String UTF8 = "UTF-8"; public static final int OVERWRITE = 0; public static final int RESUME = 1; public static final int APPEND = 2; /** Buffer for reading and writing SFTP traffic. */ private Buffer _buffer; /** Packet used for sending SFTP outbound traffic. */ private Packet _packet; /** Header instance to reuse for reading responses for performance. */ private final Header _header = new Header(); /** Server version received during start of SFTP session. */ private int _serverVersion; /** Extensions received from the server. */ private Map _extensions; /** Input stream for SFTP channel to read incoming channel data. */ private InputStream _io_in; /** Current sequence number of SFTP packet sent to server. */ private int _seq = 1; /** Filename encoding to use when converting Strings/byte[]. */ private String _fileEncoding = UTF8; /** True if filename encoding is UTF-8. */ private boolean _utf8 = true; /** Remote home directory for user. */ private String _home; /** Remote current working directory. */ private String _cwd; /** Local current working directory. */ private String _lcwd; /** * Creates a new instance of ChannelSftp. * * @param session */ ChannelSftp(Session session) { super(session, ChannelType.SFTP); } @Override public void start() throws JSchException { try { PipedOutputStream pos = new PipedOutputStream(); _io.setOutputStream(pos); _io.setInputStream(new PipedInputStream(pos, 32 * 1024)); // TODO make pipe size configurable _io_in = _io.in; if( _io_in == null ) { throw new JSchException("Channel is down"); } new RequestSftp().request(_session, this); _buffer = new Buffer(_remoteMaxPacketSize); _packet = new Packet(_buffer); // send SSH_FXP_INIT sendINIT(); // receive SSH_FXP_VERSION readHeader(); if( _header.length > MAX_MSG_LENGTH ) { throw new SftpException(SSH_FX_FAILURE, "Received message is too long: " + _header.length); } _serverVersion = _header.rid; // Retrieve version from header if( _header.length > 0 ) { _extensions = new HashMap(); fill(_buffer, _header.length); // read in extension data byte[] extensionName, extensionData; while( _header.length > 0 ) { extensionName = _buffer.getString(); extensionData = _buffer.getString(); _header.length -= 4 + extensionName.length + 4 + extensionData.length; _extensions.put(Util.byte2str(extensionName), Util.byte2str(extensionData)); } } // Set local current working directory and home directory after connecting _lcwd = new File(".").getCanonicalPath(); // TODO Should be configurable location _home = Util.byte2str(_realpath(""), _fileEncoding); _cwd = _home; } catch(JSchException e) { throw e; } catch(Exception e) { throw new JSchException("Failed to start ChannelSftp", e); } } /** * Quits the SFTP session and closes the channel. (Same as * exit()). */ public void quit() { disconnect(); } /** * Exits the SFTP session and closes the channel. (Same as * quit()) */ public void exit() { disconnect(); } /** * Changes the local current working directory to the specified path. * * @param path to set as local current working directory * @throws SftpException if any errors occur or path is not valid */ public void lcd(String path) throws SftpException { path = localAbsolutePath(path); if( !new File(path).isDirectory() ) { throw new SftpException(SSH_FX_NO_SUCH_FILE, "Failed to lcd, directory does not exist: "+path); } try { path = new File(path).getCanonicalPath(); } catch(Exception e) { /* Ignore error. */ } _lcwd = path; } /** * Changes the remote current working directory to the specified path. * * @param path to set as remote current working directory * @throws SftpException if any errors occur of path is not valid */ public void cd(String path) throws SftpException { try { path = isUnique(remoteAbsolutePath(path)); byte[] realPath = _realpath(path); SftpATTRS attr = _stat(realPath); if( (attr.getFlags() & SSH_FILEXFER_ATTR_PERMISSIONS) == 0 ) { throw new SftpException(SSH_FX_FAILURE, "Failed to cd (permission denied): " + path); } else if( !attr.isDir() ) { throw new SftpException(SSH_FX_FAILURE, "Failed to cd (not a directory): " + path); } _cwd = Util.byte2str(realPath, _fileEncoding); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to cd path: "+path, e); } } public void put(String src, String dst) throws SftpException { put(src, dst, null, OVERWRITE); } public void put(String src, String dst, int mode) throws SftpException { put(src, dst, null, mode); } public void put(String src, String dst, SftpProgressMonitor monitor) throws SftpException { put(src, dst, monitor, OVERWRITE); } public void put(String src, String dst, SftpProgressMonitor monitor, int mode) throws SftpException { src = localAbsolutePath(src); dst = remoteAbsolutePath(dst); try { List matches = globRemote(dst); if( matches.size() != 1 ) { if( matches.isEmpty() ) { if( isPattern(dst) ) { throw new SftpException(SSH_FX_FAILURE, "Invalid destination for put: "+dst); } else { dst = Util.unquote(dst); } } throw new SftpException(SSH_FX_FAILURE, "Destination is not unique: "+matches); } else { dst = matches.get(0); } boolean isRemoteDir = isRemoteDir(dst); matches = globLocal(src); StringBuffer dstsb = null; if( isRemoteDir ) { if( !dst.endsWith("/") ) { dst += "/"; } dstsb = new StringBuffer(dst); } else if( matches.size() > 1 ) { throw new SftpException(SSH_FX_FAILURE, "Copying multiple files, but the destination is missing or a file."); } String _dst; for( String _src : matches ) { if( isRemoteDir ) { int i = _src.lastIndexOf(File.separatorChar); if( FS_IS_BS ) { int ii = _src.lastIndexOf('/'); if( ii != -1 && ii > i ) { i = ii; } } if( i == -1 ) { dstsb.append(_src); } else { dstsb.append(_src.substring(i + 1)); } _dst = dstsb.toString(); dstsb.delete(dst.length(), _dst.length()); } else { _dst = dst; } long sizeOfDest = 0; if( mode == RESUME ) { try { sizeOfDest = _stat(_dst).getSize(); } catch(Exception eee) { // TODO Error handling? } long sizeOfSrc = new File(_src).length(); if( sizeOfSrc < sizeOfDest ) { throw new SftpException(SSH_FX_FAILURE, "failed to resume for " + _dst); } else if( sizeOfSrc == sizeOfDest ) { return; } } if( monitor != null ) { monitor.init(SftpProgressMonitor.PUT, _src, _dst, (new File(_src)).length()); if( mode == RESUME ) { monitor.count(sizeOfDest); } } FileInputStream fis = null; try { _put(fis = new FileInputStream(_src), _dst, monitor, mode); } finally { if( fis != null ) { try { fis.close(); } catch(IOException ie) { /* Ignore error. */ } } } } } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to put: "+src, e); } } public void put(InputStream src, String dst) throws SftpException { put(src, dst, null, OVERWRITE); } public void put(InputStream src, String dst, int mode) throws SftpException { put(src, dst, null, mode); } public void put(InputStream src, String dst, SftpProgressMonitor monitor) throws SftpException { put(src, dst, monitor, OVERWRITE); } public void put(InputStream src, String dst, SftpProgressMonitor monitor, int mode) throws SftpException { try { dst = remoteAbsolutePath(dst); List matches = globRemote(dst); if( matches.size() != 1 ) { if( matches.isEmpty() ) { if( isPattern(dst) ) { throw new SftpException(SSH_FX_FAILURE, "Invalid destination for put: "+dst); } else { dst = Util.unquote(dst); } } throw new SftpException(SSH_FX_FAILURE, "Destination is not unique: "+matches); } else { dst = matches.get(0); } if( isRemoteDir(dst) ) { throw new SftpException(SSH_FX_FAILURE, dst + " is a directory"); } _put(src, dst, monitor, mode); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to put: "+e, e); } } private void _put(InputStream src, String dst, SftpProgressMonitor monitor, int mode) throws SftpException { try { byte[] dstb = Util.str2byte(dst, _fileEncoding); long skip = 0; if( mode == RESUME || mode == APPEND ) { try { skip = _stat(dstb).getSize(); } catch(Exception eee) { // TODO Error handling? //System.err.println(eee); } } if( mode == RESUME && skip > 0 ) { long skipped = src.skip(skip); if( skipped < skip ) { throw new SftpException(SSH_FX_FAILURE, "failed to resume for " + dst); } } if( mode == OVERWRITE ) { sendOPENW(dstb); } else { sendOPENA(dstb); } readResponse(); if( _header.type != SSH_FXP_HANDLE ) { throw new SftpException(SSH_FX_FAILURE, "Invalid FXP response: "+_header.type); } byte[] handle = _buffer.getString(); // handle byte[] data = null; boolean dontcopy = true; // WHA?!! if( !dontcopy ) { data = new byte[_buffer.buffer.length - (5 + 13 + 21 + handle.length + (32 + 20 /* padding and mac */))]; } long offset = 0; if( mode == RESUME || mode == APPEND ) { offset += skip; } int startid = _seq; int ackid = _seq; int ackcount = 0; while( true ) { int nread = 0; int s = 0; int datalen = 0; int count = 0; if( !dontcopy ) { datalen = data.length - s; } else { data = _buffer.buffer; s = 5 + 13 + 21 + handle.length; datalen = _buffer.buffer.length - s - 32 - 20; // padding and mac } do { nread = src.read(data, s, datalen); if( nread > 0 ) { s += nread; datalen -= nread; count += nread; } } while( datalen > 0 && nread > 0 ); if( count <= 0 ) { break; } int _i = count; while( _i > 0 ) { _i -= sendWRITE(handle, offset, data, 0, _i); if( (_seq - 1) == startid || _io_in.available() >= 1024 ) { while( _io_in.available() > 0 ) { ackid = readResponseOk(); if( startid > ackid || ackid > _seq - 1 ) { if( ackid == _seq ) { System.err.println("ack error: startid=" + startid + " seq=" + _seq + " _ackid=" + ackid); } else { throw new SftpException(SSH_FX_FAILURE, "ack error: startid=" + startid + " seq=" + _seq + " _ackid=" + ackid); } } ackcount++; } } } offset += count; if( monitor != null && !monitor.count(count) ) { break; } } int _ackcount = _seq - startid; while( _ackcount > ackcount ) { readResponseOk(); ackcount++; } if( monitor != null ) { monitor.end(); } _sendCLOSE(handle); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to put: "+dst, e); } } public OutputStream put(String dst) throws SftpException { return put(dst, (SftpProgressMonitor) null, OVERWRITE); } public OutputStream put(String dst, int mode) throws SftpException { return put(dst, (SftpProgressMonitor) null, mode); } public OutputStream put(String dst, SftpProgressMonitor monitor, int mode) throws SftpException { return put(dst, monitor, mode, 0); } public OutputStream put(String dst, SftpProgressMonitor monitor, int mode, long offset) throws SftpException { try { dst = isUnique(remoteAbsolutePath(dst)); if( isRemoteDir(dst) ) { throw new SftpException(SSH_FX_FAILURE, dst + " is a directory"); } byte[] dstb = Util.str2byte(dst, _fileEncoding); long skip = 0; if( mode == RESUME || mode == APPEND ) { try { skip = _stat(dstb).getSize(); } catch(Exception eee) { //System.err.println(eee); } } if( mode == OVERWRITE ) { sendOPENW(dstb); } else { sendOPENA(dstb); } readResponse(); if( _header.type != SSH_FXP_HANDLE ) { throw new SftpException(SSH_FX_FAILURE, "Invalid FXP response: "+_header.type); } if( mode == RESUME || mode == APPEND ) { offset += skip; } byte[] handle = _buffer.getString(); // handle return new PutOutputStream(handle, offset, monitor); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to put: "+dst, e); } } public void get(String src, String dst) throws SftpException { get(src, dst, null, OVERWRITE); } public void get(String src, String dst, SftpProgressMonitor monitor) throws SftpException { get(src, dst, monitor, OVERWRITE); } public void get(String src, String dst, SftpProgressMonitor monitor, int mode) throws SftpException { src = remoteAbsolutePath(src); dst = localAbsolutePath(dst); boolean dstExists = false, error = false; String _dst = null; try { List matches = globRemote(src); if( matches.isEmpty() ) { throw new SftpException(SSH_FX_NO_SUCH_FILE, "No such file: "+src); } File dstFile = new File(dst); boolean isDstDir = dstFile.isDirectory(); StringBuffer dstsb = null; if( isDstDir ) { if( !dst.endsWith(File.separator) ) { dst += File.separator; } dstsb = new StringBuffer(dst); } else if( matches.size() > 1 ) { throw new SftpException(SSH_FX_FAILURE, "Copying multiple files, but destination is missing or a file."); } for( String _src : matches ) { SftpATTRS attr = _stat(_src); if( attr.isDir() ) { throw new SftpException(SSH_FX_FAILURE, "Not supported to get directory " + _src); } _dst = null; if( isDstDir ) { int i = _src.lastIndexOf('/'); if( i == -1 ) { dstsb.append(_src); } else { dstsb.append(_src.substring(i + 1)); } _dst = dstsb.toString(); dstsb.delete(dst.length(), _dst.length()); } else { _dst = dst; } File _dstFile = new File(_dst); if( mode == RESUME ) { long sizeOfSrc = attr.getSize(); long sizeOfDst = _dstFile.length(); if( sizeOfDst > sizeOfSrc ) { throw new SftpException(SSH_FX_FAILURE, "Failed to resume for " + _dst); } else if( sizeOfDst == sizeOfSrc ) { return; // Nothing to resume, already have full file } } if( monitor != null ) { monitor.init(SftpProgressMonitor.GET, _src, _dst, attr.getSize()); if( mode == RESUME ) { monitor.count(_dstFile.length()); } } FileOutputStream fos = null; dstExists = _dstFile.exists(); try { fos = new FileOutputStream(_dst, mode != OVERWRITE); _get(_src, fos, monitor, mode, new File(_dst).length()); } finally { if( fos != null ) { try { fos.close(); } catch(IOException ie) { /* Ignore error. */ } } } } } catch(SftpException e) { error = true; throw e; } catch(Exception e) { error = true; throw new SftpException(SSH_FX_FAILURE, "Failed to get src: "+src, e); } finally { if( error && !dstExists && _dst != null ) { File _dstFile = new File(_dst); if( _dstFile.exists() && _dstFile.length() == 0 ) { _dstFile.delete(); } } } } public void get(String src, OutputStream dst) throws SftpException { get(src, dst, null, OVERWRITE, 0); } public void get(String src, OutputStream dst, SftpProgressMonitor monitor) throws SftpException { get(src, dst, monitor, OVERWRITE, 0); } public void get(String src, OutputStream dst, SftpProgressMonitor monitor, int mode, long skip) throws SftpException { try { src = isUnique(remoteAbsolutePath(src)); if( monitor != null ) { monitor.init(SftpProgressMonitor.GET, src, "??", _stat(src).getSize()); if( mode == RESUME ) { monitor.count(skip); } } _get(src, dst, monitor, mode, skip); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to get src: "+src, e); } } private void _get(String src, OutputStream dst, SftpProgressMonitor monitor, int mode, long skip) throws SftpException { byte[] srcb = Util.str2byte(src, _fileEncoding); try { sendOPENR(srcb); readResponse(); if( _header.type != SSH_FXP_HANDLE ) { throw new SftpException(SSH_FX_FAILURE, "Invalid FXP response: "+_header.type); } byte[] handle = _buffer.getString(); // filename long offset = mode == RESUME ? skip : 0; int requestLen = 0; loop: while( true ) { requestLen = _buffer.buffer.length - 13; if( _serverVersion == 0 ) { requestLen = 1024; } sendREAD(handle, offset, requestLen); readHeader(); if( _header.type == SSH_FXP_STATUS ) { fill(_buffer, _header.length); int i = _buffer.getInt(); if( i == SSH_FX_EOF ) { break loop; } throwStatusError(_buffer, i); } if( _header.type != SSH_FXP_DATA ) { break loop; } _buffer.rewind(); fill(_buffer.buffer, 0, 4); _header.length -= 4; int i = _buffer.getInt(); // length of data int foo = i; while( foo > 0 ) { int bar = foo > _buffer.buffer.length ? _buffer.buffer.length : foo; if( (i = _io_in.read(_buffer.buffer, 0, bar)) < 0 ) { break loop; } int bytesRead = i; dst.write(_buffer.buffer, 0, bytesRead); offset += bytesRead; foo -= bytesRead; if( monitor != null ) { if( !monitor.count(bytesRead) ) { while( foo > 0 ) { i = _io_in.read(_buffer.buffer, 0, (_buffer.buffer.length < foo ? _buffer.buffer.length : foo)); if( i <= 0 ) { break; } foo -= i; } break loop; } } } } dst.flush(); if( monitor != null ) { monitor.end(); } _sendCLOSE(handle); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to get src: "+src, e); } } public InputStream get(String src) throws SftpException { return get(src, null, 0L); } public InputStream get(String src, SftpProgressMonitor monitor) throws SftpException { return get(src, monitor, 0L); } public InputStream get(String src, SftpProgressMonitor monitor, long skip) throws SftpException { try { src = isUnique(remoteAbsolutePath(src)); byte[] srcb = Util.str2byte(src, _fileEncoding); if( monitor != null ) { monitor.init(SftpProgressMonitor.GET, src, "??", _stat(srcb).getSize()); } sendOPENR(srcb); readResponse(); if( _header.type != SSH_FXP_HANDLE ) { throw new SftpException(SSH_FX_FAILURE, "Invalid FXP response: "+_header.type); } byte[] handle = _buffer.getString(); // handle return new GetInputStream(skip, handle, monitor); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to get src: "+src, e); } } public List ls(String path) throws SftpException { try { path = remoteAbsolutePath(path); int index = path.lastIndexOf('/'); String dir = Util.unquote(path.substring(0, index + 1)); String sPattern = path.substring(index + 1); // If pattern has included '*' or '?', we need to convert // to UTF-8 string before globbing. byte[] bPattern = null; boolean wildcardPattern = isPattern(sPattern); if( wildcardPattern ) { bPattern = Util.str2byte(sPattern); // UTF-8 encoded } else { String upath = Util.unquote(path); if( _stat(upath).isDir() ) { bPattern = null; dir = upath; } else { // If we could generate longname by ourself, // we don't have to use openDIR. if( _utf8 ) { bPattern = Util.unquote(Util.str2byte(sPattern)); } else { sPattern = Util.unquote(sPattern); bPattern = Util.str2byte(sPattern, _fileEncoding); } } } sendOPENDIR(Util.str2byte(dir, _fileEncoding)); readResponse(); if( _header.type != SSH_FXP_HANDLE ) { throw new SftpException(SSH_FX_FAILURE, "Invalid FXP response: "+_header.type); } byte[] handle = _buffer.getString(); // handle List lsEntries = new ArrayList(); while( true ) { sendREADDIR(handle); readHeader(); if( _header.type != SSH_FXP_STATUS && _header.type != SSH_FXP_NAME ) { throw new SftpException(SSH_FX_FAILURE, "Invalid FXP response: "+_header.type); } else if( _header.type == SSH_FXP_STATUS ) { fill(_buffer, _header.length); int i = _buffer.getInt(); if( i == SSH_FX_EOF ) { break; } throwStatusError(_buffer, i); } _buffer.rewind(); fill(_buffer.buffer, 0, 4); _header.length -= 4; int count = _buffer.getInt(); _buffer.reset(); while( count > 0 ) { if( _header.length > 0 ) { _buffer.shift(); int j = (_buffer.buffer.length > (_buffer.index + _header.length)) ? _header.length : (_buffer.buffer.length - _buffer.index); int i = fill(_buffer.buffer, _buffer.index, j); _buffer.index += i; _header.length -= i; } byte[] bFilename = _buffer.getString(); String sFilename = Util.byte2str(bFilename, _fileEncoding); byte[] bLongname = _serverVersion <= 3 ? _buffer.getString() : null; SftpATTRS attrs = SftpATTRS.getATTR(_buffer); boolean found = false; if( bPattern == null ) { found = true; } else if( !wildcardPattern ) { found = Arrays.equals(bPattern, bFilename); } else { found = Util.glob(bPattern, _utf8 ? bFilename : Util.str2byte(sFilename, UTF8)); } if( found ) { // TODO: need to generate long name from attrs for sftp protocol 4(and later) String sLongname = bLongname == null ? (attrs.toString() + " " + sFilename) : Util.byte2str(bLongname, _fileEncoding); lsEntries.add(new LsEntry(sFilename, sLongname, attrs)); } count--; } } _sendCLOSE(handle); return lsEntries; } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to ls path: "+path, e); } } public String readlink(String path) throws SftpException { if( _serverVersion < 3 ) { throw new SftpException(SSH_FX_OP_UNSUPPORTED, "The remote SFTP server is too old to support readlink operation"); } try { path = isUnique(remoteAbsolutePath(path)); sendREADLINK(Util.str2byte(path, _fileEncoding)); readResponse(); if( _header.type != SSH_FXP_NAME ) { throw new SftpException(SSH_FX_FAILURE, "Invalid FXP response: "+_header.type); } int count = _buffer.getInt(); // count byte[] filename = null; for( int i = 0; i < count; i++ ) { filename = _buffer.getString(); // absolute path if( _serverVersion <= 3 ) { _buffer.getString(); // long filename } SftpATTRS.getATTR(_buffer); // dummy attribute } return Util.byte2str(filename, _fileEncoding); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to readlink path: "+path, e); } } public void symlink(String oldpath, String newpath) throws SftpException { if( _serverVersion < 3 ) { throw new SftpException(SSH_FX_OP_UNSUPPORTED, "The remote SFTP server is too old to support symlink operation"); } try { oldpath = isUnique(remoteAbsolutePath(oldpath)); newpath = remoteAbsolutePath(newpath); if( isPattern(newpath) ) { throw new SftpException(SSH_FX_FAILURE, "Failed to symlink, new path is invalid: "+newpath); } newpath = Util.unquote(newpath); sendSYMLINK(Util.str2byte(oldpath, _fileEncoding), Util.str2byte(newpath, _fileEncoding)); readResponseOk(); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to symlink path: "+oldpath, e); } } public void rename(String oldpath, String newpath) throws SftpException { if( _serverVersion < 2 ) { throw new SftpException(SSH_FX_OP_UNSUPPORTED, "The remote SFTP server is too old to support rename operation"); } try { oldpath = isUnique(remoteAbsolutePath(oldpath)); newpath = remoteAbsolutePath(newpath); List matches = globRemote(newpath); if( matches.size() >= 2 ) { throw new SftpException(SSH_FX_FAILURE, "Failed to rename path, found too many matches: "+matches); } else if( matches.size() == 1 ) { newpath = matches.get(0); } else { if( isPattern(newpath) ) { throw new SftpException(SSH_FX_FAILURE, "Failed to rename path, new path is invalid: "+newpath); } newpath = Util.unquote(newpath); } sendRENAME(Util.str2byte(oldpath, _fileEncoding), Util.str2byte(newpath, _fileEncoding)); readResponseOk(); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to rename path: "+oldpath, e); } } public void rm(final String path) throws SftpException { try { for( String remotePath : globRemote(remoteAbsolutePath(path)) ) { sendREMOVE(Util.str2byte(remotePath, _fileEncoding)); readResponseOk(); } } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to rm path: "+path, e); } } public void chgrp(int gid, String path) throws SftpException { try { for( String remotePath : globRemote(remoteAbsolutePath(path)) ) { SftpATTRS attr = _stat(remotePath); attr.setFLAGS(0); attr.setUIDGID(attr.getUId(), gid); _setStat(remotePath, attr); } } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to chgrp path: "+path, e); } } public void chown(int uid, String path) throws SftpException { try { for( String remotePath : globRemote(remoteAbsolutePath(path)) ) { SftpATTRS attr = _stat(remotePath); attr.setFLAGS(0); attr.setUIDGID(uid, attr.getGId()); _setStat(remotePath, attr); } } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to chown path: "+path, e); } } public void chmod(int permissions, String path) throws SftpException { try { for( String remotePath : globRemote(remoteAbsolutePath(path)) ) { SftpATTRS attr = _stat(remotePath); attr.setFLAGS(0); attr.setPERMISSIONS(permissions); _setStat(remotePath, attr); } } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to chmod path: "+path, e); } } public void setMtime(String path, int mtime) throws SftpException { try { for( String remotePath : globRemote(remoteAbsolutePath(path)) ) { SftpATTRS attr = _stat(remotePath); attr.setFLAGS(0); attr.setACMODTIME(attr.getAccessTime(), mtime); _setStat(remotePath, attr); } } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to set mtime path: "+path, e); } } public void rmdir(String path) throws SftpException { try { for( String remotePath : globRemote(remoteAbsolutePath(path)) ) { sendRMDIR(Util.str2byte(remotePath, _fileEncoding)); readResponseOk(); } } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to rmdir path :"+path, e); } } public void mkdir(String path) throws SftpException { try { sendMKDIR(Util.str2byte(remoteAbsolutePath(path), _fileEncoding), null); readResponseOk(); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to mkdir path: "+path, e); } } public SftpATTRS stat(String path) throws SftpException { try { return _stat(isUnique(remoteAbsolutePath(path))); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to stat path: "+path, e); } } private SftpATTRS _stat(String path) throws SftpException { return _stat(Util.str2byte(path, _fileEncoding)); } private SftpATTRS _stat(byte[] path) throws SftpException { try { sendSTAT(path); readResponse(); if( _header.type != SSH_FXP_ATTRS ) { throw new SftpException(SSH_FX_FAILURE, "Invalid FXP response: "+_header.type); } return SftpATTRS.getATTR(_buffer); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to stat path: "+path, e); } } private boolean isRemoteDir(String path) { try { return _stat(path).isDir(); } catch(Exception e) { return false; } } public SftpATTRS lstat(String path) throws SftpException { try { return _lstat(isUnique(remoteAbsolutePath(path))); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to lstat path: "+path, e); } } private SftpATTRS _lstat(String path) throws SftpException { try { sendLSTAT(Util.str2byte(path, _fileEncoding)); readResponse(); if( _header.type != SSH_FXP_ATTRS ) { throw new SftpException(SSH_FX_FAILURE, "Invalid FXP response: "+_header.type); } return SftpATTRS.getATTR(_buffer); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to lstat path: "+path, e); } } /** * Returns the real path for the specified remote path as determined by the * server. * * @param path to retrieve * @return absolute remote path from server * @throws SftpException if any SFTP errors occur * @throws IOException if any read errors occur * @throws Exception if any write errors occur */ private byte[] _realpath(String path) throws SftpException, IOException, Exception { sendREALPATH(Util.str2byte(path, _fileEncoding)); readResponse(); if( _header.type != SSH_FXP_NAME ) { throw new SftpException(SSH_FX_FAILURE, "Invalid FXP response: "+_header.type); } byte[] str = null; int count = _buffer.getInt(); while( count-- > 0 ) { str = _buffer.getString(); // absolute path if( _serverVersion <= 3 ) { _buffer.getString(); // long filename } SftpATTRS.getATTR(_buffer); // dummy attribute } return str; } public void setStat(String path, SftpATTRS attr) throws SftpException { try { // For each remote path found for specified path, set the attributes for( String remotePath : globRemote(remoteAbsolutePath(path)) ) { _setStat(remotePath, attr); } } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to set stat for path: "+path, e); } } private void _setStat(String path, SftpATTRS attr) throws SftpException { try { sendSETSTAT(Util.str2byte(path, _fileEncoding), attr); readResponseOk(); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to set stat for path: "+path, e); } } /** * Returns the user's current working directory path on the remote server. * * @return current working directory path */ public String pwd() { return _cwd; } /** * Returns the local current working directory path on client. * * @return local current working directory */ public String lpwd() { return _lcwd; } /** * Returns the user's home directory path on the remote server. * * @return home directory path */ public String getHome() { return _home; } /** * Returns the client SFTP version. * * @return client SFTP version */ public String version() { return String.valueOf(CLIENT_VERSION); } /** * Returns the server SFTP version established during initial connection. * * @return server SFTP version * @throws SftpException if SFTP channel is not connected */ public int getServerVersion() throws SftpException { if( !isConnected() ) { throw new SftpException(SSH_FX_FAILURE, "The channel is not connected"); } return _serverVersion; } /** * Sends the INIT request to start the SFTP session by sending this client's * SFTP version. * * @throws Exception if any errors occur */ private void sendINIT() throws Exception { putHEAD(SSH_FXP_INIT, 5); _buffer.putInt(CLIENT_VERSION); _session.write(_packet, this, 5 + 4); } /** * Sends a real path request for the specified path. * * @param path to request real path * @throws Exception if any errors occur */ private void sendREALPATH(byte[] path) throws Exception { sendPacketPath(SSH_FXP_REALPATH, path); } private void sendSTAT(byte[] path) throws Exception { sendPacketPath(SSH_FXP_STAT, path); } private void sendLSTAT(byte[] path) throws Exception { sendPacketPath(SSH_FXP_LSTAT, path); } private void sendFSTAT(byte[] handle) throws Exception { sendPacketPath(SSH_FXP_FSTAT, handle); } private void sendSETSTAT(byte[] path, SftpATTRS attr) throws Exception { putHEAD(SSH_FXP_SETSTAT, 9 + path.length + attr.length()); _buffer.putInt(_seq++); _buffer.putString(path); // path attr.dump(_buffer); _session.write(_packet, this, 9 + path.length + attr.length() + 4); } private void sendREMOVE(byte[] path) throws Exception { sendPacketPath(SSH_FXP_REMOVE, path); } private void sendMKDIR(byte[] path, SftpATTRS attr) throws Exception { putHEAD(SSH_FXP_MKDIR, 9 + path.length + (attr != null ? attr.length() : 4)); _buffer.putInt(_seq++); _buffer.putString(path); // path if( attr != null ) { attr.dump(_buffer); } else { _buffer.putInt(0); } _session.write(_packet, this, 9 + path.length + (attr != null ? attr.length() : 4) + 4); } private void sendRMDIR(byte[] path) throws Exception { sendPacketPath(SSH_FXP_RMDIR, path); } private void sendSYMLINK(byte[] p1, byte[] p2) throws Exception { sendPacketPath(SSH_FXP_SYMLINK, p1, p2); } private void sendREADLINK(byte[] path) throws Exception { sendPacketPath(SSH_FXP_READLINK, path); } private void sendOPENDIR(byte[] path) throws Exception { sendPacketPath(SSH_FXP_OPENDIR, path); } private void sendREADDIR(byte[] path) throws Exception { sendPacketPath(SSH_FXP_READDIR, path); } private void sendRENAME(byte[] p1, byte[] p2) throws Exception { sendPacketPath(SSH_FXP_RENAME, p1, p2); } private void sendCLOSE(byte[] path) throws Exception { sendPacketPath(SSH_FXP_CLOSE, path); } private void _sendCLOSE(byte[] handle) throws Exception { sendCLOSE(handle); readResponseOk(); } private void sendOPENR(byte[] path) throws Exception { sendOPEN(path, SSH_FXF_READ); } private void sendOPENW(byte[] path) throws Exception { sendOPEN(path, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC); } private void sendOPENA(byte[] path) throws Exception { sendOPEN(path, SSH_FXF_WRITE |/*SSH_FXF_APPEND|*/ SSH_FXF_CREAT); } private void sendOPEN(byte[] path, int mode) throws Exception { putHEAD(SSH_FXP_OPEN, 17 + path.length); _buffer.putInt(_seq++); _buffer.putString(path); _buffer.putInt(mode); _buffer.putInt(0); // attrs _session.write(_packet, this, 17 + path.length + 4); } /** * Sends an SFTP packet path request with the specified SFTP protocol code * and path value. * * @param fxp SFTP protocol code * @param path value * @throws Exception if any errors occur writing packet to session */ private void sendPacketPath(byte fxp, byte[] path) throws Exception { putHEAD(fxp, 9 + path.length); _buffer.putInt(_seq++); _buffer.putString(path); _session.write(_packet, this, 9 + path.length + 4); } private void sendPacketPath(byte fxp, byte[] path1, byte[] path2) throws Exception { putHEAD(fxp, 13 + path1.length + path2.length); _buffer.putInt(_seq++); _buffer.putString(path1); _buffer.putString(path2); _session.write(_packet, this, 13 + path1.length + path2.length + 4); } private int sendWRITE(byte[] handle, long offset, byte[] data, int start, int length) throws Exception { int _length = length; _packet.reset(); if( _buffer.buffer.length < _buffer.index + 13 + 21 + handle.length + length + 32 + 20 ) { // padding and mac _length = _buffer.buffer.length - (_buffer.index + 13 + 21 + handle.length + 32 + 20 ); // padding and mac } putHEAD(SSH_FXP_WRITE, 21 + handle.length + _length); // 14 _buffer.putInt(_seq++); // 4 _buffer.putString(handle); // 4+handle.length _buffer.putLong(offset); // 8 if( _buffer.buffer != data ) { _buffer.putString(data, start, _length); // 4+_length } else { _buffer.putInt(_length); _buffer.skip(_length); } _session.write(_packet, this, 21 + handle.length + _length + 4); return _length; } private void sendREAD(byte[] handle, long offset, int length) throws Exception { putHEAD(SSH_FXP_READ, 21 + handle.length); _buffer.putInt(_seq++); _buffer.putString(handle); _buffer.putLong(offset); _buffer.putInt(length); _session.write(_packet, this, 21 + handle.length + 4); } /** * Puts the header in the current buffer for a SFTP packet. * * @param type of SFTP code request * @param length of SFTP data in bytes * @throws Exception if any errors occur */ private void putHEAD(byte type, int length) { // byte SSH_MSG_CHANNEL_DATA // uint32 recipient channel // uint32 total packet length // uint32 sftp data length // byte SFTP request code // .... channel type specific data follows _packet.reset(); _buffer.putByte(SSH_MSG_CHANNEL_DATA); _buffer.putInt(_recipient); _buffer.putInt(length + 4); _buffer.putInt(length); _buffer.putByte(type); } /** * Globs (pattern searches) the remote file system for the specified path or * pattern. * * @param path (or pattern) to search for * @return matching absolute remote paths * @throws Exception if any errors occur */ private List globRemote(final String path) throws Exception { int index = path.lastIndexOf('/'); if( index < 0 ) { // Return if not path is not an absolute path return Arrays.asList(Util.unquote(path)); } String dir = Util.unquote(path.substring(0, index+1)); //(index == 0 ? 1 : index))); String sPattern = path.substring(index + 1); if( !isPattern(sPattern) ) { // Return if path is not a pattern return Arrays.asList(dir + Util.unquote(sPattern)); } sendOPENDIR(Util.str2byte(dir, _fileEncoding)); readResponse(); if( _header.type != SSH_FXP_HANDLE ) { throw new SftpException(SSH_FX_FAILURE, "Invalid FXP response: "+_header.type); } List matches = new ArrayList(); byte[] bPattern = Util.str2byte(sPattern, UTF8); byte[] handle = _buffer.getString(); // filename while( true ) { sendREADDIR(handle); readHeader(); if( _header.type == SSH_FXP_STATUS ) { fill(_buffer, _header.length); break; } else if( _header.type != SSH_FXP_NAME ) { throw new SftpException(SSH_FX_FAILURE, "Invalid FXP response: "+_header.type); } _buffer.rewind(); fill(_buffer.buffer, 0, 4); _header.length -= 4; int i, count = _buffer.getInt(); _buffer.reset(); while( count > 0 ) { if( _header.length > 0 ) { _buffer.shift(); int j = (_buffer.buffer.length > (_buffer.index + _header.length)) ? _header.length : (_buffer.buffer.length - _buffer.index); if( (i = _io_in.read(_buffer.buffer, _buffer.index, j)) <= 0 ) { break; } _buffer.index += i; _header.length -= i; } byte[] bFilename = _buffer.getString(); // absolute path String sFilename = Util.byte2str(bFilename, _fileEncoding); if( _serverVersion <= 3 ) { _buffer.getString(); // file longname } SftpATTRS.getATTR(_buffer); // Read in file attributes if( Util.glob(bPattern, _utf8 ? bFilename : Util.str2byte(sFilename)) ) { matches.add(dir + sFilename); } count--; } } _sendCLOSE(handle); return matches; } private List globLocal(final String path) throws Exception { byte[] bPath = Util.str2byte(path, UTF8); int i = bPath.length - 1; while( i >= 0 ) { if( bPath[i] != '*' && bPath[i] != '?' ) { i--; continue; } else if( !FS_IS_BS && i > 0 && bPath[i - 1] == '\\' ) { i--; if( i > 0 && bPath[i - 1] == '\\' ) { i-=2; continue; } } break; } if( i < 0 ) { return Arrays.asList(FS_IS_BS ? path : Util.unquote(path)); } while( i >= 0 ) { if( bPath[i] == File.separatorChar || (FS_IS_BS && bPath[i] == '/') ) { break; // On Windows, '/' is also the separator. } i--; } if( i < 0 ) { return Arrays.asList(FS_IS_BS ? path : Util.unquote(path)); } byte[] dir; if( i == 0 ) { dir = new byte[]{(byte) File.separatorChar}; } else { dir = new byte[i]; System.arraycopy(bPath, 0, dir, 0, i); } byte[] pattern = new byte[bPath.length - i - 1]; System.arraycopy(bPath, i + 1, pattern, 0, pattern.length); try { List matches = new ArrayList(); String pdir = Util.byte2str(dir) + File.separator; for( String filename : new File(Util.byte2str(dir)).list() ) { if( Util.glob(pattern, Util.str2byte(filename)) ) { matches.add(pdir + filename); } } return matches; } catch(Exception e) { throw new IOException("Failed to globLocal path: "+path, e); } } private void throwStatusError(Buffer buf, int status) throws SftpException { if( _serverVersion >= 3 && // WindRiver's sftp will send invalid buf.getLength() >= 4 ) { // SSH_FXP_STATUS packet. throw new SftpException(status, "SFTP status error: " + Util.byte2str(buf.getString(), UTF8)); } else { throw new SftpException(status, "SFTP status error: unknown"); } } /** * Fills the specified buffer through the specified length from the SFTP * input stream. * * @param buf to fill * @param len to fill * @throws IOException if any read errors occur */ private void fill(Buffer buf, int len) throws IOException { buf.reset(); fill(buf.buffer, 0, len); buf.skip(len); } /** * Fills the specified buffer from the offset s through length len by * reading from the SFTP input stream. * * @param buf to fill with data from SFTP input stream * @param s offset * @param len length * @return amount of bytes read * @throws IOException if any read errors occur */ private int fill(byte[] buf, int s, int len) throws IOException { int i, offset = s; while( len > 0 ) { if( (i = _io_in.read(buf, s, len)) <= 0 ) { throw new IOException("SFTP InputStream is closed"); } s += i; len -= i; } return s - offset; } /** * Reads the header information from the SFTP input stream and fills the * specified buffer with any associated data after the header. Checks the * SFTP protocol response code and throws a status error if sent from * server. * * @return header info from buffer * @throws IOException if any read errors occur * @throws SftpException if status error is returned from server */ private void readResponse() throws IOException, SftpException { readHeader(); // Read header data from input fill(_buffer, _header.length); // Read rest of data from input stream into buffer if( _header.type == SSH_FXP_STATUS ) { throwStatusError(_buffer, _buffer.getInt()); // Throw status error } } private int readResponseOk() throws IOException, SftpException { readHeader(); // Read header data from input fill(_buffer, _header.length); // Read rest of data from input stream into buffer if( _header.type != SSH_FXP_STATUS ) { throw new SftpException(SSH_FX_FAILURE, "Invalid FXP status response: "+_header.type); } int status = _buffer.getInt(); if( status != SSH_FX_OK ) { throwStatusError(_buffer, status); } return _header.rid; // Return acknowledge ID from header } /** * Reads the header information from the instance buffer into the instance * header. * * @throws IOException if any read errors occur */ private void readHeader() throws IOException { _buffer.rewind(); fill(_buffer.buffer, 0, 9); // Read first 9 bytes containing header _header.length = _buffer.getInt() - 5; _header.type = (byte) (_buffer.getByte() & 0xff); _header.rid = _buffer.getInt(); } /** * Returns the remote absolute path from the specified relative path. * * @param path * @return absolute path (properly formatted) * @throws SftpException */ private String remoteAbsolutePath(String path) { if( path.charAt(0) == '/' ) { return path; } return _cwd + (_cwd.charAt(_cwd.length()-1) == '/' ? path : '/' + path); } /** * Returns the local absolute path for the specified path. * * @param path * @return absolute local path */ private String localAbsolutePath(String path) { if( new File(path).isAbsolute() ) { return path; } return _lcwd + (_lcwd.endsWith(File.separator) ? path : File.separator + path); } /** * This method will check if the given string can be expanded to the * unique string. If it can be expanded to multiple files, SftpException * will be thrown. * * @return the returned string is unquoted */ private String isUnique(String path) throws SftpException, Exception { List matches = globRemote(path); if( matches.size() != 1 ) { throw new SftpException(SSH_FX_FAILURE, path + " is not unique: " + matches); } return matches.get(0); } /** * Sets the file name encoding to use. By default, filename encoding is * UTF-8. If the SFTP server version does not support changing the filename * encoding than a SftpException will be thrown. * * @param encoding to use for path/file names * @throws SftpException if not connected or server doesn't support * different encodings */ public void setFilenameEncoding(String encoding) throws SftpException { if( getServerVersion() > 3 && !UTF8.equals(encoding) ) { throw new SftpException(SSH_FX_FAILURE, "The encoding cannot be changed for this sftp server version"); } _fileEncoding = encoding; _utf8 = UTF8.equals(_fileEncoding); } public String getExtension(String key) { return _extensions != null ? _extensions.get(key) : null; } /** * Returns the absolute path from the server for the specified path. * * @param path to check * @return absolute path from server * @throws SftpException if any errors occur */ public String realpath(String path) throws SftpException { try { return Util.byte2str(_realpath(remoteAbsolutePath(path)), _fileEncoding); } catch(SftpException e) { throw e; } catch(Exception e) { throw new SftpException(SSH_FX_FAILURE, "Failed to realpath path: "+path, e); } } private static boolean isPattern(byte[] path) { int i = path.length - 1; while( i >= 0 ) { if( path[i] == '*' || path[i] == '?' ) { if( i > 0 && path[i - 1] == '\\' ) { i--; if( i > 0 && path[i - 1] == '\\' ) { // \\* or \\? break; } } else { break; } } i--; } return !(i < 0); } private static boolean isPattern(String path) { return isPattern(Util.str2byte(path, UTF8)); } /** * Simple class for storing SFTP packet header information. * * @author Atsuhiko Yamanaka * @author Michael Laudati */ final class Header { /** Packet length from header. */ int length; /** Packet type code from header. */ byte type; /** Recipient ID from header. */ int rid; } /** * Represents an entry returned by the 'ls' SFTP command containing * information about a file or folder on the remote system. * * @author Atsuhiko Yamanaka * @author Michael Laudati */ public final class LsEntry implements Comparable { /** File name of entry. */ private final String __filename; /** Long name of entry. */ private final String __longname; /** SFTP attributes for file/folder. */ private final SftpATTRS __attrs; /** * Creates a new instance of LsEntry with the specified * properties. Constructor should remain package access since instances * should only be created by the SFTP channel. * * @param filename of entry * @param longname of entry * @param attrs entry */ LsEntry(String filename, String longname, SftpATTRS attrs) { __filename = filename; __longname = longname; __attrs = attrs; } /** * Returns the file name of the entry. * * @return file name */ public String getFilename() { return __filename; } /** * Returns the long name of the entry. * * @return long name of entry */ public String getLongname() { return __longname; } /** * Returns the SFTP attributes for the entry. * * @return attributes of entry */ public SftpATTRS getAttrs() { return __attrs; } @Override public String toString() { return __longname; } @Override public int compareTo(LsEntry entry) { return __filename.compareTo(entry.getFilename()); } } /** * Implementation of OutputStream for writing data out to the * SFTP channel for a PUT operation, which creates a file on the remote host * and fills it with the data from this output stream. * * @author Atsuhiko Yamanaka * @author Michael Laudati */ private final class PutOutputStream extends OutputStream { private boolean __init = true; private boolean __closed = false; private int __startId = 0; private int __ackCount = 0; private int __writeCount = 0; private long __offset; private final byte[] __data = new byte[1]; private final byte[] __handle; private final SftpProgressMonitor __monitor; PutOutputStream(byte[] handle, long offset, SftpProgressMonitor monitor) { __handle = handle; __offset = offset; __monitor = monitor; } @Override public void write(byte[] b) throws IOException { write(b, 0, b.length); } @Override public void write(byte[] b, int s, int len) throws IOException { if( __init ) { __startId = _seq; __init = false; } if( __closed ) { throw new IOException("OutputStream already closed"); } try { int _len = len, ackId; while( _len > 0 ) { int sent = sendWRITE(__handle, __offset, b, s, _len); __writeCount++; __offset += sent; s += sent; _len -= sent; if( (_seq - 1) == __startId || _io_in.available() >= 1024 ) { while( _io_in.available() > 0 ) { ackId = readResponseOk(); if( __startId > ackId || ackId > _seq - 1 ) { throw new SftpException(SSH_FX_FAILURE, "Invalid ack ID: "+ackId); } __ackCount++; } } } if( __monitor != null && !__monitor.count(len) ) { close(); throw new IOException("OutputStream canceled by user"); } } catch(IOException e) { throw e; } catch(Exception e) { throw new IOException(e); } } @Override public void write(int b) throws IOException { __data[0] = (byte) b; write(__data, 0, 1); } @Override public void flush() throws IOException { if( __closed ) { throw new IOException("OutputStream already closed"); } if( !__init ) { try { while( __writeCount > __ackCount ) { readResponseOk(); __ackCount++; } } catch(SftpException e) { throw new IOException(e); } } } @Override public void close() throws IOException { if( __closed ) { return; } __closed = true; flush(); if( __monitor != null ) { __monitor.end(); } try { _sendCLOSE(__handle); } catch(IOException e) { throw e; } catch(Exception e) { throw new IOException("Failed to close OutputStream", e); } } } /** * Implementation of InputStream for reading input from the * SFTP channel supplied from a GET operation. * * @author Atsuhiko Yamanaka * @author Michael Laudati */ private final class GetInputStream extends InputStream { private long __offset; private boolean __closed = false; private int __restLength = 0; private byte[] __restByte = new byte[1024]; private final byte[] __data = new byte[1]; private final SftpProgressMonitor __monitor; private final byte[] __handle; GetInputStream(long skip, byte[] handle, SftpProgressMonitor monitor) { __offset = skip; __monitor = monitor; __handle = handle; } @Override public int read() throws IOException { int i = read(__data, 0, 1); // Read in one byte return i == -1 ? -1 : __data[0] & 0xff; } @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } @Override public int read(byte[] b, int s, int len) throws IOException { if( __closed ) { return -1; } else if( len == 0 ) { return 0; } if( __restLength > 0 ) { int readLen = __restLength > len ? len : __restLength; System.arraycopy(__restByte, 0, b, s, readLen); if( readLen != __restLength ) { System.arraycopy(__restByte, readLen, __restByte, 0, __restLength - readLen); } if( __monitor != null && !__monitor.count(readLen) ) { close(); return -1; } __restLength -= readLen; return readLen; } if( _buffer.buffer.length - 13 < len ) { len = _buffer.buffer.length - 13; } if( _serverVersion == 0 && len > 1024 ) { len = 1024; } try { sendREAD(__handle, __offset, len); } catch(Exception e) { throw new IOException("Failed to send read request", e); } readHeader(); __restLength = _header.length; if( _header.type != SSH_FXP_STATUS && _header.type != SSH_FXP_DATA ) { throw new IOException("Invalid status response: "+_header.type); } else if( _header.type == SSH_FXP_STATUS ) { fill(_buffer, __restLength); int i = _buffer.getInt(); __restLength = 0; if( i == SSH_FX_EOF ) { close(); return -1; } throw new IOException("Invalid status response: "+i); } _buffer.rewind(); fill(_buffer.buffer, 0, 4); int i, availableLen = _buffer.getInt(); // Available length returned __restLength -= 4; __offset += __restLength; if( availableLen > 0 ) { int readLen = __restLength > len ? len : __restLength; i = _io_in.read(b, s, readLen); if( i < 0 ) { return -1; } __restLength -= i; if( __restLength > 0 ) { if( __restByte.length < __restLength ) { __restByte = new byte[__restLength]; } int j, _s = 0, _len = __restLength; while( _len > 0 ) { if( (j = _io_in.read(__restByte, _s, _len)) <= 0 ) { break; } _s += j; _len -= j; } } if( __monitor != null && !__monitor.count(i) ) { close(); return -1; } return i; } return 0; // ?? } @Override public void close() throws IOException { if( __closed ) { return; } __closed = true; if( __monitor != null ) { __monitor.end(); } try { _sendCLOSE(__handle); } catch(Exception e) { throw new IOException("Failed to close InputStream", e); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy