1/*
2 * Copyright (c) 2006-2011 Christian Plattner. All rights reserved.
3 * Please refer to the LICENSE.txt for licensing details.
4 */
5package ch.ethz.ssh2;
6
7import java.io.BufferedOutputStream;
8import java.io.IOException;
9import java.io.InputStream;
10import java.io.OutputStream;
11import java.nio.charset.Charset;
12import java.nio.charset.UnsupportedCharsetException;
13import java.util.HashMap;
14import java.util.List;
15import java.util.Map;
16import java.util.Vector;
17
18import ch.ethz.ssh2.channel.Channel;
19import ch.ethz.ssh2.log.Logger;
20import ch.ethz.ssh2.packets.TypesReader;
21import ch.ethz.ssh2.packets.TypesWriter;
22import ch.ethz.ssh2.sftp.AttribFlags;
23import ch.ethz.ssh2.sftp.ErrorCodes;
24import 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@inf.ethz.ch
65 * @version $Id: SFTPv3Client.java 46 2011-07-06 08:40:29Z dkocher@sudo.ch $
66 */
67public 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