148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood/*
248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * Copyright (c) 2006-2011 Christian Plattner. All rights reserved.
348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * Please refer to the LICENSE.txt for licensing details.
448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood */
548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodpackage ch.ethz.ssh2;
648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport java.io.BufferedOutputStream;
848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport java.io.IOException;
948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport java.io.InputStream;
1048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport java.io.OutputStream;
1148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport java.nio.charset.Charset;
1248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport java.nio.charset.UnsupportedCharsetException;
1348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport java.util.HashMap;
1448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport java.util.List;
1548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport java.util.Map;
1648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport java.util.Vector;
1748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
1848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport ch.ethz.ssh2.channel.Channel;
1948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport ch.ethz.ssh2.log.Logger;
2048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport ch.ethz.ssh2.packets.TypesReader;
2148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport ch.ethz.ssh2.packets.TypesWriter;
2248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport ch.ethz.ssh2.sftp.AttribFlags;
2348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport ch.ethz.ssh2.sftp.ErrorCodes;
2448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodimport ch.ethz.ssh2.sftp.Packet;
2548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
2648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood/**
2748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * A <code>SFTPv3Client</code> represents a SFTP (protocol version 3)
2848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * client connection tunnelled over a SSH-2 connection. This is a very simple
2948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * (synchronous) implementation.
3048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * <p/>
3148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * Basically, most methods in this class map directly to one of
3248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * the packet types described in draft-ietf-secsh-filexfer-02.txt.
3348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * <p/>
3448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * Note: this is experimental code.
3548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * <p/>
3648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * Error handling: the methods of this class throw IOExceptions. However, unless
3748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will
3848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * be thrown (a subclass of IOException). Therefore, you can implement more verbose
3948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * behavior by checking if a thrown exception if of this type. If yes, then you
4048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * can cast the exception and access detailed information about the failure.
4148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * <p/>
4248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * Notes about file names, directory names and paths, copy-pasted
4348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * from the specs:
4448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * <ul>
4548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * <li>SFTP v3 represents file names as strings. File names are
4648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * assumed to use the slash ('/') character as a directory separator.</li>
4748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * <li>File names starting with a slash are "absolute", and are relative to
4848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * the root of the file system.  Names starting with any other character
4948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * are relative to the user's default directory (home directory).</li>
5048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * <li>Servers SHOULD interpret a path name component ".." as referring to
5148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * the parent directory, and "." as referring to the current directory.
5248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * If the server implementation limits access to certain parts of the
5348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * file system, it must be extra careful in parsing file names when
5448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * enforcing such restrictions.  There have been numerous reported
5548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * security bugs where a ".." in a path name has allowed access outside
5648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * the intended area.</li>
5748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * <li>An empty path name is valid, and it refers to the user's default
5848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * directory (usually the user's home directory).</li>
5948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * </ul>
6048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * <p/>
6148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * If you are still not tired then please go on and read the comment for
6248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * {@link #setCharset(String)}.
6348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood *
6448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * @author Christian Plattner, plattner@inf.ethz.ch
6548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood * @version $Id: SFTPv3Client.java 46 2011-07-06 08:40:29Z dkocher@sudo.ch $
6648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood */
6748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwoodpublic class SFTPv3Client
6848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood{
6948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private static final Logger log = Logger.getLogger(SFTPv3Client.class);
7048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
7148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private Session sess;
7248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
7348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private InputStream is;
7448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private OutputStream os;
7548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
7648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private int protocol_version = 0;
7748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
7848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private int next_request_id = 1000;
7948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
8048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private String charsetName = null;
8148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
8248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
8348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
8448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
8548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private PacketListener listener;
8648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
8748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
8848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Create a SFTP v3 client.
8948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
9048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param conn The underlying SSH-2 connection to be used.
9148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
9248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
9348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3Client(Connection conn, PacketListener listener) throws IOException
9448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
9548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (conn == null)
9648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
9748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IllegalArgumentException("Cannot accept null argument!");
9848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
9948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
10048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		this.listener = listener;
10148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
10248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Opening session and starting SFTP subsystem.");
10348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sess = conn.openSession();
10448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sess.startSubSystem("sftp");
10548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
10648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		is = sess.getStdout();
10748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		os = new BufferedOutputStream(sess.getStdin(), 2048);
10848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
10948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (is == null)
11048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
11148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("There is a problem with the streams of the underlying channel.");
11248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
11348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
11448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		init();
11548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
11648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
11748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
11848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Create a SFTP v3 client.
11948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
12048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param conn The underlying SSH-2 connection to be used.
12148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
12248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
12348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3Client(Connection conn) throws IOException
12448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
12548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		this(conn, new PacketListener()
12648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
12748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			public void read(String packet)
12848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
12948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				log.debug("Read packet " + packet);
13048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
13148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
13248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			public void write(String packet)
13348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
13448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				log.debug("Write packet " + packet);
13548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
13648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		});
13748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
13848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
13948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
14048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Set the charset used to convert between Java Unicode Strings and byte encodings
14148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * used by the server for paths and file names. Unfortunately, the SFTP v3 draft
14248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * says NOTHING about such conversions (well, with the exception of error messages
14348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * which have to be in UTF-8). Newer drafts specify to use UTF-8 for file names
14448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * (if I remember correctly). However, a quick test using OpenSSH serving a EXT-3
14548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * filesystem has shown that UTF-8 seems to be a bad choice for SFTP v3 (tested with
14648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * filenames containing german umlauts). "windows-1252" seems to work better for Europe.
14748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Luckily, "windows-1252" is the platform default in my case =).
14848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * <p/>
14948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * If you don't set anything, then the platform default will be used (this is the default
15048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * behavior).
15148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
15248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param charset the name of the charset to be used or <code>null</code> to use the platform's
15348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * default encoding.
15448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
15548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @see #getCharset()
15648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
15748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public void setCharset(String charset) throws IOException
15848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
15948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (charset == null)
16048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
16148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			charsetName = charset;
16248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			return;
16348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
16448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
16548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		try
16648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
16748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			Charset.forName(charset);
16848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
16948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		catch (UnsupportedCharsetException e)
17048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
17148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw (IOException) new IOException("This charset is not supported").initCause(e);
17248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
17348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		charsetName = charset;
17448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
17548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
17648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
17748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * The currently used charset for filename encoding/decoding.
17848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
17948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return The name of the charset (<code>null</code> if the platform's default charset is being used)
18048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @see #setCharset(String)
18148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
18248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public String getCharset()
18348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
18448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return charsetName;
18548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
18648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
18748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private void checkHandleValidAndOpen(SFTPv3FileHandle handle) throws IOException
18848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
18948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (handle.client != this)
19048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
19148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The file handle was created with another SFTPv3FileHandle instance.");
19248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
19348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
19448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (handle.isClosed)
19548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
19648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The file handle is closed.");
19748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
19848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
19948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
20048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException
20148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
20248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.write(Packet.forName(type));
20348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
20448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int msglen = len + 1;
20548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
20648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (type != Packet.SSH_FXP_INIT)
20748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
20848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			msglen += 4;
20948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
21048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
21148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		os.write(msglen >> 24);
21248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		os.write(msglen >> 16);
21348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		os.write(msglen >> 8);
21448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		os.write(msglen);
21548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		os.write(type);
21648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
21748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (type != Packet.SSH_FXP_INIT)
21848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
21948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			os.write(requestId >> 24);
22048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			os.write(requestId >> 16);
22148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			os.write(requestId >> 8);
22248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			os.write(requestId);
22348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
22448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
22548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		os.write(msg, off, len);
22648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		os.flush();
22748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
22848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
22948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private void sendMessage(int type, int requestId, byte[] msg) throws IOException
23048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
23148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(type, requestId, msg, 0, msg.length);
23248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
23348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
23448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private void readBytes(byte[] buff, int pos, int len) throws IOException
23548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
23648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		while (len > 0)
23748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
23848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			int count = is.read(buff, pos, len);
23948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (count < 0)
24048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
24148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				throw new IOException("Unexpected end of sftp stream.");
24248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
24348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if ((count == 0) || (count > len))
24448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
24548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				throw new IOException("Underlying stream implementation is bogus!");
24648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
24748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			len -= count;
24848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			pos += count;
24948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
25048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
25148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
25248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
25348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Read a message and guarantee that the <b>contents</b> is not larger than
25448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * <code>maxlen</code> bytes.
25548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * <p/>
25648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Note: receiveMessage(34000) actually means that the message may be up to 34004
25748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * bytes (the length attribute preceeding the contents is 4 bytes).
25848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
25948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param maxlen
26048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return the message contents
26148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
26248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
26348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private byte[] receiveMessage(int maxlen) throws IOException
26448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
26548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		byte[] msglen = new byte[4];
26648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
26748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		readBytes(msglen, 0, 4);
26848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
26948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff));
27048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
27148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if ((len > maxlen) || (len <= 0))
27248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
27348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("Illegal sftp packet len: " + len);
27448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
27548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
27648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		byte[] msg = new byte[len];
27748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
27848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		readBytes(msg, 0, len);
27948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
28048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return msg;
28148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
28248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
28348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private int generateNextRequestID()
28448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
28548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		synchronized (this)
28648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
28748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			return next_request_id++;
28848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
28948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
29048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
29148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private void closeHandle(byte[] handle) throws IOException
29248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
29348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
29448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
29548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
29648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(handle, 0, handle.length);
29748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
29848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes());
29948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
30048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		expectStatusOKMessage(req_id);
30148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
30248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
30348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private SFTPv3FileAttributes readAttrs(TypesReader tr) throws IOException
30448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
30548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		/*
30648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   * uint32   flags
30748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   * uint64   size           present only if flag SSH_FILEXFER_ATTR_SIZE
30848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   * uint32   uid            present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
30948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   * uint32   gid            present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
31048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   * uint32   permissions    present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
31148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   * uint32   atime          present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
31248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   * uint32   mtime          present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
31348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   * uint32   extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
31448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   * string   extended_type
31548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   * string   extended_data
31648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   * ...      more extended data (extended_type - extended_data pairs),
31748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   *          so that number of pairs equals extended_count
31848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   */
31948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
32048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		SFTPv3FileAttributes fa = new SFTPv3FileAttributes();
32148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
32248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int flags = tr.readUINT32();
32348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
32448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0)
32548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
32648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			log.debug("SSH_FILEXFER_ATTR_SIZE");
32748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			fa.size = tr.readUINT64();
32848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
32948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
33048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0)
33148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
33248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			log.debug("SSH_FILEXFER_ATTR_V3_UIDGID");
33348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			fa.uid = tr.readUINT32();
33448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			fa.gid = tr.readUINT32();
33548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
33648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
33748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0)
33848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
33948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			log.debug("SSH_FILEXFER_ATTR_PERMISSIONS");
34048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			fa.permissions = tr.readUINT32();
34148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
34248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
34348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0)
34448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
34548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			log.debug("SSH_FILEXFER_ATTR_V3_ACMODTIME");
34648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			fa.atime = tr.readUINT32();
34748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			fa.mtime = tr.readUINT32();
34848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
34948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
35048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
35148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0)
35248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
35348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			int count = tr.readUINT32();
35448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
35548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			log.debug("SSH_FILEXFER_ATTR_EXTENDED (" + count + ")");
35648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			/* Read it anyway to detect corrupt packets */
35748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
35848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			while (count > 0)
35948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
36048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				tr.readByteString();
36148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				tr.readByteString();
36248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				count--;
36348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
36448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
36548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
36648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return fa;
36748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
36848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
36948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
37048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Retrieve the file attributes of an open file.
37148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
37248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param handle a SFTPv3FileHandle handle.
37348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return a SFTPv3FileAttributes object.
37448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
37548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
37648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3FileAttributes fstat(SFTPv3FileHandle handle) throws IOException
37748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
37848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		checkHandleValidAndOpen(handle);
37948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
38048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
38148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
38248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
38348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
38448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
38548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Sending SSH_FXP_FSTAT...");
38648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes());
38748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
38848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		byte[] resp = receiveMessage(34000);
38948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
39048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesReader tr = new TypesReader(resp);
39148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
39248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int t = tr.readByte();
39348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(Packet.forName(t));
39448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
39548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int rep_id = tr.readUINT32();
39648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (rep_id != req_id)
39748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
39848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The server sent an invalid id field.");
39948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
40048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
40148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t == Packet.SSH_FXP_ATTRS)
40248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
40348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			return readAttrs(tr);
40448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
40548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
40648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t != Packet.SSH_FXP_STATUS)
40748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
40848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
40948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
41048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
41148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int errorCode = tr.readUINT32();
41248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		String errorMessage = tr.readString();
41348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(errorMessage);
41448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		throw new SFTPException(errorMessage, errorCode);
41548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
41648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
41748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException
41848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
41948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
42048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
42148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
42248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(path, charsetName);
42348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
42448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Sending SSH_FXP_STAT/SSH_FXP_LSTAT...");
42548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(statMethod, req_id, tw.getBytes());
42648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
42748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		byte[] resp = receiveMessage(34000);
42848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
42948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesReader tr = new TypesReader(resp);
43048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
43148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int t = tr.readByte();
43248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(Packet.forName(t));
43348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
43448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int rep_id = tr.readUINT32();
43548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (rep_id != req_id)
43648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
43748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The server sent an invalid id field.");
43848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
43948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
44048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t == Packet.SSH_FXP_ATTRS)
44148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
44248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			return readAttrs(tr);
44348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
44448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
44548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t != Packet.SSH_FXP_STATUS)
44648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
44748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
44848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
44948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
45048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int errorCode = tr.readUINT32();
45148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		String errorMessage = tr.readString();
45248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(errorMessage);
45348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		throw new SFTPException(errorMessage, errorCode);
45448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
45548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
45648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
45748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Retrieve the file attributes of a file. This method
45848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * follows symbolic links on the server.
45948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
46048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param path See the {@link SFTPv3Client comment} for the class for more details.
46148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return a SFTPv3FileAttributes object.
46248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
46348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @see #lstat(String)
46448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
46548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3FileAttributes stat(String path) throws IOException
46648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
46748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return statBoth(path, Packet.SSH_FXP_STAT);
46848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
46948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
47048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
47148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Retrieve the file attributes of a file. This method
47248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * does NOT follow symbolic links on the server.
47348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
47448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param path See the {@link SFTPv3Client comment} for the class for more details.
47548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return a SFTPv3FileAttributes object.
47648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
47748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @see #stat(String)
47848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
47948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3FileAttributes lstat(String path) throws IOException
48048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
48148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return statBoth(path, Packet.SSH_FXP_LSTAT);
48248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
48348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
48448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
48548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Read the target of a symbolic link. Note: OpenSSH (as of version 4.4) gets very upset
48648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * (SSH_FX_BAD_MESSAGE error) if you want to read the target of a file that is not a
48748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * symbolic link. Better check first with {@link #lstat(String)}.
48848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
48948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param path See the {@link SFTPv3Client comment} for the class for more details.
49048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return The target of the link.
49148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
49248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
49348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public String readLink(String path) throws IOException
49448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
49548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
49648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
49748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
49848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(path, charsetName);
49948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
50048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Sending SSH_FXP_READLINK...");
50148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes());
50248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
50348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		byte[] resp = receiveMessage(34000);
50448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
50548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesReader tr = new TypesReader(resp);
50648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
50748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int t = tr.readByte();
50848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(Packet.forName(t));
50948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
51048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int rep_id = tr.readUINT32();
51148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (rep_id != req_id)
51248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
51348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The server sent an invalid id field.");
51448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
51548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
51648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t == Packet.SSH_FXP_NAME)
51748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
51848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			int count = tr.readUINT32();
51948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
52048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (count != 1)
52148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
52248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");
52348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
52448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
52548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			return tr.readString(charsetName);
52648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
52748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
52848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t != Packet.SSH_FXP_STATUS)
52948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
53048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
53148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
53248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
53348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int errorCode = tr.readUINT32();
53448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		String errorMessage = tr.readString();
53548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(errorMessage);
53648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		throw new SFTPException(errorMessage, errorCode);
53748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
53848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
53948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private void expectStatusOKMessage(int id) throws IOException
54048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
54148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		byte[] resp = receiveMessage(34000);
54248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
54348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesReader tr = new TypesReader(resp);
54448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
54548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int t = tr.readByte();
54648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(Packet.forName(t));
54748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
54848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int rep_id = tr.readUINT32();
54948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (rep_id != id)
55048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
55148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The server sent an invalid id field.");
55248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
55348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
55448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t != Packet.SSH_FXP_STATUS)
55548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
55648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
55748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
55848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
55948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int errorCode = tr.readUINT32();
56048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
56148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (errorCode == ErrorCodes.SSH_FX_OK)
56248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
56348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			return;
56448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
56548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		String errorMessage = tr.readString();
56648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(errorMessage);
56748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		throw new SFTPException(errorMessage, errorCode);
56848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
56948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
57048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
57148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Modify the attributes of a file. Used for operations such as changing
57248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * the ownership, permissions or access times, as well as for truncating a file.
57348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
57448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param path See the {@link SFTPv3Client comment} for the class for more details.
57548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
57648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * made to the attributes of the file. Empty fields will be ignored.
57748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
57848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
57948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public void setstat(String path, SFTPv3FileAttributes attr) throws IOException
58048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
58148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
58248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
58348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
58448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(path, charsetName);
58548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeBytes(createAttrs(attr));
58648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
58748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Sending SSH_FXP_SETSTAT...");
58848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes());
58948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
59048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		expectStatusOKMessage(req_id);
59148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
59248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
59348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
59448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Modify the attributes of a file. Used for operations such as changing
59548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * the ownership, permissions or access times, as well as for truncating a file.
59648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
59748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param handle a SFTPv3FileHandle handle
59848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
59948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * made to the attributes of the file. Empty fields will be ignored.
60048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
60148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
60248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public void fsetstat(SFTPv3FileHandle handle, SFTPv3FileAttributes attr) throws IOException
60348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
60448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		checkHandleValidAndOpen(handle);
60548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
60648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
60748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
60848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
60948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
61048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeBytes(createAttrs(attr));
61148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
61248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Sending SSH_FXP_FSETSTAT...");
61348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes());
61448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
61548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		expectStatusOKMessage(req_id);
61648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
61748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
61848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
61948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Create a symbolic link on the server. Creates a link "src" that points
62048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * to "target".
62148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
62248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param src See the {@link SFTPv3Client comment} for the class for more details.
62348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param target See the {@link SFTPv3Client comment} for the class for more details.
62448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
62548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
62648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public void createSymlink(String src, String target) throws IOException
62748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
62848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
62948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
63048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		/* Either I am too stupid to understand the SFTP draft
63148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   * or the OpenSSH guys changed the semantics of src and target.
63248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				   */
63348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
63448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
63548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(target, charsetName);
63648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(src, charsetName);
63748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
63848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Sending SSH_FXP_SYMLINK...");
63948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes());
64048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
64148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		expectStatusOKMessage(req_id);
64248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
64348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
64448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
64548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Have the server canonicalize any given path name to an absolute path.
64648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * This is useful for converting path names containing ".." components or
64748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * relative pathnames without a leading slash into absolute paths.
64848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
64948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param path See the {@link SFTPv3Client comment} for the class for more details.
65048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return An absolute path.
65148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
65248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
65348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public String canonicalPath(String path) throws IOException
65448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
65548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
65648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
65748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
65848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(path, charsetName);
65948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
66048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Sending SSH_FXP_REALPATH...");
66148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes());
66248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
66348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		byte[] resp = receiveMessage(34000);
66448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
66548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesReader tr = new TypesReader(resp);
66648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
66748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int t = tr.readByte();
66848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(Packet.forName(t));
66948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
67048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int rep_id = tr.readUINT32();
67148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (rep_id != req_id)
67248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
67348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The server sent an invalid id field.");
67448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
67548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
67648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t == Packet.SSH_FXP_NAME)
67748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
67848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			int count = tr.readUINT32();
67948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
68048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (count != 1)
68148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
68248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");
68348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
68448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
68548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			final String name = tr.readString(charsetName);
68648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			listener.read(name);
68748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			return name;
68848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
68948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
69048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t != Packet.SSH_FXP_STATUS)
69148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
69248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
69348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
69448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
69548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int errorCode = tr.readUINT32();
69648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		String errorMessage = tr.readString();
69748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(errorMessage);
69848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		throw new SFTPException(errorMessage, errorCode);
69948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
70048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
70148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private List<SFTPv3DirectoryEntry> scanDirectory(byte[] handle) throws IOException
70248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
70348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		List<SFTPv3DirectoryEntry> files = new Vector<SFTPv3DirectoryEntry>();
70448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
70548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		while (true)
70648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
70748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			int req_id = generateNextRequestID();
70848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
70948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			TypesWriter tw = new TypesWriter();
71048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			tw.writeString(handle, 0, handle.length);
71148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
71248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			log.debug("Sending SSH_FXP_READDIR...");
71348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes());
71448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
71548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			byte[] resp = receiveMessage(34000);
71648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
71748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			TypesReader tr = new TypesReader(resp);
71848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
71948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			int t = tr.readByte();
72048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			listener.read(Packet.forName(t));
72148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
72248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			int rep_id = tr.readUINT32();
72348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (rep_id != req_id)
72448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
72548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				throw new IOException("The server sent an invalid id field.");
72648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
72748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
72848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (t == Packet.SSH_FXP_NAME)
72948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
73048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				int count = tr.readUINT32();
73148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
73248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				log.debug("Parsing " + count + " name entries...");
73348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				while (count > 0)
73448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				{
73548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					SFTPv3DirectoryEntry dirEnt = new SFTPv3DirectoryEntry();
73648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
73748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					dirEnt.filename = tr.readString(charsetName);
73848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					dirEnt.longEntry = tr.readString(charsetName);
73948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					listener.read(dirEnt.longEntry);
74048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
74148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					dirEnt.attributes = readAttrs(tr);
74248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					files.add(dirEnt);
74348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
74448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					log.debug("File: '" + dirEnt.filename + "'");
74548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					count--;
74648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				}
74748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				continue;
74848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
74948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
75048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (t != Packet.SSH_FXP_STATUS)
75148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
75248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
75348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
75448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
75548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			int errorCode = tr.readUINT32();
75648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
75748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (errorCode == ErrorCodes.SSH_FX_EOF)
75848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
75948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				return files;
76048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
76148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			String errorMessage = tr.readString();
76248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			listener.read(errorMessage);
76348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new SFTPException(errorMessage, errorCode);
76448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
76548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
76648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
76748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public final SFTPv3FileHandle openDirectory(String path) throws IOException
76848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
76948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
77048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
77148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
77248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(path, charsetName);
77348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
77448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Sending SSH_FXP_OPENDIR...");
77548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes());
77648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
77748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		byte[] resp = receiveMessage(34000);
77848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
77948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesReader tr = new TypesReader(resp);
78048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
78148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int t = tr.readByte();
78248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(Packet.forName(t));
78348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
78448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int rep_id = tr.readUINT32();
78548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (rep_id != req_id)
78648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
78748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The server sent an invalid id field.");
78848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
78948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
79048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t == Packet.SSH_FXP_HANDLE)
79148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
79248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			log.debug("Got SSH_FXP_HANDLE.");
79348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			return new SFTPv3FileHandle(this, tr.readByteString());
79448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
79548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
79648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t != Packet.SSH_FXP_STATUS)
79748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
79848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
79948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
80048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
80148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int errorCode = tr.readUINT32();
80248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		String errorMessage = tr.readString();
80348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(errorMessage);
80448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		throw new SFTPException(errorMessage, errorCode);
80548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
80648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
80748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private String expandString(byte[] b, int off, int len)
80848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
80948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		StringBuilder sb = new StringBuilder();
81048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
81148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		for (int i = 0; i < len; i++)
81248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
81348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			int c = b[off + i] & 0xff;
81448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
81548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if ((c >= 32) && (c <= 126))
81648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
81748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				sb.append((char) c);
81848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
81948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			else
82048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
82148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				sb.append("{0x" + Integer.toHexString(c) + "}");
82248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
82348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
82448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
82548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return sb.toString();
82648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
82748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
82848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private void init() throws IOException
82948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
83048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		/* Send SSH_FXP_INIT (version 3) */
83148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
83248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		final int client_version = 3;
83348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
83448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Sending SSH_FXP_INIT (" + client_version + ")...");
83548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
83648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeUINT32(client_version);
83748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes());
83848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
83948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		/* Receive SSH_FXP_VERSION */
84048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
84148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Waiting for SSH_FXP_VERSION...");
84248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */
84348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
84448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int t = tr.readByte();
84548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(Packet.forName(t));
84648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
84748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t != Packet.SSH_FXP_VERSION)
84848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
84948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The server did not send a SSH_FXP_VERSION packet (got " + t + ")");
85048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
85148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
85248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		protocol_version = tr.readUINT32();
85348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
85448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("SSH_FXP_VERSION: protocol_version = " + protocol_version);
85548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (protocol_version != 3)
85648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
85748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("Server version " + protocol_version + " is currently not supported");
85848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
85948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
86048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		/* Read and save extensions (if any) for later use */
86148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
86248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		while (tr.remain() != 0)
86348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
86448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			String name = tr.readString();
86548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			listener.read(name);
86648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			byte[] value = tr.readByteString();
86748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			log.debug("SSH_FXP_VERSION: extension: " + name + " = '" + expandString(value, 0, value.length) + "'");
86848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
86948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
87048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
87148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
87248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Returns the negotiated SFTP protocol version between the client and the server.
87348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
87448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return SFTP protocol version, i.e., "3".
87548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
87648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public int getProtocolVersion()
87748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
87848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return protocol_version;
87948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
88048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
88148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
88248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Queries the channel state
88348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return True if the underlying session is in open state
88448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
88548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public boolean isConnected() {
88648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return sess.getState() == Channel.STATE_OPEN;
88748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
88848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
88948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
89048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Close this SFTP session. NEVER forget to call this method to free up
89148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * resources - even if you got an exception from one of the other methods.
89248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Sometimes these other methods may throw an exception, saying that the
89348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * underlying channel is closed (this can happen, e.g., if the other server
89448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * sent a close message.) However, as long as you have not called the
89548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * <code>close()</code> method, you are likely wasting resources.
89648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
89748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public void close()
89848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
89948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sess.close();
90048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
90148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
90248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
90348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * List the contents of a directory.
90448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
90548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
90648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return A Vector containing {@link SFTPv3DirectoryEntry} objects.
90748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
90848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
90948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public List<SFTPv3DirectoryEntry> ls(String dirName) throws IOException
91048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
91148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood        SFTPv3FileHandle handle = openDirectory(dirName);
91248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood        List<SFTPv3DirectoryEntry> result = scanDirectory(handle.fileHandle);
91348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		closeFile(handle);
91448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return result;
91548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
91648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
91748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
91848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Create a new directory.
91948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
92048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
92148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param posixPermissions the permissions for this directory, e.g., "0700" (remember that
92248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * this is octal noation). The server will likely apply a umask.
92348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
92448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
92548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public void mkdir(String dirName, int posixPermissions) throws IOException
92648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
92748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
92848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
92948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
93048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(dirName, charsetName);
93148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS);
93248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeUINT32(posixPermissions);
93348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
93448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes());
93548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
93648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		expectStatusOKMessage(req_id);
93748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
93848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
93948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
94048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Remove a file.
94148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
94248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
94348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
94448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
94548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public void rm(String fileName) throws IOException
94648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
94748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
94848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
94948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
95048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(fileName, charsetName);
95148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
95248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes());
95348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
95448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		expectStatusOKMessage(req_id);
95548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
95648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
95748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
95848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Remove an empty directory.
95948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
96048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
96148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
96248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
96348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public void rmdir(String dirName) throws IOException
96448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
96548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
96648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
96748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
96848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(dirName, charsetName);
96948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
97048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes());
97148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
97248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		expectStatusOKMessage(req_id);
97348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
97448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
97548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
97648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Move a file or directory.
97748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
97848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param oldPath See the {@link SFTPv3Client comment} for the class for more details.
97948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param newPath See the {@link SFTPv3Client comment} for the class for more details.
98048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
98148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
98248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public void mv(String oldPath, String newPath) throws IOException
98348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
98448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
98548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
98648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
98748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(oldPath, charsetName);
98848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(newPath, charsetName);
98948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
99048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes());
99148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
99248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		expectStatusOKMessage(req_id);
99348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
99448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
99548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
99648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Open the file for reading.
99748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
99848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public static final int SSH_FXF_READ = 0x00000001;
99948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
100048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Open the file for writing.  If both this and SSH_FXF_READ are
100148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * specified, the file is opened for both reading and writing.
100248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
100348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public static final int SSH_FXF_WRITE = 0x00000002;
100448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
100548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Force all writes to append data at the end of the file.
100648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
100748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public static final int SSH_FXF_APPEND = 0x00000004;
100848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
100948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * If this flag is specified, then a new file will be created if one
101048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * does not alread exist (if O_TRUNC is specified, the new file will
101148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * be truncated to zero length if it previously exists).
101248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
101348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public static final int SSH_FXF_CREAT = 0x00000008;
101448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
101548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Forces an existing file with the same name to be truncated to zero
101648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * length when creating a file by specifying SSH_FXF_CREAT.
101748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * SSH_FXF_CREAT MUST also be specified if this flag is used.
101848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
101948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public static final int SSH_FXF_TRUNC = 0x00000010;
102048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
102148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Causes the request to fail if the named file already exists.
102248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
102348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public static final int SSH_FXF_EXCL = 0x00000020;
102448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
102548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
102648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Open a file for reading.
102748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
102848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
102948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return a SFTPv3FileHandle handle
103048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
103148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
103248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3FileHandle openFileRO(String fileName) throws IOException
103348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
103448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return openFile(fileName, SSH_FXF_READ, null);
103548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
103648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
103748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
103848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Open a file for reading and writing.
103948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
104048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
104148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return a SFTPv3FileHandle handle
104248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
104348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
104448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3FileHandle openFileRW(String fileName) throws IOException
104548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
104648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return openFile(fileName, SSH_FXF_READ | SSH_FXF_WRITE, null);
104748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
104848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
104948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
105048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Open a file in append mode. The SFTP v3 draft says nothing but assuming normal POSIX
105148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * behavior, all writes will be appendend to the end of the file, no matter which offset
105248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * one specifies.
105348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * <p/>
105448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * A side note for the curious: OpenSSH does an lseek() to the specified writing offset before each write(),
105548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * even for writes to files opened in O_APPEND mode. However, bear in mind that when working
105648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * in the O_APPEND mode, each write() includes an implicit lseek() to the end of the file
105748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * (well, this is what the newsgroups say).
105848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
105948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
106048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return a SFTPv3FileHandle handle
106148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
106248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
106348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3FileHandle openFileRWAppend(String fileName) throws IOException
106448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
106548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return openFile(fileName, SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND, null);
106648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
106748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
106848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
106948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Open a file in append mode. The SFTP v3 draft says nothing but assuming normal POSIX
107048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * behavior, all writes will be appendend to the end of the file, no matter which offset
107148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * one specifies.
107248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * <p/>
107348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * A side note for the curious: OpenSSH does an lseek() to the specified writing offset before each write(),
107448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * even for writes to files opened in O_APPEND mode. However, bear in mind that when working
107548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * in the O_APPEND mode, each write() includes an implicit lseek() to the end of the file
107648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * (well, this is what the newsgroups say).
107748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
107848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
107948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return a SFTPv3FileHandle handle
108048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
108148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
108248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3FileHandle openFileWAppend(String fileName) throws IOException
108348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
108448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return openFile(fileName, SSH_FXF_WRITE | SSH_FXF_APPEND, null);
108548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
108648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
108748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
108848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Create a file and open it for reading and writing.
108948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Same as {@link #createFile(String, SFTPv3FileAttributes) createFile(fileName, null)}.
109048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
109148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
109248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return a SFTPv3FileHandle handle
109348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
109448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
109548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3FileHandle createFile(String fileName) throws IOException
109648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
109748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return createFile(fileName, null);
109848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
109948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
110048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
110148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Create a file and open it for reading and writing.
110248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * You can specify the default attributes of the file (the server may or may
110348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * not respect your wishes).
110448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
110548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
110648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param attr may be <code>null</code> to use server defaults. Probably only
110748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * the <code>uid</code>, <code>gid</code> and <code>permissions</code>
110848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
110948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * structure make sense. You need only to set those fields where you want
111048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * to override the server's defaults.
111148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return a SFTPv3FileHandle handle
111248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
111348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
111448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3FileHandle createFile(String fileName, SFTPv3FileAttributes attr) throws IOException
111548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
111648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return openFile(fileName, SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE, attr);
111748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
111848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
111948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
112048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Create a file (truncate it if it already exists) and open it for writing.
112148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Same as {@link #createFileTruncate(String, SFTPv3FileAttributes) createFileTruncate(fileName, null)}.
112248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
112348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
112448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return a SFTPv3FileHandle handle
112548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
112648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
112748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3FileHandle createFileTruncate(String fileName) throws IOException
112848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
112948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return createFileTruncate(fileName, null);
113048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
113148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
113248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
113348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * reate a file (truncate it if it already exists) and open it for writing.
113448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * You can specify the default attributes of the file (the server may or may
113548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * not respect your wishes).
113648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
113748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
113848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param attr may be <code>null</code> to use server defaults. Probably only
113948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * the <code>uid</code>, <code>gid</code> and <code>permissions</code>
114048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
114148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * structure make sense. You need only to set those fields where you want
114248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * to override the server's defaults.
114348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return a SFTPv3FileHandle handle
114448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
114548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
114648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3FileHandle createFileTruncate(String fileName, SFTPv3FileAttributes attr) throws IOException
114748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
114848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return openFile(fileName, SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_WRITE, attr);
114948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
115048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
115148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private byte[] createAttrs(SFTPv3FileAttributes attr)
115248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
115348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
115448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
115548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int attrFlags = 0;
115648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
115748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (attr == null)
115848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
115948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			tw.writeUINT32(0);
116048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
116148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		else
116248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
116348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (attr.size != null)
116448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
116548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE;
116648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
116748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
116848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if ((attr.uid != null) && (attr.gid != null))
116948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
117048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID;
117148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
117248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
117348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (attr.permissions != null)
117448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
117548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS;
117648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
117748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
117848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if ((attr.atime != null) && (attr.mtime != null))
117948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
118048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME;
118148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
118248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
118348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			tw.writeUINT32(attrFlags);
118448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
118548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (attr.size != null)
118648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
118748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				tw.writeUINT64(attr.size);
118848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
118948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
119048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if ((attr.uid != null) && (attr.gid != null))
119148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
119248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				tw.writeUINT32(attr.uid);
119348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				tw.writeUINT32(attr.gid);
119448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
119548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
119648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (attr.permissions != null)
119748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
119848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				tw.writeUINT32(attr.permissions);
119948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
120048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
120148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if ((attr.atime != null) && (attr.mtime != null))
120248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
120348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				tw.writeUINT32(attr.atime);
120448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				tw.writeUINT32(attr.mtime);
120548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
120648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
120748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
120848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		return tw.getBytes();
120948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
121048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
121148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public SFTPv3FileHandle openFile(String fileName, int flags, SFTPv3FileAttributes attr) throws IOException
121248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
121348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id = generateNextRequestID();
121448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
121548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
121648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(fileName, charsetName);
121748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeUINT32(flags);
121848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeBytes(createAttrs(attr));
121948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
122048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Sending SSH_FXP_OPEN...");
122148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes());
122248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
122348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		byte[] resp = receiveMessage(34000);
122448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
122548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesReader tr = new TypesReader(resp);
122648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
122748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int t = tr.readByte();
122848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(Packet.forName(t));
122948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
123048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int rep_id = tr.readUINT32();
123148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (rep_id != req_id)
123248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
123348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The server sent an invalid id field.");
123448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
123548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
123648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t == Packet.SSH_FXP_HANDLE)
123748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
123848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			log.debug("Got SSH_FXP_HANDLE.");
123948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			return new SFTPv3FileHandle(this, tr.readByteString());
124048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
124148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
124248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t != Packet.SSH_FXP_STATUS)
124348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
124448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
124548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
124648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
124748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int errorCode = tr.readUINT32();
124848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		String errorMessage = tr.readString();
124948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(errorMessage);
125048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		throw new SFTPException(errorMessage, errorCode);
125148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
125248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
125348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
125448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * A read  is divided into multiple requests sent sequentially before
125548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * reading any status from the server
125648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
125748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private static class OutstandingReadRequest
125848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
125948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id;
126048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		/**
126148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		 * Read offset to request on server starting at the file offset for the first request.
126248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		 */
126348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		long serverOffset;
126448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		/**
126548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		 * Length of requested data
126648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		 */
126748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int len;
126848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		/**
126948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		 * Offset in destination buffer
127048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		 */
127148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int dstOffset;
127248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		/**
127348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		 * Temporary buffer
127448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		 */
127548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		byte[] buffer;
127648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
127748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
127848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private void sendReadRequest(int id, SFTPv3FileHandle handle, long offset, int len) throws IOException
127948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
128048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
128148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
128248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeUINT64(offset);
128348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeUINT32(len);
128448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
128548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Sending SSH_FXP_READ (" + id + ") " + offset + "/" + len);
128648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_READ, id, tw.getBytes());
128748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
128848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
128948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
129048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Parallel read requests maximum size.
129148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
129248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private static final int DEFAULT_MAX_PARALLELISM = 64;
129348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
129448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
129548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Parallel read requests.
129648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
129748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private int parallelism = DEFAULT_MAX_PARALLELISM;
129848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
129948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
130048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param parallelism
130148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
130248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public void setRequestParallelism(int parallelism)
130348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
130448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		this.parallelism = Math.min(parallelism, DEFAULT_MAX_PARALLELISM);
130548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
130648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
130748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
130848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Mapping request ID to request.
130948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
131048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	Map<Integer, OutstandingReadRequest> pendingReadQueue
131148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			= new HashMap<Integer, OutstandingReadRequest>();
131248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
131348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
131448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Read bytes from a file in a parallel fashion. As many bytes as you want will be read.
131548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * <p/>
131648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * <ul>
131748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * <li>The server will read as many bytes as it can from the file (up to <code>len</code>),
131848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * and return them.</li>
131948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * <li>If EOF is encountered before reading any data, <code>-1</code> is returned.
132048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * <li>If an error occurs, an exception is thrown</li>.
132148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * <li>For normal disk files, it is guaranteed that the server will return the specified
132248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * number of bytes, or up to end of file. For, e.g., device files this may return
132348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * fewer bytes than requested.</li>
132448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * </ul>
132548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
132648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param handle a SFTPv3FileHandle handle
132748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param fileOffset offset (in bytes) in the file
132848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param dst the destination byte array
132948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param dstoff offset in the destination byte array
133048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param len how many bytes to read, 0 &lt; len
133148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @return the number of bytes that could be read, may be less than requested if
133248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *         the end of the file is reached, -1 is returned in case of <code>EOF</code>
133348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
133448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
133548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public int read(SFTPv3FileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException
133648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
133748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		boolean errorOccured = false;
133848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
133948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		checkHandleValidAndOpen(handle);
134048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
134148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int remaining = len * parallelism;
134248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int clientOffset = dstoff;
134348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
134448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		long serverOffset = fileOffset;
134548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		for (OutstandingReadRequest r : pendingReadQueue.values())
134648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
134748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			// Server offset should take pending requests into account.
134848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			serverOffset += r.len;
134948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
135048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
135148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		while (true)
135248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
135348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			// Stop if there was an error and no outstanding request
135448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if ((pendingReadQueue.size() == 0) && errorOccured)
135548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
135648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				break;
135748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
135848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
135948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			// Send as many requests as we are allowed to
136048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			while (pendingReadQueue.size() < parallelism)
136148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
136248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				if (errorOccured)
136348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				{
136448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					break;
136548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				}
136648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				// Send the next read request
136748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				OutstandingReadRequest req = new OutstandingReadRequest();
136848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				req.req_id = generateNextRequestID();
136948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				req.serverOffset = serverOffset;
137048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				req.len = (remaining > len) ? len : remaining;
137148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				req.buffer = dst;
137248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				req.dstOffset = dstoff;
137348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
137448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				serverOffset += req.len;
137548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				clientOffset += req.len;
137648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				remaining -= req.len;
137748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
137848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				sendReadRequest(req.req_id, handle, req.serverOffset, req.len);
137948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
138048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				pendingReadQueue.put(req.req_id, req);
138148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
138248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (pendingReadQueue.size() == 0)
138348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
138448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				break;
138548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
138648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
138748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			// Receive a single answer
138848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			byte[] resp = receiveMessage(34000);
138948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			TypesReader tr = new TypesReader(resp);
139048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
139148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			int t = tr.readByte();
139248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			listener.read(Packet.forName(t));
139348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
139448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			// Search the pending queue
139548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			OutstandingReadRequest req = pendingReadQueue.remove(tr.readUINT32());
139648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (null == req)
139748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
139848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				throw new IOException("The server sent an invalid id field.");
139948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
140048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			// Evaluate the answer
140148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (t == Packet.SSH_FXP_STATUS)
140248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
140348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				/* In any case, stop sending more packets */
140448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
140548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				int code = tr.readUINT32();
140648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				String msg = tr.readString();
140748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				listener.read(msg);
140848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
140948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				if (log.isDebugEnabled())
141048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				{
141148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					String[] desc = ErrorCodes.getDescription(code);
141248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					log.debug("Got SSH_FXP_STATUS (" + req.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")");
141348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				}
141448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				// Flag to read all pending requests but don't send any more.
141548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				errorOccured = true;
141648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				if (pendingReadQueue.isEmpty())
141748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				{
141848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					if (ErrorCodes.SSH_FX_EOF == code)
141948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					{
142048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood						return -1;
142148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					}
142248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					throw new SFTPException(msg, code);
142348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				}
142448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
142548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			else if (t == Packet.SSH_FXP_DATA)
142648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
142748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				// OK, collect data
142848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				int readLen = tr.readUINT32();
142948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
143048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				if ((readLen < 0) || (readLen > req.len))
143148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				{
143248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					throw new IOException("The server sent an invalid length field in a SSH_FXP_DATA packet.");
143348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				}
143448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
143548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				if (log.isDebugEnabled())
143648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				{
143748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					log.debug("Got SSH_FXP_DATA (" + req.req_id + ") " + req.serverOffset + "/" + readLen
143848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood							+ " (requested: " + req.len + ")");
143948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				}
144048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
144148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				// Read bytes into buffer
144248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				tr.readBytes(req.buffer, req.dstOffset, readLen);
144348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
144448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				if (readLen < req.len)
144548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				{
144648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					/* Send this request packet again to request the remaing data in this slot. */
144748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					req.req_id = generateNextRequestID();
144848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					req.serverOffset += readLen;
144948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					req.len -= readLen;
145048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
145148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					log.debug("Requesting again: " + req.serverOffset + "/" + req.len);
145248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					sendReadRequest(req.req_id, handle, req.serverOffset, req.len);
145348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
145448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood					pendingReadQueue.put(req.req_id, req);
145548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				}
145648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				return readLen;
145748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
145848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			else
145948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
146048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
146148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
146248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
146348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		// Should never reach here.
146448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		throw new SFTPException("No EOF reached", -1);
146548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
146648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
146748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
146848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * A read  is divided into multiple requests sent sequentially before
146948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * reading any status from the server
147048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
147148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private static class OutstandingStatusRequest
147248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
147348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int req_id;
147448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
147548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
147648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
147748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Mapping request ID to request.
147848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
147948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	Map<Integer, OutstandingStatusRequest> pendingStatusQueue
148048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			= new HashMap<Integer, OutstandingStatusRequest>();
148148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
148248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
148348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Write bytes to a file. If <code>len</code> &gt; 32768, then the write operation will
148448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * be split into multiple writes.
148548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
148648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param handle a SFTPv3FileHandle handle.
148748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param fileOffset offset (in bytes) in the file.
148848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param src the source byte array.
148948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param srcoff offset in the source byte array.
149048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param len how many bytes to write.
149148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
149248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
149348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public void write(SFTPv3FileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException
149448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
149548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		checkHandleValidAndOpen(handle);
149648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
149748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		// Send the next write request
149848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		OutstandingStatusRequest req = new OutstandingStatusRequest();
149948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		req.req_id = generateNextRequestID();
150048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
150148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesWriter tw = new TypesWriter();
150248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
150348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeUINT64(fileOffset);
150448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		tw.writeString(src, srcoff, len);
150548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
150648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		log.debug("Sending SSH_FXP_WRITE...");
150748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		sendMessage(Packet.SSH_FXP_WRITE, req.req_id, tw.getBytes());
150848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
150948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		pendingStatusQueue.put(req.req_id, req);
151048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
151148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		// Only read next status if parallelism reached
151248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		while (pendingStatusQueue.size() >= parallelism)
151348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
151448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			this.readStatus();
151548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
151648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
151748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
151848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private void readStatus() throws IOException
151948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
152048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		byte[] resp = receiveMessage(34000);
152148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
152248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesReader tr = new TypesReader(resp);
152348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int t = tr.readByte();
152448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(Packet.forName(t));
152548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
152648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		// Search the pending queue
152748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		OutstandingStatusRequest status = pendingStatusQueue.remove(tr.readUINT32());
152848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (null == status)
152948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
153048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The server sent an invalid id field.");
153148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
153248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
153348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		// Evaluate the answer
153448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t == Packet.SSH_FXP_STATUS)
153548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
153648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			// In any case, stop sending more packets
153748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			int code = tr.readUINT32();
153848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (log.isDebugEnabled())
153948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
154048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				String[] desc = ErrorCodes.getDescription(code);
154148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				log.debug("Got SSH_FXP_STATUS (" + status.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")");
154248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
154348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (code == ErrorCodes.SSH_FX_OK)
154448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
154548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				return;
154648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
154748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			String msg = tr.readString();
154848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			listener.read(msg);
154948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new SFTPException(msg, code);
155048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
155148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
155248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
155348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
155448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	private void readPendingReadStatus() throws IOException
155548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
155648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		byte[] resp = receiveMessage(34000);
155748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
155848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		TypesReader tr = new TypesReader(resp);
155948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		int t = tr.readByte();
156048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		listener.read(Packet.forName(t));
156148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
156248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		// Search the pending queue
156348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood        OutstandingReadRequest status = pendingReadQueue.remove(tr.readUINT32());
156448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood        if (null == status)
156548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
156648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new IOException("The server sent an invalid id field.");
156748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
156848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
156948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		// Evaluate the answer
157048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		if (t == Packet.SSH_FXP_STATUS)
157148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
157248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			// In any case, stop sending more packets
157348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			int code = tr.readUINT32();
157448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (log.isDebugEnabled())
157548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
157648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				String[] desc = ErrorCodes.getDescription(code);
157748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				log.debug("Got SSH_FXP_STATUS (" + status.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")");
157848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
157948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (code == ErrorCodes.SSH_FX_OK)
158048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
158148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				return;
158248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
158348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood            if (code == ErrorCodes.SSH_FX_EOF)
158448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood            {
158548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood                return;
158648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood            }
158748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			String msg = tr.readString();
158848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			listener.read(msg);
158948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			throw new SFTPException(msg, code);
159048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
159148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
159248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
159348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood
159448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	/**
159548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * Close a file.
159648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 *
159748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @param handle a SFTPv3FileHandle handle
159848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 * @throws IOException
159948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	 */
160048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	public void closeFile(SFTPv3FileHandle handle) throws IOException
160148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	{
160248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		try
160348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
160448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			while (!pendingReadQueue.isEmpty())
160548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
160648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				this.readPendingReadStatus();
160748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
160848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			while (!pendingStatusQueue.isEmpty())
160948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
161048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				this.readStatus();
161148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
161248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			if (!handle.isClosed)
161348ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			{
161448ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood				closeHandle(handle.fileHandle);
161548ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			}
161648ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
161748ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		finally
161848ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		{
161948ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood			handle.isClosed = true;
162048ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood		}
162148ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood	}
162248ded2421114c4c87ef3f8005c9f793a5d077cbdMike Lockwood}
1623