Home | History | Annotate | Download | only in ssh2
      1 /*
      2  * Copyright (c) 2006-2011 Christian Plattner. All rights reserved.
      3  * Please refer to the LICENSE.txt for licensing details.
      4  */
      5 package ch.ethz.ssh2;
      6 
      7 import java.io.BufferedOutputStream;
      8 import java.io.IOException;
      9 import java.io.InputStream;
     10 import java.io.OutputStream;
     11 import java.nio.charset.Charset;
     12 import java.nio.charset.UnsupportedCharsetException;
     13 import java.util.HashMap;
     14 import java.util.List;
     15 import java.util.Map;
     16 import java.util.Vector;
     17 
     18 import ch.ethz.ssh2.channel.Channel;
     19 import ch.ethz.ssh2.log.Logger;
     20 import ch.ethz.ssh2.packets.TypesReader;
     21 import ch.ethz.ssh2.packets.TypesWriter;
     22 import ch.ethz.ssh2.sftp.AttribFlags;
     23 import ch.ethz.ssh2.sftp.ErrorCodes;
     24 import ch.ethz.ssh2.sftp.Packet;
     25 
     26 /**
     27  * A <code>SFTPv3Client</code> represents a SFTP (protocol version 3)
     28  * client connection tunnelled over a SSH-2 connection. This is a very simple
     29  * (synchronous) implementation.
     30  * <p/>
     31  * Basically, most methods in this class map directly to one of
     32  * the packet types described in draft-ietf-secsh-filexfer-02.txt.
     33  * <p/>
     34  * Note: this is experimental code.
     35  * <p/>
     36  * Error handling: the methods of this class throw IOExceptions. However, unless
     37  * there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will
     38  * be thrown (a subclass of IOException). Therefore, you can implement more verbose
     39  * behavior by checking if a thrown exception if of this type. If yes, then you
     40  * can cast the exception and access detailed information about the failure.
     41  * <p/>
     42  * Notes about file names, directory names and paths, copy-pasted
     43  * from the specs:
     44  * <ul>
     45  * <li>SFTP v3 represents file names as strings. File names are
     46  * assumed to use the slash ('/') character as a directory separator.</li>
     47  * <li>File names starting with a slash are "absolute", and are relative to
     48  * the root of the file system.  Names starting with any other character
     49  * are relative to the user's default directory (home directory).</li>
     50  * <li>Servers SHOULD interpret a path name component ".." as referring to
     51  * the parent directory, and "." as referring to the current directory.
     52  * If the server implementation limits access to certain parts of the
     53  * file system, it must be extra careful in parsing file names when
     54  * enforcing such restrictions.  There have been numerous reported
     55  * security bugs where a ".." in a path name has allowed access outside
     56  * the intended area.</li>
     57  * <li>An empty path name is valid, and it refers to the user's default
     58  * directory (usually the user's home directory).</li>
     59  * </ul>
     60  * <p/>
     61  * If you are still not tired then please go on and read the comment for
     62  * {@link #setCharset(String)}.
     63  *
     64  * @author Christian Plattner, plattner (at) inf.ethz.ch
     65  * @version $Id: SFTPv3Client.java 46 2011-07-06 08:40:29Z dkocher (at) sudo.ch $
     66  */
     67 public class SFTPv3Client
     68 {
     69 	private static final Logger log = Logger.getLogger(SFTPv3Client.class);
     70 
     71 	private Session sess;
     72 
     73 	private InputStream is;
     74 	private OutputStream os;
     75 
     76 	private int protocol_version = 0;
     77 
     78 	private int next_request_id = 1000;
     79 
     80 	private String charsetName = null;
     81 
     82 	/**
     83 	 *
     84 	 */
     85 	private PacketListener listener;
     86 
     87 	/**
     88 	 * Create a SFTP v3 client.
     89 	 *
     90 	 * @param conn The underlying SSH-2 connection to be used.
     91 	 * @throws IOException
     92 	 */
     93 	public SFTPv3Client(Connection conn, PacketListener listener) throws IOException
     94 	{
     95 		if (conn == null)
     96 		{
     97 			throw new IllegalArgumentException("Cannot accept null argument!");
     98 		}
     99 
    100 		this.listener = listener;
    101 
    102 		log.debug("Opening session and starting SFTP subsystem.");
    103 		sess = conn.openSession();
    104 		sess.startSubSystem("sftp");
    105 
    106 		is = sess.getStdout();
    107 		os = new BufferedOutputStream(sess.getStdin(), 2048);
    108 
    109 		if (is == null)
    110 		{
    111 			throw new IOException("There is a problem with the streams of the underlying channel.");
    112 		}
    113 
    114 		init();
    115 	}
    116 
    117 	/**
    118 	 * Create a SFTP v3 client.
    119 	 *
    120 	 * @param conn The underlying SSH-2 connection to be used.
    121 	 * @throws IOException
    122 	 */
    123 	public SFTPv3Client(Connection conn) throws IOException
    124 	{
    125 		this(conn, new PacketListener()
    126 		{
    127 			public void read(String packet)
    128 			{
    129 				log.debug("Read packet " + packet);
    130 			}
    131 
    132 			public void write(String packet)
    133 			{
    134 				log.debug("Write packet " + packet);
    135 			}
    136 		});
    137 	}
    138 
    139 	/**
    140 	 * Set the charset used to convert between Java Unicode Strings and byte encodings
    141 	 * used by the server for paths and file names. Unfortunately, the SFTP v3 draft
    142 	 * says NOTHING about such conversions (well, with the exception of error messages
    143 	 * which have to be in UTF-8). Newer drafts specify to use UTF-8 for file names
    144 	 * (if I remember correctly). However, a quick test using OpenSSH serving a EXT-3
    145 	 * filesystem has shown that UTF-8 seems to be a bad choice for SFTP v3 (tested with
    146 	 * filenames containing german umlauts). "windows-1252" seems to work better for Europe.
    147 	 * Luckily, "windows-1252" is the platform default in my case =).
    148 	 * <p/>
    149 	 * If you don't set anything, then the platform default will be used (this is the default
    150 	 * behavior).
    151 	 *
    152 	 * @param charset the name of the charset to be used or <code>null</code> to use the platform's
    153 	 * default encoding.
    154 	 * @throws IOException
    155 	 * @see #getCharset()
    156 	 */
    157 	public void setCharset(String charset) throws IOException
    158 	{
    159 		if (charset == null)
    160 		{
    161 			charsetName = charset;
    162 			return;
    163 		}
    164 
    165 		try
    166 		{
    167 			Charset.forName(charset);
    168 		}
    169 		catch (UnsupportedCharsetException e)
    170 		{
    171 			throw (IOException) new IOException("This charset is not supported").initCause(e);
    172 		}
    173 		charsetName = charset;
    174 	}
    175 
    176 	/**
    177 	 * The currently used charset for filename encoding/decoding.
    178 	 *
    179 	 * @return The name of the charset (<code>null</code> if the platform's default charset is being used)
    180 	 * @see #setCharset(String)
    181 	 */
    182 	public String getCharset()
    183 	{
    184 		return charsetName;
    185 	}
    186 
    187 	private void checkHandleValidAndOpen(SFTPv3FileHandle handle) throws IOException
    188 	{
    189 		if (handle.client != this)
    190 		{
    191 			throw new IOException("The file handle was created with another SFTPv3FileHandle instance.");
    192 		}
    193 
    194 		if (handle.isClosed)
    195 		{
    196 			throw new IOException("The file handle is closed.");
    197 		}
    198 	}
    199 
    200 	private void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException
    201 	{
    202 		listener.write(Packet.forName(type));
    203 
    204 		int msglen = len + 1;
    205 
    206 		if (type != Packet.SSH_FXP_INIT)
    207 		{
    208 			msglen += 4;
    209 		}
    210 
    211 		os.write(msglen >> 24);
    212 		os.write(msglen >> 16);
    213 		os.write(msglen >> 8);
    214 		os.write(msglen);
    215 		os.write(type);
    216 
    217 		if (type != Packet.SSH_FXP_INIT)
    218 		{
    219 			os.write(requestId >> 24);
    220 			os.write(requestId >> 16);
    221 			os.write(requestId >> 8);
    222 			os.write(requestId);
    223 		}
    224 
    225 		os.write(msg, off, len);
    226 		os.flush();
    227 	}
    228 
    229 	private void sendMessage(int type, int requestId, byte[] msg) throws IOException
    230 	{
    231 		sendMessage(type, requestId, msg, 0, msg.length);
    232 	}
    233 
    234 	private void readBytes(byte[] buff, int pos, int len) throws IOException
    235 	{
    236 		while (len > 0)
    237 		{
    238 			int count = is.read(buff, pos, len);
    239 			if (count < 0)
    240 			{
    241 				throw new IOException("Unexpected end of sftp stream.");
    242 			}
    243 			if ((count == 0) || (count > len))
    244 			{
    245 				throw new IOException("Underlying stream implementation is bogus!");
    246 			}
    247 			len -= count;
    248 			pos += count;
    249 		}
    250 	}
    251 
    252 	/**
    253 	 * Read a message and guarantee that the <b>contents</b> is not larger than
    254 	 * <code>maxlen</code> bytes.
    255 	 * <p/>
    256 	 * Note: receiveMessage(34000) actually means that the message may be up to 34004
    257 	 * bytes (the length attribute preceeding the contents is 4 bytes).
    258 	 *
    259 	 * @param maxlen
    260 	 * @return the message contents
    261 	 * @throws IOException
    262 	 */
    263 	private byte[] receiveMessage(int maxlen) throws IOException
    264 	{
    265 		byte[] msglen = new byte[4];
    266 
    267 		readBytes(msglen, 0, 4);
    268 
    269 		int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff));
    270 
    271 		if ((len > maxlen) || (len <= 0))
    272 		{
    273 			throw new IOException("Illegal sftp packet len: " + len);
    274 		}
    275 
    276 		byte[] msg = new byte[len];
    277 
    278 		readBytes(msg, 0, len);
    279 
    280 		return msg;
    281 	}
    282 
    283 	private int generateNextRequestID()
    284 	{
    285 		synchronized (this)
    286 		{
    287 			return next_request_id++;
    288 		}
    289 	}
    290 
    291 	private void closeHandle(byte[] handle) throws IOException
    292 	{
    293 		int req_id = generateNextRequestID();
    294 
    295 		TypesWriter tw = new TypesWriter();
    296 		tw.writeString(handle, 0, handle.length);
    297 
    298 		sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes());
    299 
    300 		expectStatusOKMessage(req_id);
    301 	}
    302 
    303 	private SFTPv3FileAttributes readAttrs(TypesReader tr) throws IOException
    304 	{
    305 		/*
    306 				   * uint32   flags
    307 				   * uint64   size           present only if flag SSH_FILEXFER_ATTR_SIZE
    308 				   * uint32   uid            present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
    309 				   * uint32   gid            present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
    310 				   * uint32   permissions    present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
    311 				   * uint32   atime          present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
    312 				   * uint32   mtime          present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
    313 				   * uint32   extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
    314 				   * string   extended_type
    315 				   * string   extended_data
    316 				   * ...      more extended data (extended_type - extended_data pairs),
    317 				   *          so that number of pairs equals extended_count
    318 				   */
    319 
    320 		SFTPv3FileAttributes fa = new SFTPv3FileAttributes();
    321 
    322 		int flags = tr.readUINT32();
    323 
    324 		if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0)
    325 		{
    326 			log.debug("SSH_FILEXFER_ATTR_SIZE");
    327 			fa.size = tr.readUINT64();
    328 		}
    329 
    330 		if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0)
    331 		{
    332 			log.debug("SSH_FILEXFER_ATTR_V3_UIDGID");
    333 			fa.uid = tr.readUINT32();
    334 			fa.gid = tr.readUINT32();
    335 		}
    336 
    337 		if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0)
    338 		{
    339 			log.debug("SSH_FILEXFER_ATTR_PERMISSIONS");
    340 			fa.permissions = tr.readUINT32();
    341 		}
    342 
    343 		if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0)
    344 		{
    345 			log.debug("SSH_FILEXFER_ATTR_V3_ACMODTIME");
    346 			fa.atime = tr.readUINT32();
    347 			fa.mtime = tr.readUINT32();
    348 
    349 		}
    350 
    351 		if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0)
    352 		{
    353 			int count = tr.readUINT32();
    354 
    355 			log.debug("SSH_FILEXFER_ATTR_EXTENDED (" + count + ")");
    356 			/* Read it anyway to detect corrupt packets */
    357 
    358 			while (count > 0)
    359 			{
    360 				tr.readByteString();
    361 				tr.readByteString();
    362 				count--;
    363 			}
    364 		}
    365 
    366 		return fa;
    367 	}
    368 
    369 	/**
    370 	 * Retrieve the file attributes of an open file.
    371 	 *
    372 	 * @param handle a SFTPv3FileHandle handle.
    373 	 * @return a SFTPv3FileAttributes object.
    374 	 * @throws IOException
    375 	 */
    376 	public SFTPv3FileAttributes fstat(SFTPv3FileHandle handle) throws IOException
    377 	{
    378 		checkHandleValidAndOpen(handle);
    379 
    380 		int req_id = generateNextRequestID();
    381 
    382 		TypesWriter tw = new TypesWriter();
    383 		tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
    384 
    385 		log.debug("Sending SSH_FXP_FSTAT...");
    386 		sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes());
    387 
    388 		byte[] resp = receiveMessage(34000);
    389 
    390 		TypesReader tr = new TypesReader(resp);
    391 
    392 		int t = tr.readByte();
    393 		listener.read(Packet.forName(t));
    394 
    395 		int rep_id = tr.readUINT32();
    396 		if (rep_id != req_id)
    397 		{
    398 			throw new IOException("The server sent an invalid id field.");
    399 		}
    400 
    401 		if (t == Packet.SSH_FXP_ATTRS)
    402 		{
    403 			return readAttrs(tr);
    404 		}
    405 
    406 		if (t != Packet.SSH_FXP_STATUS)
    407 		{
    408 			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
    409 		}
    410 
    411 		int errorCode = tr.readUINT32();
    412 		String errorMessage = tr.readString();
    413 		listener.read(errorMessage);
    414 		throw new SFTPException(errorMessage, errorCode);
    415 	}
    416 
    417 	private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException
    418 	{
    419 		int req_id = generateNextRequestID();
    420 
    421 		TypesWriter tw = new TypesWriter();
    422 		tw.writeString(path, charsetName);
    423 
    424 		log.debug("Sending SSH_FXP_STAT/SSH_FXP_LSTAT...");
    425 		sendMessage(statMethod, req_id, tw.getBytes());
    426 
    427 		byte[] resp = receiveMessage(34000);
    428 
    429 		TypesReader tr = new TypesReader(resp);
    430 
    431 		int t = tr.readByte();
    432 		listener.read(Packet.forName(t));
    433 
    434 		int rep_id = tr.readUINT32();
    435 		if (rep_id != req_id)
    436 		{
    437 			throw new IOException("The server sent an invalid id field.");
    438 		}
    439 
    440 		if (t == Packet.SSH_FXP_ATTRS)
    441 		{
    442 			return readAttrs(tr);
    443 		}
    444 
    445 		if (t != Packet.SSH_FXP_STATUS)
    446 		{
    447 			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
    448 		}
    449 
    450 		int errorCode = tr.readUINT32();
    451 		String errorMessage = tr.readString();
    452 		listener.read(errorMessage);
    453 		throw new SFTPException(errorMessage, errorCode);
    454 	}
    455 
    456 	/**
    457 	 * Retrieve the file attributes of a file. This method
    458 	 * follows symbolic links on the server.
    459 	 *
    460 	 * @param path See the {@link SFTPv3Client comment} for the class for more details.
    461 	 * @return a SFTPv3FileAttributes object.
    462 	 * @throws IOException
    463 	 * @see #lstat(String)
    464 	 */
    465 	public SFTPv3FileAttributes stat(String path) throws IOException
    466 	{
    467 		return statBoth(path, Packet.SSH_FXP_STAT);
    468 	}
    469 
    470 	/**
    471 	 * Retrieve the file attributes of a file. This method
    472 	 * does NOT follow symbolic links on the server.
    473 	 *
    474 	 * @param path See the {@link SFTPv3Client comment} for the class for more details.
    475 	 * @return a SFTPv3FileAttributes object.
    476 	 * @throws IOException
    477 	 * @see #stat(String)
    478 	 */
    479 	public SFTPv3FileAttributes lstat(String path) throws IOException
    480 	{
    481 		return statBoth(path, Packet.SSH_FXP_LSTAT);
    482 	}
    483 
    484 	/**
    485 	 * Read the target of a symbolic link. Note: OpenSSH (as of version 4.4) gets very upset
    486 	 * (SSH_FX_BAD_MESSAGE error) if you want to read the target of a file that is not a
    487 	 * symbolic link. Better check first with {@link #lstat(String)}.
    488 	 *
    489 	 * @param path See the {@link SFTPv3Client comment} for the class for more details.
    490 	 * @return The target of the link.
    491 	 * @throws IOException
    492 	 */
    493 	public String readLink(String path) throws IOException
    494 	{
    495 		int req_id = generateNextRequestID();
    496 
    497 		TypesWriter tw = new TypesWriter();
    498 		tw.writeString(path, charsetName);
    499 
    500 		log.debug("Sending SSH_FXP_READLINK...");
    501 		sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes());
    502 
    503 		byte[] resp = receiveMessage(34000);
    504 
    505 		TypesReader tr = new TypesReader(resp);
    506 
    507 		int t = tr.readByte();
    508 		listener.read(Packet.forName(t));
    509 
    510 		int rep_id = tr.readUINT32();
    511 		if (rep_id != req_id)
    512 		{
    513 			throw new IOException("The server sent an invalid id field.");
    514 		}
    515 
    516 		if (t == Packet.SSH_FXP_NAME)
    517 		{
    518 			int count = tr.readUINT32();
    519 
    520 			if (count != 1)
    521 			{
    522 				throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");
    523 			}
    524 
    525 			return tr.readString(charsetName);
    526 		}
    527 
    528 		if (t != Packet.SSH_FXP_STATUS)
    529 		{
    530 			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
    531 		}
    532 
    533 		int errorCode = tr.readUINT32();
    534 		String errorMessage = tr.readString();
    535 		listener.read(errorMessage);
    536 		throw new SFTPException(errorMessage, errorCode);
    537 	}
    538 
    539 	private void expectStatusOKMessage(int id) throws IOException
    540 	{
    541 		byte[] resp = receiveMessage(34000);
    542 
    543 		TypesReader tr = new TypesReader(resp);
    544 
    545 		int t = tr.readByte();
    546 		listener.read(Packet.forName(t));
    547 
    548 		int rep_id = tr.readUINT32();
    549 		if (rep_id != id)
    550 		{
    551 			throw new IOException("The server sent an invalid id field.");
    552 		}
    553 
    554 		if (t != Packet.SSH_FXP_STATUS)
    555 		{
    556 			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
    557 		}
    558 
    559 		int errorCode = tr.readUINT32();
    560 
    561 		if (errorCode == ErrorCodes.SSH_FX_OK)
    562 		{
    563 			return;
    564 		}
    565 		String errorMessage = tr.readString();
    566 		listener.read(errorMessage);
    567 		throw new SFTPException(errorMessage, errorCode);
    568 	}
    569 
    570 	/**
    571 	 * Modify the attributes of a file. Used for operations such as changing
    572 	 * the ownership, permissions or access times, as well as for truncating a file.
    573 	 *
    574 	 * @param path See the {@link SFTPv3Client comment} for the class for more details.
    575 	 * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
    576 	 * made to the attributes of the file. Empty fields will be ignored.
    577 	 * @throws IOException
    578 	 */
    579 	public void setstat(String path, SFTPv3FileAttributes attr) throws IOException
    580 	{
    581 		int req_id = generateNextRequestID();
    582 
    583 		TypesWriter tw = new TypesWriter();
    584 		tw.writeString(path, charsetName);
    585 		tw.writeBytes(createAttrs(attr));
    586 
    587 		log.debug("Sending SSH_FXP_SETSTAT...");
    588 		sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes());
    589 
    590 		expectStatusOKMessage(req_id);
    591 	}
    592 
    593 	/**
    594 	 * Modify the attributes of a file. Used for operations such as changing
    595 	 * the ownership, permissions or access times, as well as for truncating a file.
    596 	 *
    597 	 * @param handle a SFTPv3FileHandle handle
    598 	 * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
    599 	 * made to the attributes of the file. Empty fields will be ignored.
    600 	 * @throws IOException
    601 	 */
    602 	public void fsetstat(SFTPv3FileHandle handle, SFTPv3FileAttributes attr) throws IOException
    603 	{
    604 		checkHandleValidAndOpen(handle);
    605 
    606 		int req_id = generateNextRequestID();
    607 
    608 		TypesWriter tw = new TypesWriter();
    609 		tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
    610 		tw.writeBytes(createAttrs(attr));
    611 
    612 		log.debug("Sending SSH_FXP_FSETSTAT...");
    613 		sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes());
    614 
    615 		expectStatusOKMessage(req_id);
    616 	}
    617 
    618 	/**
    619 	 * Create a symbolic link on the server. Creates a link "src" that points
    620 	 * to "target".
    621 	 *
    622 	 * @param src See the {@link SFTPv3Client comment} for the class for more details.
    623 	 * @param target See the {@link SFTPv3Client comment} for the class for more details.
    624 	 * @throws IOException
    625 	 */
    626 	public void createSymlink(String src, String target) throws IOException
    627 	{
    628 		int req_id = generateNextRequestID();
    629 
    630 		/* Either I am too stupid to understand the SFTP draft
    631 				   * or the OpenSSH guys changed the semantics of src and target.
    632 				   */
    633 
    634 		TypesWriter tw = new TypesWriter();
    635 		tw.writeString(target, charsetName);
    636 		tw.writeString(src, charsetName);
    637 
    638 		log.debug("Sending SSH_FXP_SYMLINK...");
    639 		sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes());
    640 
    641 		expectStatusOKMessage(req_id);
    642 	}
    643 
    644 	/**
    645 	 * Have the server canonicalize any given path name to an absolute path.
    646 	 * This is useful for converting path names containing ".." components or
    647 	 * relative pathnames without a leading slash into absolute paths.
    648 	 *
    649 	 * @param path See the {@link SFTPv3Client comment} for the class for more details.
    650 	 * @return An absolute path.
    651 	 * @throws IOException
    652 	 */
    653 	public String canonicalPath(String path) throws IOException
    654 	{
    655 		int req_id = generateNextRequestID();
    656 
    657 		TypesWriter tw = new TypesWriter();
    658 		tw.writeString(path, charsetName);
    659 
    660 		log.debug("Sending SSH_FXP_REALPATH...");
    661 		sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes());
    662 
    663 		byte[] resp = receiveMessage(34000);
    664 
    665 		TypesReader tr = new TypesReader(resp);
    666 
    667 		int t = tr.readByte();
    668 		listener.read(Packet.forName(t));
    669 
    670 		int rep_id = tr.readUINT32();
    671 		if (rep_id != req_id)
    672 		{
    673 			throw new IOException("The server sent an invalid id field.");
    674 		}
    675 
    676 		if (t == Packet.SSH_FXP_NAME)
    677 		{
    678 			int count = tr.readUINT32();
    679 
    680 			if (count != 1)
    681 			{
    682 				throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");
    683 			}
    684 
    685 			final String name = tr.readString(charsetName);
    686 			listener.read(name);
    687 			return name;
    688 		}
    689 
    690 		if (t != Packet.SSH_FXP_STATUS)
    691 		{
    692 			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
    693 		}
    694 
    695 		int errorCode = tr.readUINT32();
    696 		String errorMessage = tr.readString();
    697 		listener.read(errorMessage);
    698 		throw new SFTPException(errorMessage, errorCode);
    699 	}
    700 
    701 	private List<SFTPv3DirectoryEntry> scanDirectory(byte[] handle) throws IOException
    702 	{
    703 		List<SFTPv3DirectoryEntry> files = new Vector<SFTPv3DirectoryEntry>();
    704 
    705 		while (true)
    706 		{
    707 			int req_id = generateNextRequestID();
    708 
    709 			TypesWriter tw = new TypesWriter();
    710 			tw.writeString(handle, 0, handle.length);
    711 
    712 			log.debug("Sending SSH_FXP_READDIR...");
    713 			sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes());
    714 
    715 			byte[] resp = receiveMessage(34000);
    716 
    717 			TypesReader tr = new TypesReader(resp);
    718 
    719 			int t = tr.readByte();
    720 			listener.read(Packet.forName(t));
    721 
    722 			int rep_id = tr.readUINT32();
    723 			if (rep_id != req_id)
    724 			{
    725 				throw new IOException("The server sent an invalid id field.");
    726 			}
    727 
    728 			if (t == Packet.SSH_FXP_NAME)
    729 			{
    730 				int count = tr.readUINT32();
    731 
    732 				log.debug("Parsing " + count + " name entries...");
    733 				while (count > 0)
    734 				{
    735 					SFTPv3DirectoryEntry dirEnt = new SFTPv3DirectoryEntry();
    736 
    737 					dirEnt.filename = tr.readString(charsetName);
    738 					dirEnt.longEntry = tr.readString(charsetName);
    739 					listener.read(dirEnt.longEntry);
    740 
    741 					dirEnt.attributes = readAttrs(tr);
    742 					files.add(dirEnt);
    743 
    744 					log.debug("File: '" + dirEnt.filename + "'");
    745 					count--;
    746 				}
    747 				continue;
    748 			}
    749 
    750 			if (t != Packet.SSH_FXP_STATUS)
    751 			{
    752 				throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
    753 			}
    754 
    755 			int errorCode = tr.readUINT32();
    756 
    757 			if (errorCode == ErrorCodes.SSH_FX_EOF)
    758 			{
    759 				return files;
    760 			}
    761 			String errorMessage = tr.readString();
    762 			listener.read(errorMessage);
    763 			throw new SFTPException(errorMessage, errorCode);
    764 		}
    765 	}
    766 
    767 	public final SFTPv3FileHandle openDirectory(String path) throws IOException
    768 	{
    769 		int req_id = generateNextRequestID();
    770 
    771 		TypesWriter tw = new TypesWriter();
    772 		tw.writeString(path, charsetName);
    773 
    774 		log.debug("Sending SSH_FXP_OPENDIR...");
    775 		sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes());
    776 
    777 		byte[] resp = receiveMessage(34000);
    778 
    779 		TypesReader tr = new TypesReader(resp);
    780 
    781 		int t = tr.readByte();
    782 		listener.read(Packet.forName(t));
    783 
    784 		int rep_id = tr.readUINT32();
    785 		if (rep_id != req_id)
    786 		{
    787 			throw new IOException("The server sent an invalid id field.");
    788 		}
    789 
    790 		if (t == Packet.SSH_FXP_HANDLE)
    791 		{
    792 			log.debug("Got SSH_FXP_HANDLE.");
    793 			return new SFTPv3FileHandle(this, tr.readByteString());
    794 		}
    795 
    796 		if (t != Packet.SSH_FXP_STATUS)
    797 		{
    798 			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
    799 		}
    800 
    801 		int errorCode = tr.readUINT32();
    802 		String errorMessage = tr.readString();
    803 		listener.read(errorMessage);
    804 		throw new SFTPException(errorMessage, errorCode);
    805 	}
    806 
    807 	private String expandString(byte[] b, int off, int len)
    808 	{
    809 		StringBuilder sb = new StringBuilder();
    810 
    811 		for (int i = 0; i < len; i++)
    812 		{
    813 			int c = b[off + i] & 0xff;
    814 
    815 			if ((c >= 32) && (c <= 126))
    816 			{
    817 				sb.append((char) c);
    818 			}
    819 			else
    820 			{
    821 				sb.append("{0x" + Integer.toHexString(c) + "}");
    822 			}
    823 		}
    824 
    825 		return sb.toString();
    826 	}
    827 
    828 	private void init() throws IOException
    829 	{
    830 		/* Send SSH_FXP_INIT (version 3) */
    831 
    832 		final int client_version = 3;
    833 
    834 		log.debug("Sending SSH_FXP_INIT (" + client_version + ")...");
    835 		TypesWriter tw = new TypesWriter();
    836 		tw.writeUINT32(client_version);
    837 		sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes());
    838 
    839 		/* Receive SSH_FXP_VERSION */
    840 
    841 		log.debug("Waiting for SSH_FXP_VERSION...");
    842 		TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */
    843 
    844 		int t = tr.readByte();
    845 		listener.read(Packet.forName(t));
    846 
    847 		if (t != Packet.SSH_FXP_VERSION)
    848 		{
    849 			throw new IOException("The server did not send a SSH_FXP_VERSION packet (got " + t + ")");
    850 		}
    851 
    852 		protocol_version = tr.readUINT32();
    853 
    854 		log.debug("SSH_FXP_VERSION: protocol_version = " + protocol_version);
    855 		if (protocol_version != 3)
    856 		{
    857 			throw new IOException("Server version " + protocol_version + " is currently not supported");
    858 		}
    859 
    860 		/* Read and save extensions (if any) for later use */
    861 
    862 		while (tr.remain() != 0)
    863 		{
    864 			String name = tr.readString();
    865 			listener.read(name);
    866 			byte[] value = tr.readByteString();
    867 			log.debug("SSH_FXP_VERSION: extension: " + name + " = '" + expandString(value, 0, value.length) + "'");
    868 		}
    869 	}
    870 
    871 	/**
    872 	 * Returns the negotiated SFTP protocol version between the client and the server.
    873 	 *
    874 	 * @return SFTP protocol version, i.e., "3".
    875 	 */
    876 	public int getProtocolVersion()
    877 	{
    878 		return protocol_version;
    879 	}
    880 
    881 	/**
    882 	 * Queries the channel state
    883 	 * @return True if the underlying session is in open state
    884 	 */
    885 	public boolean isConnected() {
    886 		return sess.getState() == Channel.STATE_OPEN;
    887 	}
    888 
    889 	/**
    890 	 * Close this SFTP session. NEVER forget to call this method to free up
    891 	 * resources - even if you got an exception from one of the other methods.
    892 	 * Sometimes these other methods may throw an exception, saying that the
    893 	 * underlying channel is closed (this can happen, e.g., if the other server
    894 	 * sent a close message.) However, as long as you have not called the
    895 	 * <code>close()</code> method, you are likely wasting resources.
    896 	 */
    897 	public void close()
    898 	{
    899 		sess.close();
    900 	}
    901 
    902 	/**
    903 	 * List the contents of a directory.
    904 	 *
    905 	 * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
    906 	 * @return A Vector containing {@link SFTPv3DirectoryEntry} objects.
    907 	 * @throws IOException
    908 	 */
    909 	public List<SFTPv3DirectoryEntry> ls(String dirName) throws IOException
    910 	{
    911         SFTPv3FileHandle handle = openDirectory(dirName);
    912         List<SFTPv3DirectoryEntry> result = scanDirectory(handle.fileHandle);
    913 		closeFile(handle);
    914 		return result;
    915 	}
    916 
    917 	/**
    918 	 * Create a new directory.
    919 	 *
    920 	 * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
    921 	 * @param posixPermissions the permissions for this directory, e.g., "0700" (remember that
    922 	 * this is octal noation). The server will likely apply a umask.
    923 	 * @throws IOException
    924 	 */
    925 	public void mkdir(String dirName, int posixPermissions) throws IOException
    926 	{
    927 		int req_id = generateNextRequestID();
    928 
    929 		TypesWriter tw = new TypesWriter();
    930 		tw.writeString(dirName, charsetName);
    931 		tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS);
    932 		tw.writeUINT32(posixPermissions);
    933 
    934 		sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes());
    935 
    936 		expectStatusOKMessage(req_id);
    937 	}
    938 
    939 	/**
    940 	 * Remove a file.
    941 	 *
    942 	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
    943 	 * @throws IOException
    944 	 */
    945 	public void rm(String fileName) throws IOException
    946 	{
    947 		int req_id = generateNextRequestID();
    948 
    949 		TypesWriter tw = new TypesWriter();
    950 		tw.writeString(fileName, charsetName);
    951 
    952 		sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes());
    953 
    954 		expectStatusOKMessage(req_id);
    955 	}
    956 
    957 	/**
    958 	 * Remove an empty directory.
    959 	 *
    960 	 * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
    961 	 * @throws IOException
    962 	 */
    963 	public void rmdir(String dirName) throws IOException
    964 	{
    965 		int req_id = generateNextRequestID();
    966 
    967 		TypesWriter tw = new TypesWriter();
    968 		tw.writeString(dirName, charsetName);
    969 
    970 		sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes());
    971 
    972 		expectStatusOKMessage(req_id);
    973 	}
    974 
    975 	/**
    976 	 * Move a file or directory.
    977 	 *
    978 	 * @param oldPath See the {@link SFTPv3Client comment} for the class for more details.
    979 	 * @param newPath See the {@link SFTPv3Client comment} for the class for more details.
    980 	 * @throws IOException
    981 	 */
    982 	public void mv(String oldPath, String newPath) throws IOException
    983 	{
    984 		int req_id = generateNextRequestID();
    985 
    986 		TypesWriter tw = new TypesWriter();
    987 		tw.writeString(oldPath, charsetName);
    988 		tw.writeString(newPath, charsetName);
    989 
    990 		sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes());
    991 
    992 		expectStatusOKMessage(req_id);
    993 	}
    994 
    995 	/**
    996 	 * Open the file for reading.
    997 	 */
    998 	public static final int SSH_FXF_READ = 0x00000001;
    999 	/**
   1000 	 * Open the file for writing.  If both this and SSH_FXF_READ are
   1001 	 * specified, the file is opened for both reading and writing.
   1002 	 */
   1003 	public static final int SSH_FXF_WRITE = 0x00000002;
   1004 	/**
   1005 	 * Force all writes to append data at the end of the file.
   1006 	 */
   1007 	public static final int SSH_FXF_APPEND = 0x00000004;
   1008 	/**
   1009 	 * If this flag is specified, then a new file will be created if one
   1010 	 * does not alread exist (if O_TRUNC is specified, the new file will
   1011 	 * be truncated to zero length if it previously exists).
   1012 	 */
   1013 	public static final int SSH_FXF_CREAT = 0x00000008;
   1014 	/**
   1015 	 * Forces an existing file with the same name to be truncated to zero
   1016 	 * length when creating a file by specifying SSH_FXF_CREAT.
   1017 	 * SSH_FXF_CREAT MUST also be specified if this flag is used.
   1018 	 */
   1019 	public static final int SSH_FXF_TRUNC = 0x00000010;
   1020 	/**
   1021 	 * Causes the request to fail if the named file already exists.
   1022 	 */
   1023 	public static final int SSH_FXF_EXCL = 0x00000020;
   1024 
   1025 	/**
   1026 	 * Open a file for reading.
   1027 	 *
   1028 	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   1029 	 * @return a SFTPv3FileHandle handle
   1030 	 * @throws IOException
   1031 	 */
   1032 	public SFTPv3FileHandle openFileRO(String fileName) throws IOException
   1033 	{
   1034 		return openFile(fileName, SSH_FXF_READ, null);
   1035 	}
   1036 
   1037 	/**
   1038 	 * Open a file for reading and writing.
   1039 	 *
   1040 	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   1041 	 * @return a SFTPv3FileHandle handle
   1042 	 * @throws IOException
   1043 	 */
   1044 	public SFTPv3FileHandle openFileRW(String fileName) throws IOException
   1045 	{
   1046 		return openFile(fileName, SSH_FXF_READ | SSH_FXF_WRITE, null);
   1047 	}
   1048 
   1049 	/**
   1050 	 * Open a file in append mode. The SFTP v3 draft says nothing but assuming normal POSIX
   1051 	 * behavior, all writes will be appendend to the end of the file, no matter which offset
   1052 	 * one specifies.
   1053 	 * <p/>
   1054 	 * A side note for the curious: OpenSSH does an lseek() to the specified writing offset before each write(),
   1055 	 * even for writes to files opened in O_APPEND mode. However, bear in mind that when working
   1056 	 * in the O_APPEND mode, each write() includes an implicit lseek() to the end of the file
   1057 	 * (well, this is what the newsgroups say).
   1058 	 *
   1059 	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   1060 	 * @return a SFTPv3FileHandle handle
   1061 	 * @throws IOException
   1062 	 */
   1063 	public SFTPv3FileHandle openFileRWAppend(String fileName) throws IOException
   1064 	{
   1065 		return openFile(fileName, SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND, null);
   1066 	}
   1067 
   1068 	/**
   1069 	 * Open a file in append mode. The SFTP v3 draft says nothing but assuming normal POSIX
   1070 	 * behavior, all writes will be appendend to the end of the file, no matter which offset
   1071 	 * one specifies.
   1072 	 * <p/>
   1073 	 * A side note for the curious: OpenSSH does an lseek() to the specified writing offset before each write(),
   1074 	 * even for writes to files opened in O_APPEND mode. However, bear in mind that when working
   1075 	 * in the O_APPEND mode, each write() includes an implicit lseek() to the end of the file
   1076 	 * (well, this is what the newsgroups say).
   1077 	 *
   1078 	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   1079 	 * @return a SFTPv3FileHandle handle
   1080 	 * @throws IOException
   1081 	 */
   1082 	public SFTPv3FileHandle openFileWAppend(String fileName) throws IOException
   1083 	{
   1084 		return openFile(fileName, SSH_FXF_WRITE | SSH_FXF_APPEND, null);
   1085 	}
   1086 
   1087 	/**
   1088 	 * Create a file and open it for reading and writing.
   1089 	 * Same as {@link #createFile(String, SFTPv3FileAttributes) createFile(fileName, null)}.
   1090 	 *
   1091 	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   1092 	 * @return a SFTPv3FileHandle handle
   1093 	 * @throws IOException
   1094 	 */
   1095 	public SFTPv3FileHandle createFile(String fileName) throws IOException
   1096 	{
   1097 		return createFile(fileName, null);
   1098 	}
   1099 
   1100 	/**
   1101 	 * Create a file and open it for reading and writing.
   1102 	 * You can specify the default attributes of the file (the server may or may
   1103 	 * not respect your wishes).
   1104 	 *
   1105 	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   1106 	 * @param attr may be <code>null</code> to use server defaults. Probably only
   1107 	 * the <code>uid</code>, <code>gid</code> and <code>permissions</code>
   1108 	 * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
   1109 	 * structure make sense. You need only to set those fields where you want
   1110 	 * to override the server's defaults.
   1111 	 * @return a SFTPv3FileHandle handle
   1112 	 * @throws IOException
   1113 	 */
   1114 	public SFTPv3FileHandle createFile(String fileName, SFTPv3FileAttributes attr) throws IOException
   1115 	{
   1116 		return openFile(fileName, SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE, attr);
   1117 	}
   1118 
   1119 	/**
   1120 	 * Create a file (truncate it if it already exists) and open it for writing.
   1121 	 * Same as {@link #createFileTruncate(String, SFTPv3FileAttributes) createFileTruncate(fileName, null)}.
   1122 	 *
   1123 	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   1124 	 * @return a SFTPv3FileHandle handle
   1125 	 * @throws IOException
   1126 	 */
   1127 	public SFTPv3FileHandle createFileTruncate(String fileName) throws IOException
   1128 	{
   1129 		return createFileTruncate(fileName, null);
   1130 	}
   1131 
   1132 	/**
   1133 	 * reate a file (truncate it if it already exists) and open it for writing.
   1134 	 * You can specify the default attributes of the file (the server may or may
   1135 	 * not respect your wishes).
   1136 	 *
   1137 	 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
   1138 	 * @param attr may be <code>null</code> to use server defaults. Probably only
   1139 	 * the <code>uid</code>, <code>gid</code> and <code>permissions</code>
   1140 	 * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
   1141 	 * structure make sense. You need only to set those fields where you want
   1142 	 * to override the server's defaults.
   1143 	 * @return a SFTPv3FileHandle handle
   1144 	 * @throws IOException
   1145 	 */
   1146 	public SFTPv3FileHandle createFileTruncate(String fileName, SFTPv3FileAttributes attr) throws IOException
   1147 	{
   1148 		return openFile(fileName, SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_WRITE, attr);
   1149 	}
   1150 
   1151 	private byte[] createAttrs(SFTPv3FileAttributes attr)
   1152 	{
   1153 		TypesWriter tw = new TypesWriter();
   1154 
   1155 		int attrFlags = 0;
   1156 
   1157 		if (attr == null)
   1158 		{
   1159 			tw.writeUINT32(0);
   1160 		}
   1161 		else
   1162 		{
   1163 			if (attr.size != null)
   1164 			{
   1165 				attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE;
   1166 			}
   1167 
   1168 			if ((attr.uid != null) && (attr.gid != null))
   1169 			{
   1170 				attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID;
   1171 			}
   1172 
   1173 			if (attr.permissions != null)
   1174 			{
   1175 				attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS;
   1176 			}
   1177 
   1178 			if ((attr.atime != null) && (attr.mtime != null))
   1179 			{
   1180 				attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME;
   1181 			}
   1182 
   1183 			tw.writeUINT32(attrFlags);
   1184 
   1185 			if (attr.size != null)
   1186 			{
   1187 				tw.writeUINT64(attr.size);
   1188 			}
   1189 
   1190 			if ((attr.uid != null) && (attr.gid != null))
   1191 			{
   1192 				tw.writeUINT32(attr.uid);
   1193 				tw.writeUINT32(attr.gid);
   1194 			}
   1195 
   1196 			if (attr.permissions != null)
   1197 			{
   1198 				tw.writeUINT32(attr.permissions);
   1199 			}
   1200 
   1201 			if ((attr.atime != null) && (attr.mtime != null))
   1202 			{
   1203 				tw.writeUINT32(attr.atime);
   1204 				tw.writeUINT32(attr.mtime);
   1205 			}
   1206 		}
   1207 
   1208 		return tw.getBytes();
   1209 	}
   1210 
   1211 	public SFTPv3FileHandle openFile(String fileName, int flags, SFTPv3FileAttributes attr) throws IOException
   1212 	{
   1213 		int req_id = generateNextRequestID();
   1214 
   1215 		TypesWriter tw = new TypesWriter();
   1216 		tw.writeString(fileName, charsetName);
   1217 		tw.writeUINT32(flags);
   1218 		tw.writeBytes(createAttrs(attr));
   1219 
   1220 		log.debug("Sending SSH_FXP_OPEN...");
   1221 		sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes());
   1222 
   1223 		byte[] resp = receiveMessage(34000);
   1224 
   1225 		TypesReader tr = new TypesReader(resp);
   1226 
   1227 		int t = tr.readByte();
   1228 		listener.read(Packet.forName(t));
   1229 
   1230 		int rep_id = tr.readUINT32();
   1231 		if (rep_id != req_id)
   1232 		{
   1233 			throw new IOException("The server sent an invalid id field.");
   1234 		}
   1235 
   1236 		if (t == Packet.SSH_FXP_HANDLE)
   1237 		{
   1238 			log.debug("Got SSH_FXP_HANDLE.");
   1239 			return new SFTPv3FileHandle(this, tr.readByteString());
   1240 		}
   1241 
   1242 		if (t != Packet.SSH_FXP_STATUS)
   1243 		{
   1244 			throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
   1245 		}
   1246 
   1247 		int errorCode = tr.readUINT32();
   1248 		String errorMessage = tr.readString();
   1249 		listener.read(errorMessage);
   1250 		throw new SFTPException(errorMessage, errorCode);
   1251 	}
   1252 
   1253 	/**
   1254 	 * A read  is divided into multiple requests sent sequentially before
   1255 	 * reading any status from the server
   1256 	 */
   1257 	private static class OutstandingReadRequest
   1258 	{
   1259 		int req_id;
   1260 		/**
   1261 		 * Read offset to request on server starting at the file offset for the first request.
   1262 		 */
   1263 		long serverOffset;
   1264 		/**
   1265 		 * Length of requested data
   1266 		 */
   1267 		int len;
   1268 		/**
   1269 		 * Offset in destination buffer
   1270 		 */
   1271 		int dstOffset;
   1272 		/**
   1273 		 * Temporary buffer
   1274 		 */
   1275 		byte[] buffer;
   1276 	}
   1277 
   1278 	private void sendReadRequest(int id, SFTPv3FileHandle handle, long offset, int len) throws IOException
   1279 	{
   1280 		TypesWriter tw = new TypesWriter();
   1281 		tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
   1282 		tw.writeUINT64(offset);
   1283 		tw.writeUINT32(len);
   1284 
   1285 		log.debug("Sending SSH_FXP_READ (" + id + ") " + offset + "/" + len);
   1286 		sendMessage(Packet.SSH_FXP_READ, id, tw.getBytes());
   1287 	}
   1288 
   1289 	/**
   1290 	 * Parallel read requests maximum size.
   1291 	 */
   1292 	private static final int DEFAULT_MAX_PARALLELISM = 64;
   1293 
   1294 	/**
   1295 	 * Parallel read requests.
   1296 	 */
   1297 	private int parallelism = DEFAULT_MAX_PARALLELISM;
   1298 
   1299 	/**
   1300 	 * @param parallelism
   1301 	 */
   1302 	public void setRequestParallelism(int parallelism)
   1303 	{
   1304 		this.parallelism = Math.min(parallelism, DEFAULT_MAX_PARALLELISM);
   1305 	}
   1306 
   1307 	/**
   1308 	 * Mapping request ID to request.
   1309 	 */
   1310 	Map<Integer, OutstandingReadRequest> pendingReadQueue
   1311 			= new HashMap<Integer, OutstandingReadRequest>();
   1312 
   1313 	/**
   1314 	 * Read bytes from a file in a parallel fashion. As many bytes as you want will be read.
   1315 	 * <p/>
   1316 	 * <ul>
   1317 	 * <li>The server will read as many bytes as it can from the file (up to <code>len</code>),
   1318 	 * and return them.</li>
   1319 	 * <li>If EOF is encountered before reading any data, <code>-1</code> is returned.
   1320 	 * <li>If an error occurs, an exception is thrown</li>.
   1321 	 * <li>For normal disk files, it is guaranteed that the server will return the specified
   1322 	 * number of bytes, or up to end of file. For, e.g., device files this may return
   1323 	 * fewer bytes than requested.</li>
   1324 	 * </ul>
   1325 	 *
   1326 	 * @param handle a SFTPv3FileHandle handle
   1327 	 * @param fileOffset offset (in bytes) in the file
   1328 	 * @param dst the destination byte array
   1329 	 * @param dstoff offset in the destination byte array
   1330 	 * @param len how many bytes to read, 0 &lt; len
   1331 	 * @return the number of bytes that could be read, may be less than requested if
   1332 	 *         the end of the file is reached, -1 is returned in case of <code>EOF</code>
   1333 	 * @throws IOException
   1334 	 */
   1335 	public int read(SFTPv3FileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException
   1336 	{
   1337 		boolean errorOccured = false;
   1338 
   1339 		checkHandleValidAndOpen(handle);
   1340 
   1341 		int remaining = len * parallelism;
   1342 		int clientOffset = dstoff;
   1343 
   1344 		long serverOffset = fileOffset;
   1345 		for (OutstandingReadRequest r : pendingReadQueue.values())
   1346 		{
   1347 			// Server offset should take pending requests into account.
   1348 			serverOffset += r.len;
   1349 		}
   1350 
   1351 		while (true)
   1352 		{
   1353 			// Stop if there was an error and no outstanding request
   1354 			if ((pendingReadQueue.size() == 0) && errorOccured)
   1355 			{
   1356 				break;
   1357 			}
   1358 
   1359 			// Send as many requests as we are allowed to
   1360 			while (pendingReadQueue.size() < parallelism)
   1361 			{
   1362 				if (errorOccured)
   1363 				{
   1364 					break;
   1365 				}
   1366 				// Send the next read request
   1367 				OutstandingReadRequest req = new OutstandingReadRequest();
   1368 				req.req_id = generateNextRequestID();
   1369 				req.serverOffset = serverOffset;
   1370 				req.len = (remaining > len) ? len : remaining;
   1371 				req.buffer = dst;
   1372 				req.dstOffset = dstoff;
   1373 
   1374 				serverOffset += req.len;
   1375 				clientOffset += req.len;
   1376 				remaining -= req.len;
   1377 
   1378 				sendReadRequest(req.req_id, handle, req.serverOffset, req.len);
   1379 
   1380 				pendingReadQueue.put(req.req_id, req);
   1381 			}
   1382 			if (pendingReadQueue.size() == 0)
   1383 			{
   1384 				break;
   1385 			}
   1386 
   1387 			// Receive a single answer
   1388 			byte[] resp = receiveMessage(34000);
   1389 			TypesReader tr = new TypesReader(resp);
   1390 
   1391 			int t = tr.readByte();
   1392 			listener.read(Packet.forName(t));
   1393 
   1394 			// Search the pending queue
   1395 			OutstandingReadRequest req = pendingReadQueue.remove(tr.readUINT32());
   1396 			if (null == req)
   1397 			{
   1398 				throw new IOException("The server sent an invalid id field.");
   1399 			}
   1400 			// Evaluate the answer
   1401 			if (t == Packet.SSH_FXP_STATUS)
   1402 			{
   1403 				/* In any case, stop sending more packets */
   1404 
   1405 				int code = tr.readUINT32();
   1406 				String msg = tr.readString();
   1407 				listener.read(msg);
   1408 
   1409 				if (log.isDebugEnabled())
   1410 				{
   1411 					String[] desc = ErrorCodes.getDescription(code);
   1412 					log.debug("Got SSH_FXP_STATUS (" + req.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")");
   1413 				}
   1414 				// Flag to read all pending requests but don't send any more.
   1415 				errorOccured = true;
   1416 				if (pendingReadQueue.isEmpty())
   1417 				{
   1418 					if (ErrorCodes.SSH_FX_EOF == code)
   1419 					{
   1420 						return -1;
   1421 					}
   1422 					throw new SFTPException(msg, code);
   1423 				}
   1424 			}
   1425 			else if (t == Packet.SSH_FXP_DATA)
   1426 			{
   1427 				// OK, collect data
   1428 				int readLen = tr.readUINT32();
   1429 
   1430 				if ((readLen < 0) || (readLen > req.len))
   1431 				{
   1432 					throw new IOException("The server sent an invalid length field in a SSH_FXP_DATA packet.");
   1433 				}
   1434 
   1435 				if (log.isDebugEnabled())
   1436 				{
   1437 					log.debug("Got SSH_FXP_DATA (" + req.req_id + ") " + req.serverOffset + "/" + readLen
   1438 							+ " (requested: " + req.len + ")");
   1439 				}
   1440 
   1441 				// Read bytes into buffer
   1442 				tr.readBytes(req.buffer, req.dstOffset, readLen);
   1443 
   1444 				if (readLen < req.len)
   1445 				{
   1446 					/* Send this request packet again to request the remaing data in this slot. */
   1447 					req.req_id = generateNextRequestID();
   1448 					req.serverOffset += readLen;
   1449 					req.len -= readLen;
   1450 
   1451 					log.debug("Requesting again: " + req.serverOffset + "/" + req.len);
   1452 					sendReadRequest(req.req_id, handle, req.serverOffset, req.len);
   1453 
   1454 					pendingReadQueue.put(req.req_id, req);
   1455 				}
   1456 				return readLen;
   1457 			}
   1458 			else
   1459 			{
   1460 				throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
   1461 			}
   1462 		}
   1463 		// Should never reach here.
   1464 		throw new SFTPException("No EOF reached", -1);
   1465 	}
   1466 
   1467 	/**
   1468 	 * A read  is divided into multiple requests sent sequentially before
   1469 	 * reading any status from the server
   1470 	 */
   1471 	private static class OutstandingStatusRequest
   1472 	{
   1473 		int req_id;
   1474 	}
   1475 
   1476 	/**
   1477 	 * Mapping request ID to request.
   1478 	 */
   1479 	Map<Integer, OutstandingStatusRequest> pendingStatusQueue
   1480 			= new HashMap<Integer, OutstandingStatusRequest>();
   1481 
   1482 	/**
   1483 	 * Write bytes to a file. If <code>len</code> &gt; 32768, then the write operation will
   1484 	 * be split into multiple writes.
   1485 	 *
   1486 	 * @param handle a SFTPv3FileHandle handle.
   1487 	 * @param fileOffset offset (in bytes) in the file.
   1488 	 * @param src the source byte array.
   1489 	 * @param srcoff offset in the source byte array.
   1490 	 * @param len how many bytes to write.
   1491 	 * @throws IOException
   1492 	 */
   1493 	public void write(SFTPv3FileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException
   1494 	{
   1495 		checkHandleValidAndOpen(handle);
   1496 
   1497 		// Send the next write request
   1498 		OutstandingStatusRequest req = new OutstandingStatusRequest();
   1499 		req.req_id = generateNextRequestID();
   1500 
   1501 		TypesWriter tw = new TypesWriter();
   1502 		tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
   1503 		tw.writeUINT64(fileOffset);
   1504 		tw.writeString(src, srcoff, len);
   1505 
   1506 		log.debug("Sending SSH_FXP_WRITE...");
   1507 		sendMessage(Packet.SSH_FXP_WRITE, req.req_id, tw.getBytes());
   1508 
   1509 		pendingStatusQueue.put(req.req_id, req);
   1510 
   1511 		// Only read next status if parallelism reached
   1512 		while (pendingStatusQueue.size() >= parallelism)
   1513 		{
   1514 			this.readStatus();
   1515 		}
   1516 	}
   1517 
   1518 	private void readStatus() throws IOException
   1519 	{
   1520 		byte[] resp = receiveMessage(34000);
   1521 
   1522 		TypesReader tr = new TypesReader(resp);
   1523 		int t = tr.readByte();
   1524 		listener.read(Packet.forName(t));
   1525 
   1526 		// Search the pending queue
   1527 		OutstandingStatusRequest status = pendingStatusQueue.remove(tr.readUINT32());
   1528 		if (null == status)
   1529 		{
   1530 			throw new IOException("The server sent an invalid id field.");
   1531 		}
   1532 
   1533 		// Evaluate the answer
   1534 		if (t == Packet.SSH_FXP_STATUS)
   1535 		{
   1536 			// In any case, stop sending more packets
   1537 			int code = tr.readUINT32();
   1538 			if (log.isDebugEnabled())
   1539 			{
   1540 				String[] desc = ErrorCodes.getDescription(code);
   1541 				log.debug("Got SSH_FXP_STATUS (" + status.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")");
   1542 			}
   1543 			if (code == ErrorCodes.SSH_FX_OK)
   1544 			{
   1545 				return;
   1546 			}
   1547 			String msg = tr.readString();
   1548 			listener.read(msg);
   1549 			throw new SFTPException(msg, code);
   1550 		}
   1551 		throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
   1552 	}
   1553 
   1554 	private void readPendingReadStatus() throws IOException
   1555 	{
   1556 		byte[] resp = receiveMessage(34000);
   1557 
   1558 		TypesReader tr = new TypesReader(resp);
   1559 		int t = tr.readByte();
   1560 		listener.read(Packet.forName(t));
   1561 
   1562 		// Search the pending queue
   1563         OutstandingReadRequest status = pendingReadQueue.remove(tr.readUINT32());
   1564         if (null == status)
   1565 		{
   1566 			throw new IOException("The server sent an invalid id field.");
   1567 		}
   1568 
   1569 		// Evaluate the answer
   1570 		if (t == Packet.SSH_FXP_STATUS)
   1571 		{
   1572 			// In any case, stop sending more packets
   1573 			int code = tr.readUINT32();
   1574 			if (log.isDebugEnabled())
   1575 			{
   1576 				String[] desc = ErrorCodes.getDescription(code);
   1577 				log.debug("Got SSH_FXP_STATUS (" + status.req_id + ") (" + ((desc != null) ? desc[0] : "UNKNOWN") + ")");
   1578 			}
   1579 			if (code == ErrorCodes.SSH_FX_OK)
   1580 			{
   1581 				return;
   1582 			}
   1583             if (code == ErrorCodes.SSH_FX_EOF)
   1584             {
   1585                 return;
   1586             }
   1587 			String msg = tr.readString();
   1588 			listener.read(msg);
   1589 			throw new SFTPException(msg, code);
   1590 		}
   1591 		throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
   1592 	}
   1593 
   1594 	/**
   1595 	 * Close a file.
   1596 	 *
   1597 	 * @param handle a SFTPv3FileHandle handle
   1598 	 * @throws IOException
   1599 	 */
   1600 	public void closeFile(SFTPv3FileHandle handle) throws IOException
   1601 	{
   1602 		try
   1603 		{
   1604 			while (!pendingReadQueue.isEmpty())
   1605 			{
   1606 				this.readPendingReadStatus();
   1607 			}
   1608 			while (!pendingStatusQueue.isEmpty())
   1609 			{
   1610 				this.readStatus();
   1611 			}
   1612 			if (!handle.isClosed)
   1613 			{
   1614 				closeHandle(handle.fileHandle);
   1615 			}
   1616 		}
   1617 		finally
   1618 		{
   1619 			handle.isClosed = true;
   1620 		}
   1621 	}
   1622 }
   1623