Package ch.ethz.ssh2

Source Code of ch.ethz.ssh2.SFTPv3Client

package ch.ethz.ssh2;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Vector;

import ch.ethz.ssh2.packets.TypesReader;
import ch.ethz.ssh2.packets.TypesWriter;
import ch.ethz.ssh2.sftp.AttribFlags;
import ch.ethz.ssh2.sftp.ErrorCodes;
import ch.ethz.ssh2.sftp.Packet;

/**
* A <code>SFTPv3Client</code> represents a SFTP (protocol version 3)
* client connection tunnelled over a SSH-2 connection. This is a very simple
* (synchronous) implementation.
* <p>
* Basically, most methods in this class map directly to one of
* the packet types described in draft-ietf-secsh-filexfer-02.txt.
* <p>
* Note: this is experimental code.
* <p>
* Error handling: the methods of this class throw IOExceptions. However, unless
* there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will
* be thrown (a subclass of IOException). Therefore, you can implement more verbose
* behavior by checking if a thrown exception if of this type. If yes, then you
* can cast the exception and access detailed information about the failure.
* <p>
* Notes about file names, directory names and paths, copy-pasted
* from the specs:
* <ul>
* <li>SFTP v3 represents file names as strings. File names are
* assumed to use the slash ('/') character as a directory separator.</li>
* <li>File names starting with a slash are "absolute", and are relative to
* the root of the file system.  Names starting with any other character
* are relative to the user's default directory (home directory).</li>
* <li>Servers SHOULD interpret a path name component ".." as referring to
* the parent directory, and "." as referring to the current directory.
* If the server implementation limits access to certain parts of the
* file system, it must be extra careful in parsing file names when
* enforcing such restrictions.  There have been numerous reported
* security bugs where a ".." in a path name has allowed access outside
* the intended area.</li>
* <li>An empty path name is valid, and it refers to the user's default
* directory (usually the user's home directory).</li>
* </ul>
* <p>
* If you are still not tired then please go on and read the comment for
* {@link #setCharset(String)}.
*
* @author Christian Plattner, plattner@inf.ethz.ch
* @version $Id: SFTPv3Client.java,v 1.9 2006/09/20 12:51:37 cplattne Exp $
*/
public class SFTPv3Client
{
  final Connection conn;
  final Session sess;
  final PrintStream debug;

  boolean flag_closed = false;

  InputStream is;
  OutputStream os;

  int protocol_version = 0;
  HashMap server_extensions = new HashMap();

  int next_request_id = 1000;

  String charsetName = null;

  /**
   * Create a SFTP v3 client.
   *
   * @param conn The underlying SSH-2 connection to be used.
   * @param debug
   * @throws IOException
   *
   * @deprecated this constructor (debug version) will disappear in the future,
   *             use {@link #SFTPv3Client(Connection)} instead.
   */
  public SFTPv3Client(Connection conn, PrintStream debug) throws IOException
  {
    if (conn == null)
      throw new IllegalArgumentException("Cannot accept null argument!");

    this.conn = conn;
    this.debug = debug;

    if (debug != null)
      debug.println("Opening session and starting SFTP subsystem.");

    sess = conn.openSession();
    sess.startSubSystem("sftp");

    is = sess.getStdout();
    os = new BufferedOutputStream(sess.getStdin(), 2048);

    if ((is == null) || (os == null))
      throw new IOException("There is a problem with the streams of the underlying channel.");

    init();
  }

  /**
   * Create a SFTP v3 client.
   *
   * @param conn The underlying SSH-2 connection to be used.
   * @throws IOException
   */
  public SFTPv3Client(Connection conn) throws IOException
  {
    this(conn, null);
  }

  /**
   * Set the charset used to convert between Java Unicode Strings and byte encodings
   * used by the server for paths and file names. Unfortunately, the SFTP v3 draft
   * says NOTHING about such conversions (well, with the exception of error messages
   * which have to be in UTF-8). Newer drafts specify to use UTF-8 for file names
   * (if I remember correctly). However, a quick test using OpenSSH serving a EXT-3
   * filesystem has shown that UTF-8 seems to be a bad choice for SFTP v3 (tested with
   * filenames containing german umlauts). "windows-1252" seems to work better for Europe.
   * Luckily, "windows-1252" is the platform default in my case =).
   * <p>
   * If you don't set anything, then the platform default will be used (this is the default
   * behavior).
   *
   * @see #getCharset()
   *
   * @param charset the name of the charset to be used or <code>null</code> to use the platform's
   *        default encoding.
   * @throws IOException
   */
  public void setCharset(String charset) throws IOException
  {
    if (charset == null)
    {
      charsetName = charset;
      return;
    }

    try
    {
      Charset.forName(charset);
    }
    catch (Exception e)
    {
      throw (IOException) new IOException("This charset is not supported").initCause(e);
    }
    charsetName = charset;
  }

