Mercurial > 510Connectbot
comparison src/com/trilead/ssh2/SFTPv3Client.java @ 0:0ce5cc452d02
initial version
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Thu, 22 May 2014 10:41:19 -0700 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:0ce5cc452d02 |
---|---|
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 } |