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