  /**
   * The currently used charset for filename encoding/decoding.
   *
   * @see #setCharset(String)
   *
   * @return The name of the charset (<code>null</code> if the platform's default charset is being used)
   */
  public String getCharset()
  {
    return charsetName;
  }

  private final void checkHandleValidAndOpen(SFTPv3FileHandle handle) throws IOException
  {
    if (handle.client != this)
      throw new IOException("The file handle was created with another SFTPv3FileHandle instance.");

    if (handle.isClosed == true)
      throw new IOException("The file handle is closed.");
  }

  private final void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException
  {
    int msglen = len + 1;

    if (type != Packet.SSH_FXP_INIT)
      msglen += 4;

    os.write(msglen >> 24);
    os.write(msglen >> 16);
    os.write(msglen >> 8);
    os.write(msglen);
    os.write(type);

    if (type != Packet.SSH_FXP_INIT)
    {
      os.write(requestId >> 24);
      os.write(requestId >> 16);
      os.write(requestId >> 8);
      os.write(requestId);
    }

    os.write(msg, off, len);
    os.flush();
  }

  private final void sendMessage(int type, int requestId, byte[] msg) throws IOException
  {
    sendMessage(type, requestId, msg, 0, msg.length);
  }

  private final void readBytes(byte[] buff, int pos, int len) throws IOException
  {
    while (len > 0)
    {
      int count = is.read(buff, pos, len);
      if (count < 0)
        throw new IOException("Unexpected end of sftp stream.");
      if ((count == 0) || (count > len))
        throw new IOException("Underlying stream implementation is bogus!");
      len -= count;
      pos += count;
    }
  }

