diff options
author | Kenny Root <kenny@the-b.org> | 2013-04-11 21:01:32 -0700 |
---|---|---|
committer | Kenny Root <kenny@the-b.org> | 2013-04-11 21:01:32 -0700 |
commit | 3359a7f6d20f4d799140e304f646491863735028 (patch) | |
tree | dd2f06200b213687aff983bd2c67cec609f612ef /lib/src/main/java/com/trilead/ssh2/SFTPv3Client.java | |
parent | 54ca2c37bda02ff6f2579a20e122f3a893da705d (diff) | |
download | sshlib-3359a7f6d20f4d799140e304f646491863735028.tar.gz sshlib-3359a7f6d20f4d799140e304f646491863735028.tar.bz2 sshlib-3359a7f6d20f4d799140e304f646491863735028.zip |
Fix line endings
Diffstat (limited to 'lib/src/main/java/com/trilead/ssh2/SFTPv3Client.java')
-rw-r--r-- | lib/src/main/java/com/trilead/ssh2/SFTPv3Client.java | 2776 |
1 files changed, 1388 insertions, 1388 deletions
diff --git a/lib/src/main/java/com/trilead/ssh2/SFTPv3Client.java b/lib/src/main/java/com/trilead/ssh2/SFTPv3Client.java index be2fa1c..06796e9 100644 --- a/lib/src/main/java/com/trilead/ssh2/SFTPv3Client.java +++ b/lib/src/main/java/com/trilead/ssh2/SFTPv3Client.java @@ -1,1388 +1,1388 @@ -
-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.3 2008/04/01 12:38:09 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 Long(((long)tr.readUINT32()) & 0xffffffffl);
- fa.mtime = new Long(((long)tr.readUINT32()) & 0xffffffffl);
-
- }
-
- 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());
-
- /* 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".
- *
- */
- 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" (remember that
- * this is octal noation). 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 < len <= 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> > 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);
-
- 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;
- }
- }
-}
+ +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.3 2008/04/01 12:38:09 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 Long(((long)tr.readUINT32()) & 0xffffffffl); + fa.mtime = new Long(((long)tr.readUINT32()) & 0xffffffffl); + + } + + 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()); + + /* 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". + * + */ + 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" (remember that + * this is octal noation). 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 < len <= 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> > 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); + + 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; + } + } +} |