Package org.vngx.jsch

Source Code of org.vngx.jsch.ChannelSftp

/*
* 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 <code>ChannelSession</code> 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.
*
* <p>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<String,String> _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 <code>ChannelSftp</code>.
   *
   * @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<String,String>();
        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
   * <code>exit()</code>).
   */
  public void quit() {
    disconnect();
  }

  /**
   * Exits the SFTP session and closes the channel.  (Same as
   * <code>quit()</code>)
   */
  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<String> 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<String> 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<String> 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<LsEntry> 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() ) {
          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<LsEntry> lsEntries = new ArrayList<LsEntry>();
      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<String> 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 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<String> 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<String> matches = new ArrayList<String>();
    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<String> 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<String> matches = new ArrayList<String>();
      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<String> 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 <code>SftpException</code> 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<LsEntry> {

    /** 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 <code>LsEntry</code> 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 <code>OutputStream</code> 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;
      }
      flush();
      __closed = true;
      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 <code>InputStream</code> 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);
      }
    }
  }

}
TOP

Related Classes of org.vngx.jsch.ChannelSftp

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.