  /**
   * Read a message and guarantee that the <b>contents</b> is not larger than
   * <code>maxlen</code> bytes.
   * <p>
   * Note: receiveMessage(34000) actually means that the message may be up to 34004
   * bytes (the length attribute preceeding the contents is 4 bytes).
   *
   * @param maxlen
   * @return the message contents
   * @throws IOException
   */
  private final byte[] receiveMessage(int maxlen) throws IOException
  {
    byte[] msglen = new byte[4];

    readBytes(msglen, 0, 4);

    int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff));

    if ((len > maxlen) || (len <= 0))
      throw new IOException("Illegal sftp packet len: " + len);

    byte[] msg = new byte[len];

    readBytes(msg, 0, len);

    return msg;
  }

  private final int generateNextRequestID()
  {
    synchronized (this)
    {
      return next_request_id++;
    }
  }

  private final void closeHandle(byte[] handle) throws IOException
  {
    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(handle, 0, handle.length);

    sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes());

    expectStatusOKMessage(req_id);
  }

  private SFTPv3FileAttributes readAttrs(TypesReader tr) throws IOException
  {
    /*
     * uint32   flags
     * uint64   size           present only if flag SSH_FILEXFER_ATTR_SIZE
     * uint32   uid            present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
     * uint32   gid            present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
     * uint32   permissions    present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
     * uint32   atime          present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
     * uint32   mtime          present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
     * uint32   extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
     * string   extended_type
     * string   extended_data
     * ...      more extended data (extended_type - extended_data pairs),
     *          so that number of pairs equals extended_count
     */

    SFTPv3FileAttributes fa = new SFTPv3FileAttributes();

    int flags = tr.readUINT32();

    if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0)
    {
      if (debug != null)
        debug.println("SSH_FILEXFER_ATTR_SIZE");
      fa.size = new Long(tr.readUINT64());
    }

    if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0)
    {
      if (debug != null)
        debug.println("SSH_FILEXFER_ATTR_V3_UIDGID");
      fa.uid = new Integer(tr.readUINT32());
      fa.gid = new Integer(tr.readUINT32());
    }

    if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0)
    {
      if (debug != null)
        debug.println("SSH_FILEXFER_ATTR_PERMISSIONS");
      fa.permissions = new Integer(tr.readUINT32());
    }

    if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0)
    {
      if (debug != null)
        debug.println("SSH_FILEXFER_ATTR_V3_ACMODTIME");
      fa.atime = new Integer(tr.readUINT32());
      fa.mtime = new Integer(tr.readUINT32());

    }

    if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0)
    {
      int count = tr.readUINT32();

      if (debug != null)
        debug.println("SSH_FILEXFER_ATTR_EXTENDED (" + count + ")");

      /* Read it anyway to detect corrupt packets */

      while (count > 0)
      {
        tr.readByteString();
        tr.readByteString();
        count--;
      }
    }

    return fa;
  }

  /**
   * Retrieve the file attributes of an open file.
   *
   * @param handle a SFTPv3FileHandle handle.
   * @return a SFTPv3FileAttributes object.
   * @throws IOException
   */
  public SFTPv3FileAttributes fstat(SFTPv3FileHandle handle) throws IOException
  {
    checkHandleValidAndOpen(handle);

    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);

    if (debug != null)
    {
      debug.println("Sending SSH_FXP_FSTAT...");
      debug.flush();
    }

    sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes());

    byte[] resp = receiveMessage(34000);

    if (debug != null)
    {
      debug.println("Got REPLY.");
      debug.flush();
    }

    TypesReader tr = new TypesReader(resp);

    int t = tr.readByte();

    int rep_id = tr.readUINT32();
    if (rep_id != req_id)
      throw new IOException("The server sent an invalid id field.");

    if (t == Packet.SSH_FXP_ATTRS)
    {
      return readAttrs(tr);
    }

    if (t != Packet.SSH_FXP_STATUS)
      throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");

    int errorCode = tr.readUINT32();

    throw new SFTPException(tr.readString(), errorCode);
  }

  private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException
  {
    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(path, charsetName);

    if (debug != null)
    {
      debug.println("Sending SSH_FXP_STAT/SSH_FXP_LSTAT...");
      debug.flush();
    }

    sendMessage(statMethod, req_id, tw.getBytes());

    byte[] resp = receiveMessage(34000);

    if (debug != null)
    {
      debug.println("Got REPLY.");
      debug.flush();
    }

    TypesReader tr = new TypesReader(resp);

    int t = tr.readByte();

    int rep_id = tr.readUINT32();
    if (rep_id != req_id)
      throw new IOException("The server sent an invalid id field.");

    if (t == Packet.SSH_FXP_ATTRS)
    {
      return readAttrs(tr);
    }

    if (t != Packet.SSH_FXP_STATUS)
      throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");

    int errorCode = tr.readUINT32();

    throw new SFTPException(tr.readString(), errorCode);
  }

  /**
   * Retrieve the file attributes of a file. This method
   * follows symbolic links on the server.
   *
   * @see #lstat(String)
   *
   * @param path See the {@link SFTPv3Client comment} for the class for more details.
   * @return a SFTPv3FileAttributes object.
   * @throws IOException
   */
  public SFTPv3FileAttributes stat(String path) throws IOException
  {
    return statBoth(path, Packet.SSH_FXP_STAT);
  }

  /**
   * Retrieve the file attributes of a file. This method
   * does NOT follow symbolic links on the server.
   *
   * @see #stat(String)
   *
   * @param path See the {@link SFTPv3Client comment} for the class for more details.
   * @return a SFTPv3FileAttributes object.
   * @throws IOException
   */
  public SFTPv3FileAttributes lstat(String path) throws IOException
  {
    return statBoth(path, Packet.SSH_FXP_LSTAT);
  }

  /**
   * Read the target of a symbolic link.
   *
   * @param path See the {@link SFTPv3Client comment} for the class for more details.
   * @return The target of the link.
   * @throws IOException
   */
  public String readLink(String path) throws IOException
  {
    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(path, charsetName);

    if (debug != null)
    {
      debug.println("Sending SSH_FXP_READLINK...");
      debug.flush();
    }

    sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes());

    byte[] resp = receiveMessage(34000);

    if (debug != null)
    {
      debug.println("Got REPLY.");
      debug.flush();
    }

    TypesReader tr = new TypesReader(resp);

    int t = tr.readByte();

    int rep_id = tr.readUINT32();
    if (rep_id != req_id)
      throw new IOException("The server sent an invalid id field.");

    if (t == Packet.SSH_FXP_NAME)
    {
      int count = tr.readUINT32();

      if (count != 1)
        throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");

      return tr.readString(charsetName);
    }

    if (t != Packet.SSH_FXP_STATUS)
      throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");

    int errorCode = tr.readUINT32();

    throw new SFTPException(tr.readString(), errorCode);
  }

  private void expectStatusOKMessage(int id) throws IOException
  {
    byte[] resp = receiveMessage(34000);

    if (debug != null)
    {
      debug.println("Got REPLY.");
      debug.flush();
    }

    TypesReader tr = new TypesReader(resp);

    int t = tr.readByte();

    int rep_id = tr.readUINT32();
    if (rep_id != id)
      throw new IOException("The server sent an invalid id field.");

    if (t != Packet.SSH_FXP_STATUS)
      throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");

    int errorCode = tr.readUINT32();

    if (errorCode == ErrorCodes.SSH_FX_OK)
      return;

    throw new SFTPException(tr.readString(), errorCode);
  }

  /**
   *  Modify the attributes of a file. Used for operations such as changing
   *  the ownership, permissions or access times, as well as for truncating a file.
   *
   * @param path See the {@link SFTPv3Client comment} for the class for more details.
   * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
   *             made to the attributes of the file. Empty fields will be ignored.
   * @throws IOException
   */
  public void setstat(String path, SFTPv3FileAttributes attr) throws IOException
  {
    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(path, charsetName);
    tw.writeBytes(createAttrs(attr));

    if (debug != null)
    {
      debug.println("Sending SSH_FXP_SETSTAT...");
      debug.flush();
    }

    sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes());

    expectStatusOKMessage(req_id);
  }

  /**
   *   Modify the attributes of a file. Used for operations such as changing
   *  the ownership, permissions or access times, as well as for truncating a file.
   *
   * @param handle a SFTPv3FileHandle handle
   * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
   *             made to the attributes of the file. Empty fields will be ignored.
   * @throws IOException
   */
  public void fsetstat(SFTPv3FileHandle handle, SFTPv3FileAttributes attr) throws IOException
  {
    checkHandleValidAndOpen(handle);

    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
    tw.writeBytes(createAttrs(attr));

    if (debug != null)
    {
      debug.println("Sending SSH_FXP_FSETSTAT...");
      debug.flush();
    }

    sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes());

    expectStatusOKMessage(req_id);
  }

  /**
   * Create a symbolic link on the server. Creates a link "src" that points
   * to "target".
   *
   * @param src See the {@link SFTPv3Client comment} for the class for more details.
   * @param target See the {@link SFTPv3Client comment} for the class for more details.
   * @throws IOException
   */
  public void createSymlink(String src, String target) throws IOException
  {
    int req_id = generateNextRequestID();

    /* Either I am too stupid to understand the SFTP draft
     * or the OpenSSH guys changed the semantics of src and target.
     */

    TypesWriter tw = new TypesWriter();
    tw.writeString(target, charsetName);
    tw.writeString(src, charsetName);

    if (debug != null)
    {
      debug.println("Sending SSH_FXP_SYMLINK...");
      debug.flush();
    }

    sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes());

    expectStatusOKMessage(req_id);
  }

  /**
   * Have the server canonicalize any given path name to an absolute path.
   * This is useful for converting path names containing ".." components or
   * relative pathnames without a leading slash into absolute paths.
   *
   * @param path See the {@link SFTPv3Client comment} for the class for more details.
   * @return An absolute path.
   * @throws IOException
   */
  public String canonicalPath(String path) throws IOException
  {
    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(path, charsetName);

    if (debug != null)
    {
      debug.println("Sending SSH_FXP_REALPATH...");
      debug.flush();
    }

    sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes());

    byte[] resp = receiveMessage(34000);

    if (debug != null)
    {
      debug.println("Got REPLY.");
      debug.flush();
    }

    TypesReader tr = new TypesReader(resp);

    int t = tr.readByte();

    int rep_id = tr.readUINT32();
    if (rep_id != req_id)
      throw new IOException("The server sent an invalid id field.");

    if (t == Packet.SSH_FXP_NAME)
    {
      int count = tr.readUINT32();

      if (count != 1)
        throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");

      return tr.readString(charsetName);
    }

    if (t != Packet.SSH_FXP_STATUS)
      throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");

    int errorCode = tr.readUINT32();

    throw new SFTPException(tr.readString(), errorCode);
  }

  private final Vector scanDirectory(byte[] handle) throws IOException
  {
    Vector files = new Vector();

    while (true)
    {
      int req_id = generateNextRequestID();

      TypesWriter tw = new TypesWriter();
      tw.writeString(handle, 0, handle.length);

      if (debug != null)
      {
        debug.println("Sending SSH_FXP_READDIR...");
        debug.flush();
      }

      sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes());

      byte[] resp = receiveMessage(34000);

      if (debug != null)
      {
        debug.println("Got REPLY.");
        debug.flush();
      }

      TypesReader tr = new TypesReader(resp);

      int t = tr.readByte();

      int rep_id = tr.readUINT32();
      if (rep_id != req_id)
        throw new IOException("The server sent an invalid id field.");

      if (t == Packet.SSH_FXP_NAME)
      {
        int count = tr.readUINT32();

        if (debug != null)
          debug.println("Parsing " + count + " name entries...");

        while (count > 0)
        {
          SFTPv3DirectoryEntry dirEnt = new SFTPv3DirectoryEntry();

          dirEnt.filename = tr.readString(charsetName);
          dirEnt.longEntry = tr.readString(charsetName);

          dirEnt.attributes = readAttrs(tr);
          files.addElement(dirEnt);

          if (debug != null)
            debug.println("File: '" + dirEnt.filename + "'");
          count--;
        }
        continue;
      }

      if (t != Packet.SSH_FXP_STATUS)
        throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");

      int errorCode = tr.readUINT32();

      if (errorCode == ErrorCodes.SSH_FX_EOF)
        return files;

      throw new SFTPException(tr.readString(), errorCode);
    }
  }

  private final byte[] openDirectory(String path) throws IOException
  {
    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(path, charsetName);

    if (debug != null)
    {
      debug.println("Sending SSH_FXP_OPENDIR...");
      debug.flush();
    }

    sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes());

    byte[] resp = receiveMessage(34000);

    TypesReader tr = new TypesReader(resp);

    int t = tr.readByte();

    int rep_id = tr.readUINT32();
    if (rep_id != req_id)
      throw new IOException("The server sent an invalid id field.");

    if (t == Packet.SSH_FXP_HANDLE)
    {
      if (debug != null)
      {
        debug.println("Got SSH_FXP_HANDLE.");
        debug.flush();
      }

      byte[] handle = tr.readByteString();
      return handle;
    }

    if (t != Packet.SSH_FXP_STATUS)
      throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");

    int errorCode = tr.readUINT32();
    String errorMessage = tr.readString();

    throw new SFTPException(errorMessage, errorCode);
  }

  private final String expandString(byte[] b, int off, int len)
  {
    StringBuffer sb = new StringBuffer();

    for (int i = 0; i < len; i++)
    {
      int c = b[off + i] & 0xff;

      if ((c >= 32) && (c <= 126))
      {
        sb.append((char) c);
      }
      else
      {
        sb.append("{0x" + Integer.toHexString(c) + "}");
      }
    }

    return sb.toString();
  }

  private void init() throws IOException
  {
    /* Send SSH_FXP_INIT (version 3) */

    final int client_version = 3;

    if (debug != null)
      debug.println("Sending SSH_FXP_INIT (" + client_version + ")...");

    TypesWriter tw = new TypesWriter();
    tw.writeUINT32(client_version);
    sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes());

    /* Receive SSH_FXP_VERSION */

    if (debug != null)
      debug.println("Waiting for SSH_FXP_VERSION...");

    TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */

    int type = tr.readByte();

    if (type != Packet.SSH_FXP_VERSION)
    {
      throw new IOException("The server did not send a SSH_FXP_VERSION packet (got " + type + ")");
    }

    protocol_version = tr.readUINT32();

    if (debug != null)
      debug.println("SSH_FXP_VERSION: protocol_version = " + protocol_version);

    if (protocol_version != 3)
      throw new IOException("Server version " + protocol_version + " is currently not supported");

    /* Read and save extensions (if any) for later use */

    while (tr.remain() != 0)
    {
      String name = tr.readString();
      byte[] value = tr.readByteString();
      server_extensions.put(name, value);

      if (debug != null)
        debug.println("SSH_FXP_VERSION: extension: " + name + " = '" + expandString(value, 0, value.length)
            + "'");
    }
  }

  /**
   * Returns the negotiated SFTP protocol version between the client and the server.
   *
   * @return SFTP protocol version, i.e., "3".
   *
   */
  public int getProtocolVersion()
  {
    return protocol_version;
  }

  /**
   * Close this SFTP session. NEVER forget to call this method to free up
   * resources - even if you got an exception from one of the other methods.
   * Sometimes these other methods may throw an exception, saying that the
   * underlying channel is closed (this can happen, e.g., if the other server
   * sent a close message.) However, as long as you have not called the
   * <code>close()</code> method, you are likely wasting resources.
   *
   */
  public void close()
  {
    sess.close();
  }

  /**
   * List the contents of a directory.
   *
   * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
   * @return A Vector containing {@link SFTPv3DirectoryEntry} objects.
   * @throws IOException
   */
  public Vector ls(String dirName) throws IOException
  {
    byte[] handle = openDirectory(dirName);
    Vector result = scanDirectory(handle);
    closeHandle(handle);
    return result;
  }

  /**
   * Create a new directory.
   *
   * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
   * @param posixPermissions the permissions for this directory, e.g., "0700". The server
   *        will likely apply a umask.
   * @throws IOException
   */
  public void mkdir(String dirName, int posixPermissions) throws IOException
  {
    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(dirName, charsetName);
    tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS);
    tw.writeUINT32(posixPermissions);

    sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes());

    expectStatusOKMessage(req_id);
  }

  /**
   * Remove a file.
   *
   * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   * @throws IOException
   */
  public void rm(String fileName) throws IOException
  {
    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(fileName, charsetName);

    sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes());

    expectStatusOKMessage(req_id);
  }

  /**
   * Remove an empty directory.
   *
   * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
   * @throws IOException
   */
  public void rmdir(String dirName) throws IOException
  {
    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(dirName, charsetName);

    sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes());

    expectStatusOKMessage(req_id);
  }

  /**
   * Move a file or directory.
   *
   * @param oldPath See the {@link SFTPv3Client comment} for the class for more details.
   * @param newPath See the {@link SFTPv3Client comment} for the class for more details.
   * @throws IOException
   */
  public void mv(String oldPath, String newPath) throws IOException
  {
    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(oldPath, charsetName);
    tw.writeString(newPath, charsetName);

    sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes());

    expectStatusOKMessage(req_id);
  }

  /**
   * Open a file for reading.
   *
   * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   * @return a SFTPv3FileHandle handle
   * @throws IOException
   */
  public SFTPv3FileHandle openFileRO(String fileName) throws IOException
  {
    return openFile(fileName, 0x00000001, null); // SSH_FXF_READ 
  }

  /**
   * Open a file for reading and writing.
   *
   * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   * @return a SFTPv3FileHandle handle
   * @throws IOException
   */
  public SFTPv3FileHandle openFileRW(String fileName) throws IOException
  {
    return openFile(fileName, 0x00000003, null); // SSH_FXF_READ | SSH_FXF_WRITE
  }

  // Append is broken (already in the specification, because there is no way to
  // send a write operation (what offset to use??))
  //  public SFTPv3FileHandle openFileRWAppend(String fileName) throws IOException
  //  {
  //    return openFile(fileName, 0x00000007, null); // SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND
  //  }

  /**
   * Create a file and open it for reading and writing.
   * Same as {@link #createFile(String, SFTPv3FileAttributes) createFile(fileName, null)}.
   *
   * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   * @return a SFTPv3FileHandle handle
   * @throws IOException
   */
  public SFTPv3FileHandle createFile(String fileName) throws IOException
  {
    return createFile(fileName, null);
  }

  /**
   * Create a file and open it for reading and writing.
   * You can specify the default attributes of the file (the server may or may
   * not respect your wishes).
   *
   * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   * @param attr may be <code>null</code> to use server defaults. Probably only
   *             the <code>uid</code>, <code>gid</code> and <code>permissions</code>
   *             (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
   *             structure make sense. You need only to set those fields where you want
   *             to override the server's defaults.
   * @return a SFTPv3FileHandle handle
   * @throws IOException
   */
  public SFTPv3FileHandle createFile(String fileName, SFTPv3FileAttributes attr) throws IOException
  {
    return openFile(fileName, 0x00000008 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE
  }

  /**
   * Create a file (truncate it if it already exists) and open it for reading and writing.
   * Same as {@link #createFileTruncate(String, SFTPv3FileAttributes) createFileTruncate(fileName, null)}.
   *
   * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   * @return a SFTPv3FileHandle handle
   * @throws IOException
   */
  public SFTPv3FileHandle createFileTruncate(String fileName) throws IOException
  {
    return createFileTruncate(fileName, null);
  }

  /**
   * reate a file (truncate it if it already exists) and open it for reading and writing.
   * You can specify the default attributes of the file (the server may or may
   * not respect your wishes).
   *
   * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   * @param attr may be <code>null</code> to use server defaults. Probably only
   *             the <code>uid</code>, <code>gid</code> and <code>permissions</code>
   *             (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
   *             structure make sense. You need only to set those fields where you want
   *             to override the server's defaults.
   * @return a SFTPv3FileHandle handle
   * @throws IOException
   */
  public SFTPv3FileHandle createFileTruncate(String fileName, SFTPv3FileAttributes attr) throws IOException
  {
    return openFile(fileName, 0x00000018 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_READ | SSH_FXF_WRITE
  }

  private byte[] createAttrs(SFTPv3FileAttributes attr)
  {
    TypesWriter tw = new TypesWriter();

    int attrFlags = 0;

    if (attr == null)
    {
      tw.writeUINT32(0);
    }
    else
    {
      if (attr.size != null)
        attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE;

      if ((attr.uid != null) && (attr.gid != null))
        attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID;

      if (attr.permissions != null)
        attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS;

      if ((attr.atime != null) && (attr.mtime != null))
        attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME;

      tw.writeUINT32(attrFlags);

      if (attr.size != null)
        tw.writeUINT64(attr.size.longValue());

      if ((attr.uid != null) && (attr.gid != null))
      {
        tw.writeUINT32(attr.uid.intValue());
        tw.writeUINT32(attr.gid.intValue());
      }

      if (attr.permissions != null)
        tw.writeUINT32(attr.permissions.intValue());

      if ((attr.atime != null) && (attr.mtime != null))
      {
        tw.writeUINT32(attr.atime.intValue());
        tw.writeUINT32(attr.mtime.intValue());
      }
    }

    return tw.getBytes();
  }

  private SFTPv3FileHandle openFile(String fileName, int flags, SFTPv3FileAttributes attr) throws IOException
  {
    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(fileName, charsetName);
    tw.writeUINT32(flags);
    tw.writeBytes(createAttrs(attr));

    if (debug != null)
    {
      debug.println("Sending SSH_FXP_OPEN...");
      debug.flush();
    }

    sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes());

    byte[] resp = receiveMessage(34000);

    TypesReader tr = new TypesReader(resp);

    int t = tr.readByte();

    int rep_id = tr.readUINT32();
    if (rep_id != req_id)
      throw new IOException("The server sent an invalid id field.");

    if (t == Packet.SSH_FXP_HANDLE)
    {
      if (debug != null)
      {
        debug.println("Got SSH_FXP_HANDLE.");
        debug.flush();
      }

      return new SFTPv3FileHandle(this, tr.readByteString());
    }

    if (t != Packet.SSH_FXP_STATUS)
      throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");

    int errorCode = tr.readUINT32();
    String errorMessage = tr.readString();

    throw new SFTPException(errorMessage, errorCode);
  }

  /**
   * Read bytes from a file. No more than 32768 bytes may be read at once.
   * Be aware that the semantics of read() are different than for Java streams.
   * <p>
   * <ul>
   * <li>The server will read as many bytes as it can from the file (up to <code>len</code>),
   * and return them.</li>
   * <li>If EOF is encountered before reading any data, <code>-1</code> is returned.
   * <li>If an error occurs, an exception is thrown</li>.
   * <li>For normal disk files, it is guaranteed that the server will return the specified
   * number of bytes, or up to end of file. For, e.g., device files this may return
   * fewer bytes than requested.</li>
   * </ul>
   *
   * @param handle a SFTPv3FileHandle handle
   * @param fileOffset offset (in bytes) in the file
   * @param dst the destination byte array
   * @param dstoff offset in the destination byte array
   * @param len how many bytes to read, 0 &lt; len &lt;= 32768 bytes
   * @return the number of bytes that could be read, may be less than requested if
   *         the end of the file is reached, -1 is returned in case of <code>EOF</code>
   * @throws IOException
   */
  public int read(SFTPv3FileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException
  {
    checkHandleValidAndOpen(handle);

    if ((len > 32768) || (len <= 0))
      throw new IllegalArgumentException("invalid len argument");

    int req_id = generateNextRequestID();

    TypesWriter tw = new TypesWriter();
    tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
    tw.writeUINT64(fileOffset);
    tw.writeUINT32(len);

    if (debug != null)
    {
      debug.println("Sending SSH_FXP_READ...");
      debug.flush();
    }

    sendMessage(Packet.SSH_FXP_READ, req_id, tw.getBytes());

    byte[] resp = receiveMessage(34000);

    TypesReader tr = new TypesReader(resp);

    int t = tr.readByte();

    int rep_id = tr.readUINT32();
    if (rep_id != req_id)
      throw new IOException("The server sent an invalid id field.");

    if (t == Packet.SSH_FXP_DATA)
    {
      if (debug != null)
      {
        debug.println("Got SSH_FXP_DATA...");
        debug.flush();
      }

      int readLen = tr.readUINT32();

      if ((readLen < 0) || (readLen > len))
        throw new IOException("The server sent an invalid length field.");

      tr.readBytes(dst, dstoff, readLen);

      return readLen;
    }

    if (t != Packet.SSH_FXP_STATUS)
      throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");

    int errorCode = tr.readUINT32();

    if (errorCode == ErrorCodes.SSH_FX_EOF)
    {
      if (debug != null)
      {
        debug.println("Got SSH_FX_EOF.");
        debug.flush();
      }

      return -1;
    }

    String errorMessage = tr.readString();

    throw new SFTPException(errorMessage, errorCode);
  }

  /**
   * Write bytes to a file. If <code>len</code> &gt; 32768, then the write operation will
   * be split into multiple writes.
   *
   * @param handle a SFTPv3FileHandle handle.
   * @param fileOffset offset (in bytes) in the file.
   * @param src the source byte array.
   * @param srcoff offset in the source byte array.
   * @param len how many bytes to write.
   * @throws IOException
   */
  public void write(SFTPv3FileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException
  {
    checkHandleValidAndOpen(handle);

    if (len < 0)

      while (len > 0)
      {
        int writeRequestLen = len;

        if (writeRequestLen > 32768)
          writeRequestLen = 32768;

        int req_id = generateNextRequestID();

        TypesWriter tw = new TypesWriter();
        tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
        tw.writeUINT64(fileOffset);
        tw.writeString(src, srcoff, writeRequestLen);

        if (debug != null)
        {
          debug.println("Sending SSH_FXP_WRITE...");
          debug.flush();
        }

        sendMessage(Packet.SSH_FXP_WRITE, req_id, tw.getBytes());

        fileOffset += writeRequestLen;

        srcoff += writeRequestLen;
        len -= writeRequestLen;

        byte[] resp = receiveMessage(34000);

        TypesReader tr = new TypesReader(resp);

        int t = tr.readByte();

        int rep_id = tr.readUINT32();
        if (rep_id != req_id)
          throw new IOException("The server sent an invalid id field.");

        if (t != Packet.SSH_FXP_STATUS)
          throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");

        int errorCode = tr.readUINT32();

        if (errorCode == ErrorCodes.SSH_FX_OK)
          continue;

        String errorMessage = tr.readString();

        throw new SFTPException(errorMessage, errorCode);
      }
  }

  /**
   * Close a file.
   *
   * @param handle a SFTPv3FileHandle handle
   * @throws IOException
   */
  public void closeFile(SFTPv3FileHandle handle) throws IOException
  {
    if (handle == null)
      throw new IllegalArgumentException("the handle argument may not be null");

    try
    {
      if (handle.isClosed == false)
      {
        closeHandle(handle.fileHandle);
      }
    }
    finally
    {
      handle.isClosed = true;
    }
  }
}
TOP

Related Classes of ch.ethz.ssh2.SFTPv3Client

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.