KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > ch > ethz > ssh2 > SFTPv3Client


1
2 package ch.ethz.ssh2;
3
4 import java.io.BufferedOutputStream JavaDoc;
5 import java.io.IOException JavaDoc;
6 import java.io.InputStream JavaDoc;
7 import java.io.OutputStream JavaDoc;
8 import java.io.PrintStream JavaDoc;
9 import java.nio.charset.Charset JavaDoc;
10 import java.util.HashMap JavaDoc;
11 import java.util.Vector JavaDoc;
12
13 import ch.ethz.ssh2.packets.TypesReader;
14 import ch.ethz.ssh2.packets.TypesWriter;
15 import ch.ethz.ssh2.sftp.AttribFlags;
16 import ch.ethz.ssh2.sftp.ErrorCodes;
17 import ch.ethz.ssh2.sftp.Packet;
18
19 /**
20  * A <code>SFTPv3Client</code> represents a SFTP (protocol version 3)
21  * client connection tunnelled over a SSH-2 connection. This is a very simple
22  * (synchronous) implementation.
23  * <p>
24  * Basically, most methods in this class map directly to one of
25  * the packet types described in draft-ietf-secsh-filexfer-02.txt.
26  * <p>
27  * Note: this is experimental code.
28  * <p>
29  * Error handling: the methods of this class throw IOExceptions. However, unless
30  * there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will
31  * be thrown (a subclass of IOException). Therefore, you can implement more verbose
32  * behavior by checking if a thrown exception if of this type. If yes, then you
33  * can cast the exception and access detailed information about the failure.
34  * <p>
35  * Notes about file names, directory names and paths, copy-pasted
36  * from the specs:
37  * <ul>
38  * <li>SFTP v3 represents file names as strings. File names are
39  * assumed to use the slash ('/') character as a directory separator.</li>
40  * <li>File names starting with a slash are "absolute", and are relative to
41  * the root of the file system. Names starting with any other character
42  * are relative to the user's default directory (home directory).</li>
43  * <li>Servers SHOULD interpret a path name component ".." as referring to
44  * the parent directory, and "." as referring to the current directory.
45  * If the server implementation limits access to certain parts of the
46  * file system, it must be extra careful in parsing file names when
47  * enforcing such restrictions. There have been numerous reported
48  * security bugs where a ".." in a path name has allowed access outside
49  * the intended area.</li>
50  * <li>An empty path name is valid, and it refers to the user's default
51  * directory (usually the user's home directory).</li>
52  * </ul>
53  * <p>
54  * If you are still not tired then please go on and read the comment for
55  * {@link #setCharset(String)}.
56  *
57  * @author Christian Plattner, plattner@inf.ethz.ch
58  * @version $Id: SFTPv3Client.java,v 1.10 2006/10/20 07:28:45 cplattne Exp $
59  */

