Logo Search packages:      
Sourcecode: jenkins-trilead-ssh2 version File versions  Download package

SFTPv3Client.java

package com.trilead.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 com.trilead.ssh2.packets.TypesReader;
import com.trilead.ssh2.packets.TypesWriter;
import com.trilead.ssh2.sftp.AttribFlags;
import com.trilead.ssh2.sftp.ErrorCodes;
import com.trilead.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@trilead.com
 * @version $Id: SFTPv3Client.java,v 1.2 2008/03/03 07:01:36 cplattne Exp $
 */
00061 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.
       */
00089       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
       */
00118       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
       */
00142       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)
       */
00168       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
       */
00237       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
       */
00354       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
       */
00454       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
       */
00469       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
       */
00481       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
       */
00568       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
       */
00596       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
       */
00625       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
       */
00657       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());
            
                  /* Some servers send here a packet with size > 34000 */
                  /* To whom it may concern: please learn to read the specs. */
                  
                  byte[] resp = receiveMessage(65536);

                  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".
       * 
       */
00902       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.
       * 
       */
00916       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
       */
00928       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" (remember that
       *                         this is octal noation). The server will likely apply a umask.
       * 
       * @throws IOException
       */
00945       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
       */
00965       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
       */
00983       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
       */
01002       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
       */
01022       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
       */
01034       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
       */
01054       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
       */
01073       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
       */
01086       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
       */
01105       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
       */
01228       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
       */
01310       public void write(SFTPv3FileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException
      {
            checkHandleValidAndOpen(handle);

            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
       */
01371       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;
            }
      }

    /**
     * Checks if the given path exists.
     */
01392     public boolean exists(String path) throws IOException {
        return _stat(path)!=null;
    }

    /**
     * Graceful {@link #stat(String)} that returns null if the path doesn't exist.
     */
01399     public SFTPv3FileAttributes _stat(String path) throws IOException {
        try {
            return stat(path);
        } catch (SFTPException e) {
            int c = e.getServerErrorCode();
            if (c== ErrorCodes.SSH_FX_NO_SUCH_FILE || c==ErrorCodes.SSH_FX_NO_SUCH_PATH)
                return null;
            else
                throw e;
        }
    }

    /**
     * Makes sure that the directory exists, by creating it if necessary.
     */
01414     public void mkdirs(String path, int posixPermission) throws IOException {
        SFTPv3FileAttributes atts = _stat(path);
        if (atts!=null && atts.isDirectory())
            return;

        int idx = path.lastIndexOf("/");
        if (idx>0)
            mkdirs(path.substring(0,idx), posixPermission);

        try {
            mkdir(path, posixPermission);
        } catch (IOException e) {
            throw (IOException)new IOException("Failed to mkdir "+path).initCause(e);
        }
    }

    /**
     * Creates a new file and writes to it.
     */
01433     public OutputStream writeToFile(String path) throws IOException {
        final SFTPv3FileHandle h = createFile(path);
        return new OutputStream() {
            private long offset = 0;
            public void write(int b) throws IOException {
                write(new byte[]{(byte)b});
            }

            public void write(byte[] b, int off, int len) throws IOException {
                SFTPv3Client.this.write(h,offset,b,off,len);
                offset += len;
            }

            public void close() throws IOException {
                closeFile(h);
            }
        };
    }

    public InputStream read(String file) throws IOException {
        final SFTPv3FileHandle h = openFileRO(file);
        return new InputStream() {
            private long offset = 0;

            public int read() throws IOException {
                byte[] b = new byte[1];
                if(read(b)<0)
                    return -1;
                return b[0];
            }

            public int read(byte[] b, int off, int len) throws IOException {
                int r = SFTPv3Client.this.read(h,offset,b,off,len);
                if (r<0)    return -1;
                offset += r;
                return r;
            }

            public long skip(long n) throws IOException {
                offset += n;
                return n;
            }

            public void close() throws IOException {
                closeFile(h);
            }
        };
    }

    public void chmod(String path, int permissions) throws IOException {
        SFTPv3FileAttributes atts = new SFTPv3FileAttributes();
        atts.permissions = Integer.valueOf(permissions);
        setstat(path, atts);
    }
}

Generated by  Doxygen 1.6.0   Back to index