0
|
1
|
|
2 package com.trilead.ssh2;
|
|
3
|
|
4 import java.io.BufferedOutputStream;
|
|
5 import java.io.IOException;
|
|
6 import java.io.InputStream;
|
|
7 import java.io.OutputStream;
|
|
8 import java.io.PrintStream;
|
|
9 import java.nio.charset.Charset;
|
|
10 import java.util.HashMap;
|
|
11 import java.util.Vector;
|
|
12
|
|
13 import com.trilead.ssh2.packets.TypesReader;
|
|
14 import com.trilead.ssh2.packets.TypesWriter;
|
|
15 import com.trilead.ssh2.sftp.AttribFlags;
|
|
16 import com.trilead.ssh2.sftp.ErrorCodes;
|
|
17 import com.trilead.ssh2.sftp.Packet;
|
|
18
|
|
19
|
|
20 /**
|
|
21 * A <code>SFTPv3Client</code> represents a SFTP (protocol version 3)
|
|
22 * client connection tunnelled over a SSH-2 connection. This is a very simple
|
|
23 * (synchronous) implementation.
|
|
24 * <p>
|
|
25 * Basically, most methods in this class map directly to one of
|
|
26 * the packet types described in draft-ietf-secsh-filexfer-02.txt.
|
|
27 * <p>
|
|
28 * Note: this is experimental code.
|
|
29 * <p>
|
|
30 * Error handling: the methods of this class throw IOExceptions. However, unless
|
|
31 * there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will
|
|
32 * be thrown (a subclass of IOException). Therefore, you can implement more verbose
|
|
33 * behavior by checking if a thrown exception if of this type. If yes, then you
|
|
34 * can cast the exception and access detailed information about the failure.
|
|
35 * <p>
|
|
36 * Notes about file names, directory names and paths, copy-pasted
|
|
37 * from the specs:
|
|
38 * <ul>
|
|
39 * <li>SFTP v3 represents file names as strings. File names are
|
|
40 * assumed to use the slash ('/') character as a directory separator.</li>
|
|
41 * <li>File names starting with a slash are "absolute", and are relative to
|
|
42 * the root of the file system. Names starting with any other character
|
|
43 * are relative to the user's default directory (home directory).</li>
|
|
44 * <li>Servers SHOULD interpret a path name component ".." as referring to
|
|
45 * the parent directory, and "." as referring to the current directory.
|
|
46 * If the server implementation limits access to certain parts of the
|
|
47 * file system, it must be extra careful in parsing file names when
|
|
48 * enforcing such restrictions. There have been numerous reported
|
|
49 * security bugs where a ".." in a path name has allowed access outside
|
|
50 * the intended area.</li>
|
|
51 * <li>An empty path name is valid, and it refers to the user's default
|
|
52 * directory (usually the user's home directory).</li>
|
|
53 * </ul>
|
|
54 * <p>
|
|
55 * If you are still not tired then please go on and read the comment for
|
|
56 * {@link #setCharset(String)}.
|
|
57 *
|
|
58 * @author Christian Plattner, plattner@trilead.com
|
|
59 * @version $Id: SFTPv3Client.java,v 1.3 2008/04/01 12:38:09 cplattne Exp $
|
|
60 */
|
|
61 public class SFTPv3Client {
|
|
62 final Connection conn;
|
|
63 final Session sess;
|
|
64 final PrintStream debug;
|
|
65
|
|
66 boolean flag_closed = false;
|
|
67
|
|
68 InputStream is;
|
|
69 OutputStream os;
|
|
70
|
|
71 int protocol_version = 0;
|
|
72 HashMap server_extensions = new HashMap();
|
|
73
|
|
74 int next_request_id = 1000;
|
|
75
|
|
76 String 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 @Deprecated
|
|
89 public SFTPv3Client(Connection conn, PrintStream debug) throws IOException {
|
|
90 if (conn == null)
|
|
91 throw new IllegalArgumentException("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 is = sess.getStdout();
|
|
102 os = new BufferedOutputStream(sess.getStdin(), 2048);
|
|
103
|
|
104 if ((is == null) || (os == null))
|
|
105 throw new IOException("There is a problem with the streams of the underlying channel.");
|
|
106
|
|
107 init();
|
|
108 }
|
|
109
|
|
110 /**
|
|
111 * Create a SFTP v3 client.
|
|
112 *
|
|
113 * @param conn The underlying SSH-2 connection to be used.
|
|
114 * @throws IOException
|
|
115 */
|
|
116 public SFTPv3Client(Connection conn) throws IOException {
|
|
117 this(conn, null);
|
|
118 }
|
|
119
|
|
120 /**
|
|
121 * Set the charset used to convert between Java Unicode Strings and byte encodings
|
|
122 * used by the server for paths and file names. Unfortunately, the SFTP v3 draft
|
|
123 * says NOTHING about such conversions (well, with the exception of error messages
|
|
124 * which have to be in UTF-8). Newer drafts specify to use UTF-8 for file names
|
|
125 * (if I remember correctly). However, a quick test using OpenSSH serving a EXT-3
|
|
126 * filesystem has shown that UTF-8 seems to be a bad choice for SFTP v3 (tested with
|
|
127 * filenames containing german umlauts). "windows-1252" seems to work better for Europe.
|
|
128 * Luckily, "windows-1252" is the platform default in my case =).
|
|
129 * <p>
|
|
130 * If you don't set anything, then the platform default will be used (this is the default
|
|
131 * behavior).
|
|
132 *
|
|
133 * @see #getCharset()
|
|
134 *
|
|
135 * @param charset the name of the charset to be used or <code>null</code> to use the platform's
|
|
136 * default encoding.
|
|
137 * @throws IOException
|
|
138 */
|
|
139 public void setCharset(String charset) throws IOException {
|
|
140 if (charset == null) {
|
|
141 charsetName = charset;
|
|
142 return;
|
|
143 }
|
|
144
|
|
145 try {
|
|
146 Charset.forName(charset);
|
|
147 }
|
|
148 catch (Exception e) {
|
|
149 throw(IOException) new IOException("This charset is not supported").initCause(e);
|
|
150 }
|
|
151
|
|
152 charsetName = charset;
|
|
153 }
|
|
154
|
|
155 /**
|
|
156 * The currently used charset for filename encoding/decoding.
|
|
157 *
|
|
158 * @see #setCharset(String)
|
|
159 *
|
|
160 * @return The name of the charset (<code>null</code> if the platform's default charset is being used)
|
|
161 */
|
|
162 public String getCharset() {
|
|
163 return charsetName;
|
|
164 }
|
|
165
|
|
166 private final void checkHandleValidAndOpen(SFTPv3FileHandle handle) throws IOException {
|
|
167 if (handle.client != this)
|
|
168 throw new IOException("The file handle was created with another SFTPv3FileHandle instance.");
|
|
169
|
|
170 if (handle.isClosed == true)
|
|
171 throw new IOException("The file handle is closed.");
|
|
172 }
|
|
173
|
|
174 private final void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException {
|
|
175 int msglen = len + 1;
|
|
176
|
|
177 if (type != Packet.SSH_FXP_INIT)
|
|
178 msglen += 4;
|
|
179
|
|
180 os.write(msglen >> 24);
|
|
181 os.write(msglen >> 16);
|
|
182 os.write(msglen >> 8);
|
|
183 os.write(msglen);
|
|
184 os.write(type);
|
|
185
|
|
186 if (type != Packet.SSH_FXP_INIT) {
|
|
187 os.write(requestId >> 24);
|
|
188 os.write(requestId >> 16);
|
|
189 os.write(requestId >> 8);
|
|
190 os.write(requestId);
|
|
191 }
|
|
192
|
|
193 os.write(msg, off, len);
|
|
194 os.flush();
|
|
195 }
|
|
196
|
|
197 private final void sendMessage(int type, int requestId, byte[] msg) throws IOException {
|
|
198 sendMessage(type, requestId, msg, 0, msg.length);
|
|
199 }
|
|
200
|
|
201 private final void readBytes(byte[] buff, int pos, int len) throws IOException {
|
|
202 while (len > 0) {
|
|
203 int count = is.read(buff, pos, len);
|
|
204
|
|
205 if (count < 0)
|
|
206 throw new IOException("Unexpected end of sftp stream.");
|
|
207
|
|
208 if ((count == 0) || (count > len))
|
|
209 throw new IOException("Underlying stream implementation is bogus!");
|
|
210
|
|
211 len -= count;
|
|
212 pos += count;
|
|
213 }
|
|
214 }
|
|
215
|
|
216 /**
|
|
217 * Read a message and guarantee that the <b>contents</b> is not larger than
|
|
218 * <code>maxlen</code> bytes.
|
|
219 * <p>
|
|
220 * Note: receiveMessage(34000) actually means that the message may be up to 34004
|
|
221 * bytes (the length attribute preceeding the contents is 4 bytes).
|
|
222 *
|
|
223 * @param maxlen
|
|
224 * @return the message contents
|
|
225 * @throws IOException
|
|
226 */
|
|
227 private final byte[] receiveMessage(int maxlen) throws IOException {
|
|
228 byte[] msglen = new byte[4];
|
|
229 readBytes(msglen, 0, 4);
|
|
230 int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff));
|
|
231
|
|
232 if ((len > maxlen) || (len <= 0))
|
|
233 throw new IOException("Illegal sftp packet len: " + len);
|
|
234
|
|
235 byte[] msg = new byte[len];
|
|
236 readBytes(msg, 0, len);
|
|
237 return msg;
|
|
238 }
|
|
239
|
|
240 private final int generateNextRequestID() {
|
|
241 synchronized (this) {
|
|
242 return next_request_id++;
|
|
243 }
|
|
244 }
|
|
245
|
|
246 private final void closeHandle(byte[] handle) throws IOException {
|
|
247 int req_id = generateNextRequestID();
|
|
248 TypesWriter tw = new TypesWriter();
|
|
249 tw.writeString(handle, 0, handle.length);
|
|
250 sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes());
|
|
251 expectStatusOKMessage(req_id);
|
|
252 }
|
|
253
|
|
254 private SFTPv3FileAttributes readAttrs(TypesReader tr) throws IOException {
|
|
255 /*
|
|
256 * uint32 flags
|
|
257 * uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE
|
|
258 * uint32 uid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
|
|
259 * uint32 gid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
|
|
260 * uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
|
|
261 * uint32 atime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
|
|
262 * uint32 mtime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
|
|
263 * uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
|
|
264 * string extended_type
|
|
265 * string extended_data
|
|
266 * ... more extended data (extended_type - extended_data pairs),
|
|
267 * so that number of pairs equals extended_count
|
|
268 */
|
|
269 SFTPv3FileAttributes fa = new SFTPv3FileAttributes();
|
|
270 int flags = tr.readUINT32();
|
|
271
|
|
272 if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0) {
|
|
273 if (debug != null)
|
|
274 debug.println("SSH_FILEXFER_ATTR_SIZE");
|
|
275
|
|
276 fa.size = Long.valueOf(tr.readUINT64());
|
|
277 }
|
|
278
|
|
279 if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0) {
|
|
280 if (debug != null)
|
|
281 debug.println("SSH_FILEXFER_ATTR_V3_UIDGID");
|
|
282
|
|
283 fa.uid = Integer.valueOf(tr.readUINT32());
|
|
284 fa.gid = Integer.valueOf(tr.readUINT32());
|
|
285 }
|
|
286
|
|
287 if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
|
|
288 if (debug != null)
|
|
289 debug.println("SSH_FILEXFER_ATTR_PERMISSIONS");
|
|
290
|
|
291 fa.permissions = Integer.valueOf(tr.readUINT32());
|
|
292 }
|
|
293
|
|
294 if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0) {
|
|
295 if (debug != null)
|
|
296 debug.println("SSH_FILEXFER_ATTR_V3_ACMODTIME");
|
|
297
|
|
298 fa.atime = Long.valueOf((tr.readUINT32()) & 0xffffffffl);
|
|
299 fa.mtime = Long.valueOf((tr.readUINT32()) & 0xffffffffl);
|
|
300 }
|
|
301
|
|
302 if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0) {
|
|
303 int count = tr.readUINT32();
|
|
304
|
|
305 if (debug != null)
|
|
306 debug.println("SSH_FILEXFER_ATTR_EXTENDED (" + count + ")");
|
|
307
|
|
308 /* Read it anyway to detect corrupt packets */
|
|
309
|
|
310 while (count > 0) {
|
|
311 tr.readByteString();
|
|
312 tr.readByteString();
|
|
313 count--;
|
|
314 }
|
|
315 }
|
|
316
|
|
317 return fa;
|
|
318 }
|
|
319
|
|
320 /**
|
|
321 * Retrieve the file attributes of an open file.
|
|
322 *
|
|
323 * @param handle a SFTPv3FileHandle handle.
|
|
324 * @return a SFTPv3FileAttributes object.
|
|
325 * @throws IOException
|
|
326 */
|
|
327 public SFTPv3FileAttributes fstat(SFTPv3FileHandle handle) throws IOException {
|
|
328 checkHandleValidAndOpen(handle);
|
|
329 int req_id = generateNextRequestID();
|
|
330 TypesWriter tw = new TypesWriter();
|
|
331 tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
|
|
332
|
|
333 if (debug != null) {
|
|
334 debug.println("Sending SSH_FXP_FSTAT...");
|
|
335 debug.flush();
|
|
336 }
|
|
337
|
|
338 sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes());
|
|
339 byte[] resp = receiveMessage(34000);
|
|
340
|
|
341 if (debug != null) {
|
|
342 debug.println("Got REPLY.");
|
|
343 debug.flush();
|
|
344 }
|
|
345
|
|
346 TypesReader tr = new TypesReader(resp);
|
|
347 int t = tr.readByte();
|
|
348 int rep_id = tr.readUINT32();
|
|
349
|
|
350 if (rep_id != req_id)
|
|
351 throw new IOException("The server sent an invalid id field.");
|
|
352
|
|
353 if (t == Packet.SSH_FXP_ATTRS) {
|
|
354 return readAttrs(tr);
|
|
355 }
|
|
356
|
|
357 if (t != Packet.SSH_FXP_STATUS)
|
|
358 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
|
|
359
|
|
360 int errorCode = tr.readUINT32();
|
|
361 throw new SFTPException(tr.readString(), errorCode);
|
|
362 }
|
|
363
|
|
364 private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException {
|
|
365 int req_id = generateNextRequestID();
|
|
366 TypesWriter tw = new TypesWriter();
|
|
367 tw.writeString(path, charsetName);
|
|
368
|
|
369 if (debug != null) {
|
|
370 debug.println("Sending SSH_FXP_STAT/SSH_FXP_LSTAT...");
|
|
371 debug.flush();
|
|
372 }
|
|
373
|
|
374 sendMessage(statMethod, req_id, tw.getBytes());
|
|
375 byte[] resp = receiveMessage(34000);
|
|
376
|
|
377 if (debug != null) {
|
|
378 debug.println("Got REPLY.");
|
|
379 debug.flush();
|
|
380 }
|
|
381
|
|
382 TypesReader tr = new TypesReader(resp);
|
|
383 int t = tr.readByte();
|
|
384 int rep_id = tr.readUINT32();
|
|
385
|
|
386 if (rep_id != req_id)
|
|
387 throw new IOException("The server sent an invalid id field.");
|
|
388
|
|
389 if (t == Packet.SSH_FXP_ATTRS) {
|
|
390 return readAttrs(tr);
|
|
391 }
|
|
392
|
|
393 if (t != Packet.SSH_FXP_STATUS)
|
|
394 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
|
|
395
|
|
396 int errorCode = tr.readUINT32();
|
|
397 throw new SFTPException(tr.readString(), errorCode);
|
|
398 }
|
|
399
|
|
400 /**
|
|
401 * Retrieve the file attributes of a file. This method
|
|
402 * follows symbolic links on the server.
|
|
403 *
|
|
404 * @see #lstat(String)
|
|
405 *
|
|
406 * @param path See the {@link SFTPv3Client comment} for the class for more details.
|
|
407 * @return a SFTPv3FileAttributes object.
|
|
408 * @throws IOException
|
|
409 */
|
|
410 public SFTPv3FileAttributes stat(String path) throws IOException {
|
|
411 return statBoth(path, Packet.SSH_FXP_STAT);
|
|
412 }
|
|
413
|
|
414 /**
|
|
415 * Retrieve the file attributes of a file. This method
|
|
416 * does NOT follow symbolic links on the server.
|
|
417 *
|
|
418 * @see #stat(String)
|
|
419 *
|
|
420 * @param path See the {@link SFTPv3Client comment} for the class for more details.
|
|
421 * @return a SFTPv3FileAttributes object.
|
|
422 * @throws IOException
|
|
423 */
|
|
424 public SFTPv3FileAttributes lstat(String path) throws IOException {
|
|
425 return statBoth(path, Packet.SSH_FXP_LSTAT);
|
|
426 }
|
|
427
|
|
428 /**
|
|
429 * Read the target of a symbolic link.
|
|
430 *
|
|
431 * @param path See the {@link SFTPv3Client comment} for the class for more details.
|
|
432 * @return The target of the link.
|
|
433 * @throws IOException
|
|
434 */
|
|
435 public String readLink(String path) throws IOException {
|
|
436 int req_id = generateNextRequestID();
|
|
437 TypesWriter tw = new TypesWriter();
|
|
438 tw.writeString(path, charsetName);
|
|
439
|
|
440 if (debug != null) {
|
|
441 debug.println("Sending SSH_FXP_READLINK...");
|
|
442 debug.flush();
|
|
443 }
|
|
444
|
|
445 sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes());
|
|
446 byte[] resp = receiveMessage(34000);
|
|
447
|
|
448 if (debug != null) {
|
|
449 debug.println("Got REPLY.");
|
|
450 debug.flush();
|
|
451 }
|
|
452
|
|
453 TypesReader tr = new TypesReader(resp);
|
|
454 int t = tr.readByte();
|
|
455 int rep_id = tr.readUINT32();
|
|
456
|
|
457 if (rep_id != req_id)
|
|
458 throw new IOException("The server sent an invalid id field.");
|
|
459
|
|
460 if (t == Packet.SSH_FXP_NAME) {
|
|
461 int count = tr.readUINT32();
|
|
462
|
|
463 if (count != 1)
|
|
464 throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");
|
|
465
|
|
466 return tr.readString(charsetName);
|
|
467 }
|
|
468
|
|
469 if (t != Packet.SSH_FXP_STATUS)
|
|
470 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
|
|
471
|
|
472 int errorCode = tr.readUINT32();
|
|
473 throw new SFTPException(tr.readString(), errorCode);
|
|
474 }
|
|
475
|
|
476 private void expectStatusOKMessage(int id) throws IOException {
|
|
477 byte[] resp = receiveMessage(34000);
|
|
478
|
|
479 if (debug != null) {
|
|
480 debug.println("Got REPLY.");
|
|
481 debug.flush();
|
|
482 }
|
|
483
|
|
484 TypesReader tr = new TypesReader(resp);
|
|
485 int t = tr.readByte();
|
|
486 int rep_id = tr.readUINT32();
|
|
487
|
|
488 if (rep_id != id)
|
|
489 throw new IOException("The server sent an invalid id field.");
|
|
490
|
|
491 if (t != Packet.SSH_FXP_STATUS)
|
|
492 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
|
|
493
|
|
494 int errorCode = tr.readUINT32();
|
|
495
|
|
496 if (errorCode == ErrorCodes.SSH_FX_OK)
|
|
497 return;
|
|
498
|
|
499 throw new SFTPException(tr.readString(), errorCode);
|
|
500 }
|
|
501
|
|
502 /**
|
|
503 * Modify the attributes of a file. Used for operations such as changing
|
|
504 * the ownership, permissions or access times, as well as for truncating a file.
|
|
505 *
|
|
506 * @param path See the {@link SFTPv3Client comment} for the class for more details.
|
|
507 * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
|
|
508 * made to the attributes of the file. Empty fields will be ignored.
|
|
509 * @throws IOException
|
|
510 */
|
|
511 public void setstat(String path, SFTPv3FileAttributes attr) throws IOException {
|
|
512 int req_id = generateNextRequestID();
|
|
513 TypesWriter tw = new TypesWriter();
|
|
514 tw.writeString(path, charsetName);
|
|
515 tw.writeBytes(createAttrs(attr));
|
|
516
|
|
517 if (debug != null) {
|
|
518 debug.println("Sending SSH_FXP_SETSTAT...");
|
|
519 debug.flush();
|
|
520 }
|
|
521
|
|
522 sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes());
|
|
523 expectStatusOKMessage(req_id);
|
|
524 }
|
|
525
|
|
526 /**
|
|
527 * Modify the attributes of a file. Used for operations such as changing
|
|
528 * the ownership, permissions or access times, as well as for truncating a file.
|
|
529 *
|
|
530 * @param handle a SFTPv3FileHandle handle
|
|
531 * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
|
|
532 * made to the attributes of the file. Empty fields will be ignored.
|
|
533 * @throws IOException
|
|
534 */
|
|
535 public void fsetstat(SFTPv3FileHandle handle, SFTPv3FileAttributes attr) throws IOException {
|
|
536 checkHandleValidAndOpen(handle);
|
|
537 int req_id = generateNextRequestID();
|
|
538 TypesWriter tw = new TypesWriter();
|
|
539 tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
|
|
540 tw.writeBytes(createAttrs(attr));
|
|
541
|
|
542 if (debug != null) {
|
|
543 debug.println("Sending SSH_FXP_FSETSTAT...");
|
|
544 debug.flush();
|
|
545 }
|
|
546
|
|
547 sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes());
|
|
548 expectStatusOKMessage(req_id);
|
|
549 }
|
|
550
|
|
551 /**
|
|
552 * Create a symbolic link on the server. Creates a link "src" that points
|
|
553 * to "target".
|
|
554 *
|
|
555 * @param src See the {@link SFTPv3Client comment} for the class for more details.
|
|
556 * @param target See the {@link SFTPv3Client comment} for the class for more details.
|
|
557 * @throws IOException
|
|
558 */
|
|
559 public void createSymlink(String src, String target) throws IOException {
|
|
560 int req_id = generateNextRequestID();
|
|
561 /* Either I am too stupid to understand the SFTP draft
|
|
562 * or the OpenSSH guys changed the semantics of src and target.
|
|
563 */
|
|
564 TypesWriter tw = new TypesWriter();
|
|
565 tw.writeString(target, charsetName);
|
|
566 tw.writeString(src, charsetName);
|
|
567
|
|
568 if (debug != null) {
|
|
569 debug.println("Sending SSH_FXP_SYMLINK...");
|
|
570 debug.flush();
|
|
571 }
|
|
572
|
|
573 sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes());
|
|
574 expectStatusOKMessage(req_id);
|
|
575 }
|
|
576
|
|
577 /**
|
|
578 * Have the server canonicalize any given path name to an absolute path.
|
|
579 * This is useful for converting path names containing ".." components or
|
|
580 * relative pathnames without a leading slash into absolute paths.
|
|
581 *
|
|
582 * @param path See the {@link SFTPv3Client comment} for the class for more details.
|
|
583 * @return An absolute path.
|
|
584 * @throws IOException
|
|
585 */
|
|
586 public String canonicalPath(String path) throws IOException {
|
|
587 int req_id = generateNextRequestID();
|
|
588 TypesWriter tw = new TypesWriter();
|
|
589 tw.writeString(path, charsetName);
|
|
590
|
|
591 if (debug != null) {
|
|
592 debug.println("Sending SSH_FXP_REALPATH...");
|
|
593 debug.flush();
|
|
594 }
|
|
595
|
|
596 sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes());
|
|
597 byte[] resp = receiveMessage(34000);
|
|
598
|
|
599 if (debug != null) {
|
|
600 debug.println("Got REPLY.");
|
|
601 debug.flush();
|
|
602 }
|
|
603
|
|
604 TypesReader tr = new TypesReader(resp);
|
|
605 int t = tr.readByte();
|
|
606 int rep_id = tr.readUINT32();
|
|
607
|
|
608 if (rep_id != req_id)
|
|
609 throw new IOException("The server sent an invalid id field.");
|
|
610
|
|
611 if (t == Packet.SSH_FXP_NAME) {
|
|
612 int count = tr.readUINT32();
|
|
613
|
|
614 if (count != 1)
|
|
615 throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");
|
|
616
|
|
617 return tr.readString(charsetName);
|
|
618 }
|
|
619
|
|
620 if (t != Packet.SSH_FXP_STATUS)
|
|
621 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
|
|
622
|
|
623 int errorCode = tr.readUINT32();
|
|
624 throw new SFTPException(tr.readString(), errorCode);
|
|
625 }
|
|
626
|
|
627 private final Vector scanDirectory(byte[] handle) throws IOException {
|
|
628 Vector files = new Vector();
|
|
629
|
|
630 while (true) {
|
|
631 int req_id = generateNextRequestID();
|
|
632 TypesWriter tw = new TypesWriter();
|
|
633 tw.writeString(handle, 0, handle.length);
|
|
634
|
|
635 if (debug != null) {
|
|
636 debug.println("Sending SSH_FXP_READDIR...");
|
|
637 debug.flush();
|
|
638 }
|
|
639
|
|
640 sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes());
|
|
641 /* Some servers send here a packet with size > 34000 */
|
|
642 /* To whom it may concern: please learn to read the specs. */
|
|
643 byte[] resp = receiveMessage(65536);
|
|
644
|
|
645 if (debug != null) {
|
|
646 debug.println("Got REPLY.");
|
|
647 debug.flush();
|
|
648 }
|
|
649
|
|
650 TypesReader tr = new TypesReader(resp);
|
|
651 int t = tr.readByte();
|
|
652 int rep_id = tr.readUINT32();
|
|
653
|
|
654 if (rep_id != req_id)
|
|
655 throw new IOException("The server sent an invalid id field.");
|
|
656
|
|
657 if (t == Packet.SSH_FXP_NAME) {
|
|
658 int count = tr.readUINT32();
|
|
659
|
|
660 if (debug != null)
|
|
661 debug.println("Parsing " + count + " name entries...");
|
|
662
|
|
663 while (count > 0) {
|
|
664 SFTPv3DirectoryEntry dirEnt = new SFTPv3DirectoryEntry();
|
|
665 dirEnt.filename = tr.readString(charsetName);
|
|
666 dirEnt.longEntry = tr.readString(charsetName);
|
|
667 dirEnt.attributes = readAttrs(tr);
|
|
668 files.addElement(dirEnt);
|
|
669
|
|
670 if (debug != null)
|
|
671 debug.println("File: '" + dirEnt.filename + "'");
|
|
672
|
|
673 count--;
|
|
674 }
|
|
675
|
|
676 continue;
|
|
677 }
|
|
678
|
|
679 if (t != Packet.SSH_FXP_STATUS)
|
|
680 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
|
|
681
|
|
682 int errorCode = tr.readUINT32();
|
|
683
|
|
684 if (errorCode == ErrorCodes.SSH_FX_EOF)
|
|
685 return files;
|
|
686
|
|
687 throw new SFTPException(tr.readString(), errorCode);
|
|
688 }
|
|
689 }
|
|
690
|
|
691 private final byte[] openDirectory(String path) throws IOException {
|
|
692 int req_id = generateNextRequestID();
|
|
693 TypesWriter tw = new TypesWriter();
|
|
694 tw.writeString(path, charsetName);
|
|
695
|
|
696 if (debug != null) {
|
|
697 debug.println("Sending SSH_FXP_OPENDIR...");
|
|
698 debug.flush();
|
|
699 }
|
|
700
|
|
701 sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes());
|
|
702 byte[] resp = receiveMessage(34000);
|
|
703 TypesReader tr = new TypesReader(resp);
|
|
704 int t = tr.readByte();
|
|
705 int rep_id = tr.readUINT32();
|
|
706
|
|
707 if (rep_id != req_id)
|
|
708 throw new IOException("The server sent an invalid id field.");
|
|
709
|
|
710 if (t == Packet.SSH_FXP_HANDLE) {
|
|
711 if (debug != null) {
|
|
712 debug.println("Got SSH_FXP_HANDLE.");
|
|
713 debug.flush();
|
|
714 }
|
|
715
|
|
716 byte[] handle = tr.readByteString();
|
|
717 return handle;
|
|
718 }
|
|
719
|
|
720 if (t != Packet.SSH_FXP_STATUS)
|
|
721 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
|
|
722
|
|
723 int errorCode = tr.readUINT32();
|
|
724 String errorMessage = tr.readString();
|
|
725 throw new SFTPException(errorMessage, errorCode);
|
|
726 }
|
|
727
|
|
728 private final String expandString(byte[] b, int off, int len) {
|
|
729 StringBuffer sb = new StringBuffer();
|
|
730
|
|
731 for (int i = 0; i < len; i++) {
|
|
732 int c = b[off + i] & 0xff;
|
|
733
|
|
734 if ((c >= 32) && (c <= 126)) {
|
|
735 sb.append((char) c);
|
|
736 }
|
|
737 else {
|
|
738 sb.append("{0x" + Integer.toHexString(c) + "}");
|
|
739 }
|
|
740 }
|
|
741
|
|
742 return sb.toString();
|
|
743 }
|
|
744
|
|
745 private void init() throws IOException {
|
|
746 /* Send SSH_FXP_INIT (version 3) */
|
|
747 final int client_version = 3;
|
|
748
|
|
749 if (debug != null)
|
|
750 debug.println("Sending SSH_FXP_INIT (" + client_version + ")...");
|
|
751
|
|
752 TypesWriter tw = new TypesWriter();
|
|
753 tw.writeUINT32(client_version);
|
|
754 sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes());
|
|
755
|
|
756 /* Receive SSH_FXP_VERSION */
|
|
757
|
|
758 if (debug != null)
|
|
759 debug.println("Waiting for SSH_FXP_VERSION...");
|
|
760
|
|
761 TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */
|
|
762 int type = tr.readByte();
|
|
763
|
|
764 if (type != Packet.SSH_FXP_VERSION) {
|
|
765 throw new IOException("The server did not send a SSH_FXP_VERSION packet (got " + type + ")");
|
|
766 }
|
|
767
|
|
768 protocol_version = tr.readUINT32();
|
|
769
|
|
770 if (debug != null)
|
|
771 debug.println("SSH_FXP_VERSION: protocol_version = " + protocol_version);
|
|
772
|
|
773 if (protocol_version != 3)
|
|
774 throw new IOException("Server version " + protocol_version + " is currently not supported");
|
|
775
|
|
776 /* Read and save extensions (if any) for later use */
|
|
777
|
|
778 while (tr.remain() != 0) {
|
|
779 String name = tr.readString();
|
|
780 byte[] value = tr.readByteString();
|
|
781 server_extensions.put(name, value);
|
|
782
|
|
783 if (debug != null)
|
|
784 debug.println("SSH_FXP_VERSION: extension: " + name + " = '" + expandString(value, 0, value.length)
|
|
785 + "'");
|
|
786 }
|
|
787 }
|
|
788
|
|
789 /**
|
|
790 * Returns the negotiated SFTP protocol version between the client and the server.
|
|
791 *
|
|
792 * @return SFTP protocol version, i.e., "3".
|
|
793 *
|
|
794 */
|
|
795 public int getProtocolVersion() {
|
|
796 return protocol_version;
|
|
797 }
|
|
798
|
|
799 /**
|
|
800 * Close this SFTP session. NEVER forget to call this method to free up
|
|
801 * resources - even if you got an exception from one of the other methods.
|
|
802 * Sometimes these other methods may throw an exception, saying that the
|
|
803 * underlying channel is closed (this can happen, e.g., if the other server
|
|
804 * sent a close message.) However, as long as you have not called the
|
|
805 * <code>close()</code> method, you are likely wasting resources.
|
|
806 *
|
|
807 */
|
|
808 public void close() {
|
|
809 sess.close();
|
|
810 }
|
|
811
|
|
812 /**
|
|
813 * List the contents of a directory.
|
|
814 *
|
|
815 * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
|
|
816 * @return A Vector containing {@link SFTPv3DirectoryEntry} objects.
|
|
817 * @throws IOException
|
|
818 */
|
|
819 public Vector ls(String dirName) throws IOException {
|
|
820 byte[] handle = openDirectory(dirName);
|
|
821 Vector result = scanDirectory(handle);
|
|
822 closeHandle(handle);
|
|
823 return result;
|
|
824 }
|
|
825
|
|
826 /**
|
|
827 * Create a new directory.
|
|
828 *
|
|
829 * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
|
|
830 * @param posixPermissions the permissions for this directory, e.g., "0700" (remember that
|
|
831 * this is octal noation). The server will likely apply a umask.
|
|
832 *
|
|
833 * @throws IOException
|
|
834 */
|
|
835 public void mkdir(String dirName, int posixPermissions) throws IOException {
|
|
836 int req_id = generateNextRequestID();
|
|
837 TypesWriter tw = new TypesWriter();
|
|
838 tw.writeString(dirName, charsetName);
|
|
839 tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS);
|
|
840 tw.writeUINT32(posixPermissions);
|
|
841 sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes());
|
|
842 expectStatusOKMessage(req_id);
|
|
843 }
|
|
844
|
|
845 /**
|
|
846 * Remove a file.
|
|
847 *
|
|
848 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
|
|
849 * @throws IOException
|
|
850 */
|
|
851 public void rm(String fileName) throws IOException {
|
|
852 int req_id = generateNextRequestID();
|
|
853 TypesWriter tw = new TypesWriter();
|
|
854 tw.writeString(fileName, charsetName);
|
|
855 sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes());
|
|
856 expectStatusOKMessage(req_id);
|
|
857 }
|
|
858
|
|
859 /**
|
|
860 * Remove an empty directory.
|
|
861 *
|
|
862 * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
|
|
863 * @throws IOException
|
|
864 */
|
|
865 public void rmdir(String dirName) throws IOException {
|
|
866 int req_id = generateNextRequestID();
|
|
867 TypesWriter tw = new TypesWriter();
|
|
868 tw.writeString(dirName, charsetName);
|
|
869 sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes());
|
|
870 expectStatusOKMessage(req_id);
|
|
871 }
|
|
872
|
|
873 /**
|
|
874 * Move a file or directory.
|
|
875 *
|
|
876 * @param oldPath See the {@link SFTPv3Client comment} for the class for more details.
|
|
877 * @param newPath See the {@link SFTPv3Client comment} for the class for more details.
|
|
878 * @throws IOException
|
|
879 */
|
|
880 public void mv(String oldPath, String newPath) throws IOException {
|
|
881 int req_id = generateNextRequestID();
|
|
882 TypesWriter tw = new TypesWriter();
|
|
883 tw.writeString(oldPath, charsetName);
|
|
884 tw.writeString(newPath, charsetName);
|
|
885 sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes());
|
|
886 expectStatusOKMessage(req_id);
|
|
887 }
|
|
888
|
|
889 /**
|
|
890 * Open a file for reading.
|
|
891 *
|
|
892 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
|
|
893 * @return a SFTPv3FileHandle handle
|
|
894 * @throws IOException
|
|
895 */
|
|
896 public SFTPv3FileHandle openFileRO(String fileName) throws IOException {
|
|
897 return openFile(fileName, 0x00000001, null); // SSH_FXF_READ
|
|
898 }
|
|
899
|
|
900 /**
|
|
901 * Open a file for reading and writing.
|
|
902 *
|
|
903 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
|
|
904 * @return a SFTPv3FileHandle handle
|
|
905 * @throws IOException
|
|
906 */
|
|
907 public SFTPv3FileHandle openFileRW(String fileName) throws IOException {
|
|
908 return openFile(fileName, 0x00000003, null); // SSH_FXF_READ | SSH_FXF_WRITE
|
|
909 }
|
|
910
|
|
911 // Append is broken (already in the specification, because there is no way to
|
|
912 // send a write operation (what offset to use??))
|
|
913 // public SFTPv3FileHandle openFileRWAppend(String fileName) throws IOException
|
|
914 // {
|
|
915 // return openFile(fileName, 0x00000007, null); // SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND
|
|
916 // }
|
|
917
|
|
918 /**
|
|
919 * Create a file and open it for reading and writing.
|
|
920 * Same as {@link #createFile(String, SFTPv3FileAttributes) createFile(fileName, null)}.
|
|
921 *
|
|
922 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
|
|
923 * @return a SFTPv3FileHandle handle
|
|
924 * @throws IOException
|
|
925 */
|
|
926 public SFTPv3FileHandle createFile(String fileName) throws IOException {
|
|
927 return createFile(fileName, null);
|
|
928 }
|
|
929
|
|
930 /**
|
|
931 * Create a file and open it for reading and writing.
|
|
932 * You can specify the default attributes of the file (the server may or may
|
|
933 * not respect your wishes).
|
|
934 *
|
|
935 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
|
|
936 * @param attr may be <code>null</code> to use server defaults. Probably only
|
|
937 * the <code>uid</code>, <code>gid</code> and <code>permissions</code>
|
|
938 * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
|
|
939 * structure make sense. You need only to set those fields where you want
|
|
940 * to override the server's defaults.
|
|
941 * @return a SFTPv3FileHandle handle
|
|
942 * @throws IOException
|
|
943 */
|
|
944 public SFTPv3FileHandle createFile(String fileName, SFTPv3FileAttributes attr) throws IOException {
|
|
945 return openFile(fileName, 0x00000008 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE
|
|
946 }
|
|
947
|
|
948 /**
|
|
949 * Create a file (truncate it if it already exists) and open it for reading and writing.
|
|
950 * Same as {@link #createFileTruncate(String, SFTPv3FileAttributes) createFileTruncate(fileName, null)}.
|
|
951 *
|
|
952 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
|
|
953 * @return a SFTPv3FileHandle handle
|
|
954 * @throws IOException
|
|
955 */
|
|
956 public SFTPv3FileHandle createFileTruncate(String fileName) throws IOException {
|
|
957 return createFileTruncate(fileName, null);
|
|
958 }
|
|
959
|
|
960 /**
|
|
961 * reate a file (truncate it if it already exists) and open it for reading and writing.
|
|
962 * You can specify the default attributes of the file (the server may or may
|
|
963 * not respect your wishes).
|
|
964 *
|
|
965 * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
|
|
966 * @param attr may be <code>null</code> to use server defaults. Probably only
|
|
967 * the <code>uid</code>, <code>gid</code> and <code>permissions</code>
|
|
968 * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
|
|
969 * structure make sense. You need only to set those fields where you want
|
|
970 * to override the server's defaults.
|
|
971 * @return a SFTPv3FileHandle handle
|
|
972 * @throws IOException
|
|
973 */
|
|
974 public SFTPv3FileHandle createFileTruncate(String fileName, SFTPv3FileAttributes attr) throws IOException {
|
|
975 return openFile(fileName, 0x00000018 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_READ | SSH_FXF_WRITE
|
|
976 }
|
|
977
|
|
978 private byte[] createAttrs(SFTPv3FileAttributes attr) {
|
|
979 TypesWriter tw = new TypesWriter();
|
|
980 int attrFlags = 0;
|
|
981
|
|
982 if (attr == null) {
|
|
983 tw.writeUINT32(0);
|
|
984 }
|
|
985 else {
|
|
986 if (attr.size != null)
|
|
987 attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE;
|
|
988
|
|
989 if ((attr.uid != null) && (attr.gid != null))
|
|
990 attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID;
|
|
991
|
|
992 if (attr.permissions != null)
|
|
993 attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS;
|
|
994
|
|
995 if ((attr.atime != null) && (attr.mtime != null))
|
|
996 attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME;
|
|
997
|
|
998 tw.writeUINT32(attrFlags);
|
|
999
|
|
1000 if (attr.size != null)
|
|
1001 tw.writeUINT64(attr.size.longValue());
|
|
1002
|
|
1003 if ((attr.uid != null) && (attr.gid != null)) {
|
|
1004 tw.writeUINT32(attr.uid.intValue());
|
|
1005 tw.writeUINT32(attr.gid.intValue());
|
|
1006 }
|
|
1007
|
|
1008 if (attr.permissions != null)
|
|
1009 tw.writeUINT32(attr.permissions.intValue());
|
|
1010
|
|
1011 if ((attr.atime != null) && (attr.mtime != null)) {
|
|
1012 tw.writeUINT32(attr.atime.intValue());
|
|
1013 tw.writeUINT32(attr.mtime.intValue());
|
|
1014 }
|
|
1015 }
|
|
1016
|
|
1017 return tw.getBytes();
|
|
1018 }
|
|
1019
|
|
1020 private SFTPv3FileHandle openFile(String fileName, int flags, SFTPv3FileAttributes attr) throws IOException {
|
|
1021 int req_id = generateNextRequestID();
|
|
1022 TypesWriter tw = new TypesWriter();
|
|
1023 tw.writeString(fileName, charsetName);
|
|
1024 tw.writeUINT32(flags);
|
|
1025 tw.writeBytes(createAttrs(attr));
|
|
1026
|
|
1027 if (debug != null) {
|
|
1028 debug.println("Sending SSH_FXP_OPEN...");
|
|
1029 debug.flush();
|
|
1030 }
|
|
1031
|
|
1032 sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes());
|
|
1033 byte[] resp = receiveMessage(34000);
|
|
1034 TypesReader tr = new TypesReader(resp);
|
|
1035 int t = tr.readByte();
|
|
1036 int rep_id = tr.readUINT32();
|
|
1037
|
|
1038 if (rep_id != req_id)
|
|
1039 throw new IOException("The server sent an invalid id field.");
|
|
1040
|
|
1041 if (t == Packet.SSH_FXP_HANDLE) {
|
|
1042 if (debug != null) {
|
|
1043 debug.println("Got SSH_FXP_HANDLE.");
|
|
1044 debug.flush();
|
|
1045 }
|
|
1046
|
|
1047 return new SFTPv3FileHandle(this, tr.readByteString());
|
|
1048 }
|
|
1049
|
|
1050 if (t != Packet.SSH_FXP_STATUS)
|
|
1051 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
|
|
1052
|
|
1053 int errorCode = tr.readUINT32();
|
|
1054 String errorMessage = tr.readString();
|
|
1055 throw new SFTPException(errorMessage, errorCode);
|
|
1056 }
|
|
1057
|
|
1058 /**
|
|
1059 * Read bytes from a file. No more than 32768 bytes may be read at once.
|
|
1060 * Be aware that the semantics of read() are different than for Java streams.
|
|
1061 * <p>
|
|
1062 * <ul>
|
|
1063 * <li>The server will read as many bytes as it can from the file (up to <code>len</code>),
|
|
1064 * and return them.</li>
|
|
1065 * <li>If EOF is encountered before reading any data, <code>-1</code> is returned.
|
|
1066 * <li>If an error occurs, an exception is thrown</li>.
|
|
1067 * <li>For normal disk files, it is guaranteed that the server will return the specified
|
|
1068 * number of bytes, or up to end of file. For, e.g., device files this may return
|
|
1069 * fewer bytes than requested.</li>
|
|
1070 * </ul>
|
|
1071 *
|
|
1072 * @param handle a SFTPv3FileHandle handle
|
|
1073 * @param fileOffset offset (in bytes) in the file
|
|
1074 * @param dst the destination byte array
|
|
1075 * @param dstoff offset in the destination byte array
|
|
1076 * @param len how many bytes to read, 0 < len <= 32768 bytes
|
|
1077 * @return the number of bytes that could be read, may be less than requested if
|
|
1078 * the end of the file is reached, -1 is returned in case of <code>EOF</code>
|
|
1079 * @throws IOException
|
|
1080 */
|
|
1081 public int read(SFTPv3FileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException {
|
|
1082 checkHandleValidAndOpen(handle);
|
|
1083
|
|
1084 if ((len > 32768) || (len <= 0))
|
|
1085 throw new IllegalArgumentException("invalid len argument");
|
|
1086
|
|
1087 int req_id = generateNextRequestID();
|
|
1088 TypesWriter tw = new TypesWriter();
|
|
1089 tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
|
|
1090 tw.writeUINT64(fileOffset);
|
|
1091 tw.writeUINT32(len);
|
|
1092
|
|
1093 if (debug != null) {
|
|
1094 debug.println("Sending SSH_FXP_READ...");
|
|
1095 debug.flush();
|
|
1096 }
|
|
1097
|
|
1098 sendMessage(Packet.SSH_FXP_READ, req_id, tw.getBytes());
|
|
1099 byte[] resp = receiveMessage(34000);
|
|
1100 TypesReader tr = new TypesReader(resp);
|
|
1101 int t = tr.readByte();
|
|
1102 int rep_id = tr.readUINT32();
|
|
1103
|
|
1104 if (rep_id != req_id)
|
|
1105 throw new IOException("The server sent an invalid id field.");
|
|
1106
|
|
1107 if (t == Packet.SSH_FXP_DATA) {
|
|
1108 if (debug != null) {
|
|
1109 debug.println("Got SSH_FXP_DATA...");
|
|
1110 debug.flush();
|
|
1111 }
|
|
1112
|
|
1113 int readLen = tr.readUINT32();
|
|
1114
|
|
1115 if ((readLen < 0) || (readLen > len))
|
|
1116 throw new IOException("The server sent an invalid length field.");
|
|
1117
|
|
1118 tr.readBytes(dst, dstoff, readLen);
|
|
1119 return readLen;
|
|
1120 }
|
|
1121
|
|
1122 if (t != Packet.SSH_FXP_STATUS)
|
|
1123 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
|
|
1124
|
|
1125 int errorCode = tr.readUINT32();
|
|
1126
|
|
1127 if (errorCode == ErrorCodes.SSH_FX_EOF) {
|
|
1128 if (debug != null) {
|
|
1129 debug.println("Got SSH_FX_EOF.");
|
|
1130 debug.flush();
|
|
1131 }
|
|
1132
|
|
1133 return -1;
|
|
1134 }
|
|
1135
|
|
1136 String errorMessage = tr.readString();
|
|
1137 throw new SFTPException(errorMessage, errorCode);
|
|
1138 }
|
|
1139
|
|
1140 /**
|
|
1141 * Write bytes to a file. If <code>len</code> > 32768, then the write operation will
|
|
1142 * be split into multiple writes.
|
|
1143 *
|
|
1144 * @param handle a SFTPv3FileHandle handle.
|
|
1145 * @param fileOffset offset (in bytes) in the file.
|
|
1146 * @param src the source byte array.
|
|
1147 * @param srcoff offset in the source byte array.
|
|
1148 * @param len how many bytes to write.
|
|
1149 * @throws IOException
|
|
1150 */
|
|
1151 public void write(SFTPv3FileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException {
|
|
1152 checkHandleValidAndOpen(handle);
|
|
1153
|
|
1154 while (len > 0) {
|
|
1155 int writeRequestLen = len;
|
|
1156
|
|
1157 if (writeRequestLen > 32768)
|
|
1158 writeRequestLen = 32768;
|
|
1159
|
|
1160 int req_id = generateNextRequestID();
|
|
1161 TypesWriter tw = new TypesWriter();
|
|
1162 tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
|
|
1163 tw.writeUINT64(fileOffset);
|
|
1164 tw.writeString(src, srcoff, writeRequestLen);
|
|
1165
|
|
1166 if (debug != null) {
|
|
1167 debug.println("Sending SSH_FXP_WRITE...");
|
|
1168 debug.flush();
|
|
1169 }
|
|
1170
|
|
1171 sendMessage(Packet.SSH_FXP_WRITE, req_id, tw.getBytes());
|
|
1172 fileOffset += writeRequestLen;
|
|
1173 srcoff += writeRequestLen;
|
|
1174 len -= writeRequestLen;
|
|
1175 byte[] resp = receiveMessage(34000);
|
|
1176 TypesReader tr = new TypesReader(resp);
|
|
1177 int t = tr.readByte();
|
|
1178 int rep_id = tr.readUINT32();
|
|
1179
|
|
1180 if (rep_id != req_id)
|
|
1181 throw new IOException("The server sent an invalid id field.");
|
|
1182
|
|
1183 if (t != Packet.SSH_FXP_STATUS)
|
|
1184 throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
|
|
1185
|
|
1186 int errorCode = tr.readUINT32();
|
|
1187
|
|
1188 if (errorCode == ErrorCodes.SSH_FX_OK)
|
|
1189 continue;
|
|
1190
|
|
1191 String errorMessage = tr.readString();
|
|
1192 throw new SFTPException(errorMessage, errorCode);
|
|
1193 }
|
|
1194 }
|
|
1195
|
|
1196 /**
|
|
1197 * Close a file.
|
|
1198 *
|
|
1199 * @param handle a SFTPv3FileHandle handle
|
|
1200 * @throws IOException
|
|
1201 */
|
|
1202 public void closeFile(SFTPv3FileHandle handle) throws IOException {
|
|
1203 if (handle == null)
|
|
1204 throw new IllegalArgumentException("the handle argument may not be null");
|
|
1205
|
|
1206 try {
|
|
1207 if (handle.isClosed == false) {
|
|
1208 closeHandle(handle.fileHandle);
|
|
1209 }
|
|
1210 }
|
|
1211 finally {
|
|
1212 handle.isClosed = true;
|
|
1213 }
|
|
1214 }
|
|
1215 }
|