60 public class SFTPv3Client
61 {
62     final Connection conn;
63     final Session sess;
64     final PrintStream JavaDoc debug;
65
66     boolean flag_closed = false;
67
68     InputStream JavaDoc is;
69     OutputStream JavaDoc os;
70
71     int protocol_version = 0;
72     HashMap JavaDoc server_extensions = new HashMap JavaDoc();
73
74     int next_request_id = 1000;
75
76     String JavaDoc charsetName = null;
77
78     /**
79      * Create a SFTP v3 client.
80      *
81      * @param conn The underlying SSH-2 connection to be used.
82      * @param debug
83      * @throws IOException
84      *
85      * @deprecated this constructor (debug version) will disappear in the future,
86      * use {@link #SFTPv3Client(Connection)} instead.
87      */

88     public SFTPv3Client(Connection conn, PrintStream JavaDoc debug) throws IOException JavaDoc
89     {
90         if (conn == null)
91             throw new IllegalArgumentException JavaDoc("Cannot accept null argument!");
92
93         this.conn = conn;
94         this.debug = debug;
95
96         if (debug != null)
97             debug.println("Opening session and starting SFTP subsystem.");
98
99         sess = conn.openSession();
100         sess.startSubSystem("sftp");
101
102         is = sess.getStdout();
103         os = new BufferedOutputStream JavaDoc(sess.getStdin(), 2048);
104
105         if ((is == null) || (os == null))
106             throw new IOException JavaDoc("There is a problem with the streams of the underlying channel.");
107
108         init();
109     }
110
111     /**
112      * Create a SFTP v3 client.
113      *
114      * @param conn The underlying SSH-2 connection to be used.
115      * @throws IOException
116      */

117     public SFTPv3Client(Connection conn) throws IOException JavaDoc
118     {
119         this(conn, null);
120     }
121
122     /**
123      * Set the charset used to convert between Java Unicode Strings and byte encodings
124      * used by the server for paths and file names. Unfortunately, the SFTP v3 draft
125      * says NOTHING about such conversions (well, with the exception of error messages
126      * which have to be in UTF-8). Newer drafts specify to use UTF-8 for file names
127      * (if I remember correctly). However, a quick test using OpenSSH serving a EXT-3
128      * filesystem has shown that UTF-8 seems to be a bad choice for SFTP v3 (tested with
129      * filenames containing german umlauts). "windows-1252" seems to work better for Europe.
130      * Luckily, "windows-1252" is the platform default in my case =).
131      * <p>
132      * If you don't set anything, then the platform default will be used (this is the default
133      * behavior).
134      *
135      * @see #getCharset()
136      *
137      * @param charset the name of the charset to be used or <code>null</code> to use the platform's
138      * default encoding.
139      * @throws IOException
140      */

141     public void setCharset(String JavaDoc charset) throws IOException JavaDoc
142     {
143         if (charset == null)
144         {
145             charsetName = charset;
146             return;
147         }
148
149         try
150         {
151             Charset.forName(charset);
152         }
153         catch (Exception JavaDoc e)
154         {
155             throw (IOException JavaDoc) new IOException JavaDoc("This charset is not supported").initCause(e);
156         }
157         charsetName = charset;
158     }
159
160     /**
161      * The currently used charset for filename encoding/decoding.
162      *
163      * @see #setCharset(String)
164      *
165      * @return The name of the charset (<code>null</code> if the platform's default charset is being used)
166      */

167     public String JavaDoc getCharset()
168     {
169         return charsetName;
170     }
171
172     private final void checkHandleValidAndOpen(SFTPv3FileHandle handle) throws IOException JavaDoc
173     {
174         if (handle.client != this)
175             throw new IOException JavaDoc("The file handle was created with another SFTPv3FileHandle instance.");
176
177         if (handle.isClosed == true)
178             throw new IOException JavaDoc("The file handle is closed.");
179     }
180
181     private final void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException JavaDoc
182     {
183         int msglen = len + 1;
184
185         if (type != Packet.SSH_FXP_INIT)
186             msglen += 4;
187
188         os.write(msglen >> 24);
189         os.write(msglen >> 16);
190         os.write(msglen >> 8);
191         os.write(msglen);
192         os.write(type);
193
194         if (type != Packet.SSH_FXP_INIT)
195         {
196             os.write(requestId >> 24);
197             os.write(requestId >> 16);
198             os.write(requestId >> 8);
199             os.write(requestId);
200         }
201
202         os.write(msg, off, len);
203         os.flush();
204     }
205
206     private final void sendMessage(int type, int requestId, byte[] msg) throws IOException JavaDoc
207     {
208         sendMessage(type, requestId, msg, 0, msg.length);
209     }
210
211     private final void readBytes(byte[] buff, int pos, int len) throws IOException JavaDoc
212     {
213         while (len > 0)
214         {
215             int count = is.read(buff, pos, len);
216             if (count < 0)
217                 throw new IOException JavaDoc("Unexpected end of sftp stream.");
218             if ((count == 0) || (count > len))
219                 throw new IOException JavaDoc("Underlying stream implementation is bogus!");
220             len -= count;
221             pos += count;
222         }
223     }
224
225     /**
226      * Read a message and guarantee that the <b>contents</b> is not larger than
227      * <code>maxlen</code> bytes.
228      * <p>
229      * Note: receiveMessage(34000) actually means that the message may be up to 34004
230      * bytes (the length attribute preceeding the contents is 4 bytes).
231      *
232      * @param maxlen
233      * @return the message contents
234      * @throws IOException
235      */

236     private final byte[] receiveMessage(int maxlen) throws IOException JavaDoc
237     {
238         byte[] msglen = new byte[4];
239
240         readBytes(msglen, 0, 4);
241
242         int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff));
243
244         if ((len > maxlen) || (len <= 0))
245             throw new IOException JavaDoc("Illegal sftp packet len: " + len);
246
247         byte[] msg = new byte[len];
248
249         readBytes(msg, 0, len);
250
251         return msg;
252     }
253
254     private final int generateNextRequestID()
255     {
256         synchronized (this)
257         {
258             return next_request_id++;
259         }
260     }
261
262     private final void closeHandle(byte[] handle) throws IOException JavaDoc
263     {
264         int req_id = generateNextRequestID();
265
266         TypesWriter tw = new TypesWriter();
267         tw.writeString(handle, 0, handle.length);
268
269         sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes());
270
271         expectStatusOKMessage(req_id);
272     }
273
274     private SFTPv3FileAttributes readAttrs(TypesReader tr) throws IOException JavaDoc
275     {
276         /*
277          * uint32 flags
278          * uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE
279          * uint32 uid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
280          * uint32 gid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
281          * uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
282          * uint32 atime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
283          * uint32 mtime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
284          * uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
285          * string extended_type
286          * string extended_data
287          * ... more extended data (extended_type - extended_data pairs),
288          * so that number of pairs equals extended_count
289          */

290
291         SFTPv3FileAttributes fa = new SFTPv3FileAttributes();
292
293         int flags = tr.readUINT32();
294
295         if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0)
296         {
297             if (debug != null)
298                 debug.println("SSH_FILEXFER_ATTR_SIZE");
299             fa.size = new Long JavaDoc(tr.readUINT64());
300         }
301
302         if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0)
303         {
304             if (debug != null)
305                 debug.println("SSH_FILEXFER_ATTR_V3_UIDGID");
306             fa.uid = new Integer JavaDoc(tr.readUINT32());
307             fa.gid = new Integer JavaDoc(tr.readUINT32());
308         }
309
310         if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0)
311         {
312             if (debug != null)
313                 debug.println("SSH_FILEXFER_ATTR_PERMISSIONS");
314             fa.permissions = new