Save This Page
Home » j2ssh-0.2.9-src » com.sshtools.j2ssh » [javadoc | source]
    1   /*
    2    *  SSHTools - Java SSH2 API
    3    *
    4    *  Copyright (C) 2002-2003 Lee David Painter and Contributors.
    5    *
    6    *  Contributions made by:
    7    *
    8    *  Brett Smith
    9    *  Richard Pernavas
   10    *  Erwin Bolwidt
   11    *
   12    *  This program is free software; you can redistribute it and/or
   13    *  modify it under the terms of the GNU General Public License
   14    *  as published by the Free Software Foundation; either version 2
   15    *  of the License, or (at your option) any later version.
   16    *
   17    *  This program is distributed in the hope that it will be useful,
   18    *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   19    *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   20    *  GNU General Public License for more details.
   21    *
   22    *  You should have received a copy of the GNU General Public License
   23    *  along with this program; if not, write to the Free Software
   24    *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
   25    */
   26   package com.sshtools.j2ssh;
   27   
   28   import java.io.BufferedInputStream;
   29   import java.io.EOFException;
   30   import java.io.File;
   31   import java.io.FileInputStream;
   32   import java.io.FileOutputStream;
   33   import java.io.IOException;
   34   import java.io.InputStream;
   35   import com.sshtools.j2ssh.configuration.ConfigurationLoader;
   36   import com.sshtools.j2ssh.connection.ChannelEventListener;
   37   import com.sshtools.j2ssh.session.SessionChannelClient;
   38   
   39   /**
   40    * <p>
   41    * Implements a Secure Copy (SCP) client. This may be useful when the server
   42    * does not support SFTP.
   43    * </p>
   44    *
   45    * @author Lee David Painter
   46    * @version $Revision: 1.18 $
   47    *
   48    * @since 0.2.0
   49    */
   50   public final class ScpClient {
   51     private SshClient ssh;
   52     private File cwd;
   53     private boolean verbose;
   54     private ChannelEventListener eventListener;
   55     /**
   56      * <p>
   57      * Creates an SCP client. CWD (Current working directory) will be the CWD
   58      * of the process that started this JVM.
   59      * </p>
   60      *
   61      * @param ssh A connected SshClient
   62      * @param verbose Output verbose detail
   63      * @param eventListener
   64      *
   65      * @since 0.2.0
   66      */
   67     public ScpClient(SshClient ssh, boolean verbose,
   68                      ChannelEventListener eventListener) {
   69       this(new File(ConfigurationLoader.checkAndGetProperty("user.dir", ".")),
   70            ssh, verbose, eventListener);
   71     }
   72   
   73     /**
   74      * <p>
   75      * Creates an SCP client.
   76      * </p>
   77      *
   78      * @param cwd The current local directory
   79      * @param ssh A connected SshClient
   80      * @param verbose Output verbose detail
   81      * @param eventListener
   82      *
   83      * @since 0.2.0
   84      */
   85     public ScpClient(File cwd, SshClient ssh, boolean verbose,
   86                      ChannelEventListener eventListener) {
   87       this.ssh = ssh;
   88       this.cwd = cwd;
   89       this.verbose = verbose;
   90       this.eventListener = eventListener;
   91     }
   92   
   93     /**
   94      * <p>
   95      * Uploads a <code>java.io.InputStream</code> to a remove server as file.
   96      * You <strong>must</strong> supply the correct number of bytes that will
   97      * be written.
   98      * </p>
   99      *
  100      * @param in stream providing file
  101      * @param length number of bytes that will be written
  102      * @param localFile local file name
  103      * @param remoteFile remote file name
  104      *
  105      * @throws IOException on any error
  106      */
  107     public void put(InputStream in, long length, String localFile,
  108                     String remoteFile) throws IOException {
  109       ScpChannel scp = new ScpChannel("scp -t " + (verbose ? "-v " : "")
  110                                       + remoteFile);
  111       scp.addEventListener(eventListener);
  112       if (!ssh.openChannel(scp)) {
  113         throw new IOException("Failed to open SCP channel");
  114       }
  115       scp.waitForResponse();
  116       scp.writeStreamToRemote(in, length, localFile);
  117       scp.close();
  118     }
  119   
  120     /**
  121      * <p>
  122      * Gets a remote file as an <code>java.io.InputStream</code>.
  123      * </p>
  124      *
  125      * @param remoteFile remote file name
  126      *
  127      * @return stream
  128      *
  129      * @throws IOException on any error
  130      */
  131     public InputStream get(String remoteFile) throws IOException {
  132       ScpChannel scp = new ScpChannel("scp " + "-f " + (verbose ? "-v " : "")
  133                                       + remoteFile);
  134       scp.addEventListener(eventListener);
  135       if (!ssh.openChannel(scp)) {
  136         throw new IOException("Failed to open SCP Channel");
  137       }
  138       return scp.readStreamFromRemote();
  139     }
  140   
  141     /**
  142      * <p>
  143      * Uploads a local file onto the remote server.
  144      * </p>
  145      *
  146      * @param localFile The path to the local file relative to the local
  147      *        current directory; may be a file or directory
  148      * @param remoteFile The path on the remote server, may be a file or
  149      *        directory
  150      * @param recursive Copy the contents of a directory recursivly
  151      *
  152      * @throws IOException if an IO error occurs during the operation
  153      *
  154      * @since 0.2.0
  155      */
  156     public void put(String localFile, String remoteFile, boolean recursive) throws
  157         IOException {
  158       File lf = new File(localFile);
  159       if (!lf.isAbsolute()) {
  160         lf = new File(cwd, localFile);
  161       }
  162       if (!lf.exists()) {
  163         throw new IOException(localFile + " does not exist");
  164       }
  165       if (!lf.isFile() && !lf.isDirectory()) {
  166         throw new IOException(localFile
  167                               + " is not a regular file or directory");
  168       }
  169       if (lf.isDirectory() && !recursive) {
  170         throw new IOException(localFile
  171                               + " is a directory, use recursive mode");
  172       }
  173       if ( (remoteFile == null) || remoteFile.equals("")) {
  174         remoteFile = ".";
  175       }
  176       ScpChannel scp = new ScpChannel("scp "
  177                                       + (lf.isDirectory() ? "-d " : "") + "-t "
  178                                       + (recursive ? "-r " : "") +
  179                                       (verbose ? "-v " : "")
  180                                       + remoteFile);
  181       scp.addEventListener(eventListener);
  182       if (!ssh.openChannel(scp)) {
  183         throw new IOException("Failed to open SCP channel");
  184       }
  185       scp.waitForResponse();
  186       scp.writeFileToRemote(lf, recursive);
  187       scp.close();
  188     }
  189   
  190     /**
  191      * <p>
  192      * Uploads an array of local files onto the remote server.
  193      * </p>
  194      *
  195      * @param localFiles an array of local files; may be files or directories
  196      * @param remoteFile the path on the remote server, may be a file or
  197      *        directory1
  198      * @param recursive Copy the contents of directorys recursivly
  199      *
  200      * @throws IOException if an IO error occurs during the operation
  201      *
  202      * @since 0.2.0
  203      */
  204     public void put(String[] localFiles, String remoteFile, boolean recursive) throws
  205         IOException {
  206       if ( (remoteFile == null) || remoteFile.equals("")) {
  207         remoteFile = ".";
  208       }
  209       if (localFiles.length == 1) {
  210         put(localFiles[0], remoteFile, recursive);
  211       }
  212       else {
  213         ScpChannel scp = new ScpChannel("scp " + "-d -t "
  214                                         + (recursive ? "-r " : "") +
  215                                         (verbose ? "-v " : "")
  216                                         + remoteFile);
  217         scp.addEventListener(eventListener);
  218         if (!ssh.openChannel(scp)) {
  219           throw new IOException("Failed to open SCP channel");
  220         }
  221         scp.waitForResponse();
  222         for (int i = 0; i < localFiles.length; i++) {
  223           File lf = new File(localFiles[i]);
  224           if (!lf.isAbsolute()) {
  225             lf = new File(cwd, localFiles[i]);
  226           }
  227           if (!lf.isFile() && !lf.isDirectory()) {
  228             throw new IOException(lf.getName()
  229                                   + " is not a regular file or directory");
  230           }
  231           scp.writeFileToRemote(lf, recursive);
  232         }
  233         scp.close();
  234       }
  235     }
  236   
  237     /**
  238      * <p>
  239      * Downloads an array of remote files to the local computer.
  240      * </p>
  241      *
  242      * @param localFile The local path to place the files
  243      * @param remoteFiles The path of the remote files
  244      * @param recursive recursivly copy the contents of a directory
  245      *
  246      * @throws IOException if an IO error occurs during the operation
  247      *
  248      * @since 0.2.0
  249      */
  250     public void get(String localFile, String[] remoteFiles, boolean recursive) throws
  251         IOException {
  252       StringBuffer buf = new StringBuffer();
  253       for (int i = 0; i < remoteFiles.length; i++) {
  254         buf.append("\"");
  255         buf.append(remoteFiles[i]);
  256         buf.append("\" ");
  257       }
  258       String remoteFile = buf.toString();
  259       remoteFile = remoteFile.trim();
  260       get(localFile, remoteFile, recursive);
  261     }
  262   
  263     /**
  264      * <p>
  265      * Downloads a remote file onto the local computer.
  266      * </p>
  267      *
  268      * @param localFile The path to place the file
  269      * @param remoteFile The path of the file on the remote server
  270      * @param recursive recursivly copy the contents of a directory
  271      *
  272      * @throws IOException if an IO error occurs during the operation
  273      *
  274      * @since 0.2.0
  275      */
  276     public void get(String localFile, String remoteFile, boolean recursive) throws
  277         IOException {
  278       if ( (localFile == null) || localFile.equals("")) {
  279         localFile = ".";
  280       }
  281       File lf = new File(localFile);
  282       if (!lf.isAbsolute()) {
  283         lf = new File(cwd, localFile);
  284       }
  285       if (lf.exists() && !lf.isFile() && !lf.isDirectory()) {
  286         throw new IOException(localFile
  287                               + " is not a regular file or directory");
  288       }
  289       ScpChannel scp = new ScpChannel("scp " + "-f "
  290                                       + (recursive ? "-r " : "") +
  291                                       (verbose ? "-v " : "")
  292                                       + remoteFile);
  293       scp.addEventListener(eventListener);
  294       if (!ssh.openChannel(scp)) {
  295         throw new IOException("Failed to open SCP Channel");
  296       }
  297       scp.readFromRemote(lf);
  298       scp.close();
  299     }
  300   
  301     /**
  302      * <p>
  303      * Implements an SCP channel by extending the
  304      * <code>SessionChannelClient</code>.
  305      * </p>
  306      *
  307      * @since 0.2.0
  308      */
  309     class ScpChannel
  310         extends SessionChannelClient {
  311       byte[] buffer = new byte[16384];
  312       String cmd;
  313       /**
  314        * <p>
  315        * Contruct the channel with the specified scp command.
  316        * </p>
  317        *
  318        * @param cmd The scp command
  319        *
  320        * @since 0.2.0
  321        */
  322       ScpChannel(String cmd) {
  323         this.cmd = cmd;
  324         setName("scp");
  325       }
  326   
  327       /**
  328        * <p>
  329        * This implementation executes the scp command when the channel is
  330        * opened.
  331        * </p>
  332        *
  333        * @throws IOException
  334        *
  335        * @since 0.2.0
  336        */
  337       protected void onChannelOpen() throws IOException {
  338         if (!executeCommand(cmd)) {
  339           throw new IOException("Failed to execute the command " + cmd);
  340         }
  341       }
  342   
  343       /**
  344        * <p>
  345        * Writes a directory to the remote server.
  346        * </p>
  347        *
  348        * @param dir The source directory
  349        * @param recursive Add the contents of the directory recursivley
  350        *
  351        * @return true if the file was written, otherwise false
  352        *
  353        * @throws IOException if an IO error occurs
  354        *
  355        * @since 0.2.0
  356        */
  357       private boolean writeDirToRemote(File dir, boolean recursive) throws
  358           IOException {
  359         if (!recursive) {
  360           writeError("File " + dir.getName()
  361                      + " is a directory, use recursive mode");
  362           return false;
  363         }
  364         String cmd = "D0755 0 " + dir.getName() + "\n";
  365         out.write(cmd.getBytes());
  366         waitForResponse();
  367         String[] list = dir.list();
  368         for (int i = 0; i < list.length; i++) {
  369           File f = new File(dir, list[i]);
  370           writeFileToRemote(f, recursive);
  371         }
  372         out.write("E\n".getBytes());
  373         return true;
  374       }
  375   
  376       /**
  377        * <p>
  378        * Write a stream as a file to the remote server. You
  379        * <strong>must</strong> supply the correct number of bytes that will
  380        * be written.
  381        * </p>
  382        *
  383        * @param in stream
  384        * @param length number of bytes to write
  385        * @param localName local file name
  386        *
  387        * @throws IOException if an IO error occurs
  388        *
  389        * @since 0.2.0
  390        */
  391       private void writeStreamToRemote(InputStream in, long length,
  392                                        String localName) throws IOException {
  393         String cmd = "C0644 " + length + " " + localName + "\n";
  394         out.write(cmd.getBytes());
  395         waitForResponse();
  396         writeCompleteFile(in, length);
  397         writeOk();
  398         waitForResponse();
  399       }
  400   
  401       /**
  402        * <p>
  403        * Write a file to the remote server.
  404        * </p>
  405        *
  406        * @param file The source file
  407        * @param recursive Add the contents of the directory recursivley
  408        *
  409        * @throws IOException if an IO error occurs
  410        *
  411        * @since 0.2.0
  412        */
  413       private void writeFileToRemote(File file, boolean recursive) throws
  414           IOException {
  415         if (file.isDirectory()) {
  416           if (!writeDirToRemote(file, recursive)) {
  417             return;
  418           }
  419         }
  420         else if (file.isFile()) {
  421           String cmd = "C0644 " + file.length() + " " + file.getName()
  422               + "\n";
  423           out.write(cmd.getBytes());
  424           waitForResponse();
  425           FileInputStream fi = new FileInputStream(file);
  426           writeCompleteFile(fi, file.length());
  427           writeOk();
  428         }
  429         else {
  430           throw new IOException(file.getName() + " not valid for SCP");
  431         }
  432         waitForResponse();
  433       }
  434   
  435       private void readFromRemote(File file) throws IOException {
  436         String cmd;
  437         String[] cmdParts = new String[3];
  438         writeOk();
  439         while (true) {
  440           try {
  441             cmd = readString();
  442           }
  443           catch (EOFException e) {
  444             return;
  445           }
  446           char cmdChar = cmd.charAt(0);
  447           switch (cmdChar) {
  448             case 'E':
  449               writeOk();
  450               return;
  451             case 'T':
  452               throw new IOException("SCP time not supported: " + cmd);
  453             case 'C':
  454             case 'D':
  455               String targetName = file.getAbsolutePath();
  456               parseCommand(cmd, cmdParts);
  457               if (file.isDirectory()) {
  458                 targetName += (File.separator + cmdParts[2]);
  459               }
  460               File targetFile = new File(targetName);
  461               if (cmdChar == 'D') {
  462                 if (targetFile.exists()) {
  463                   if (!targetFile.isDirectory()) {
  464                     String msg = "Invalid target "
  465                         + targetFile.getName()
  466                         + ", must be a directory";
  467                     writeError(msg);
  468                     throw new IOException(msg);
  469                   }
  470                 }
  471                 else {
  472                   if (!targetFile.mkdir()) {
  473                     String msg = "Could not create directory: "
  474                         + targetFile.getName();
  475                     writeError(msg);
  476                     throw new IOException(msg);
  477                   }
  478                 }
  479                 readFromRemote(targetFile);
  480                 continue;
  481               }
  482               FileOutputStream fo = new FileOutputStream(targetFile);
  483               writeOk();
  484               long len = Long.parseLong(cmdParts[1]);
  485               readCompleteFile(fo, len);
  486               waitForResponse();
  487               writeOk();
  488               break;
  489             default:
  490               writeError("Unexpected cmd: " + cmd);
  491               throw new IOException("SCP unexpected cmd: " + cmd);
  492           }
  493         }
  494       }
  495   
  496       private InputStream readStreamFromRemote() throws IOException {
  497         String cmd;
  498         String[] cmdParts = new String[3];
  499         writeOk();
  500         try {
  501           cmd = readString();
  502         }
  503         catch (EOFException e) {
  504           return null;
  505         }
  506         char cmdChar = cmd.charAt(0);
  507         switch (cmdChar) {
  508           case 'E':
  509             writeOk();
  510             return null;
  511           case 'T':
  512             throw new IOException("SCP time not supported: " + cmd);
  513           case 'D':
  514             throw new IOException(
  515                 "Directories cannot be copied to a stream");
  516           case 'C':
  517             parseCommand(cmd, cmdParts);
  518             writeOk();
  519             long len = Long.parseLong(cmdParts[1]);
  520             return new BufferedInputStream(new ScpInputStream(len, in, this),
  521                                            16 * 1024);
  522           default:
  523             writeError("Unexpected cmd: " + cmd);
  524             throw new IOException("SCP unexpected cmd: " + cmd);
  525         }
  526       }
  527   
  528       private void parseCommand(String cmd, String[] cmdParts) throws IOException {
  529         int l;
  530         int r;
  531         l = cmd.indexOf(' ');
  532         r = cmd.indexOf(' ', l + 1);
  533         if ( (l == -1) || (r == -1)) {
  534           writeError("Syntax error in cmd");
  535           throw new IOException("Syntax error in cmd");
  536         }
  537         cmdParts[0] = cmd.substring(1, l);
  538         cmdParts[1] = cmd.substring(l + 1, r);
  539         cmdParts[2] = cmd.substring(r + 1);
  540       }
  541   
  542       private String readString() throws IOException {
  543         int ch;
  544         int i = 0;
  545         while ( ( (ch = in.read()) != ( (int) '\n')) && (ch >= 0)) {
  546           buffer[i++] = (byte) ch;
  547         }
  548         if (ch == -1) {
  549           throw new EOFException("SCP returned unexpected EOF");
  550         }
  551         if (buffer[0] == (byte) '\n') {
  552           throw new IOException("Unexpected <NL>");
  553         }
  554         if ( (buffer[0] == (byte) '\02') || (buffer[0] == (byte) '\01')) {
  555           String msg = new String(buffer, 1, i - 1);
  556           if (buffer[0] == (byte) '\02') {
  557             throw new IOException(msg);
  558           }
  559           throw new IOException("SCP returned an unexpected error: "
  560                                 + msg);
  561         }
  562         return new String(buffer, 0, i);
  563       }
  564   
  565       private void waitForResponse() throws IOException {
  566         int r = in.read();
  567         if (r == 0) {
  568           // All is well, no error
  569           return;
  570         }
  571         if (r == -1) {
  572           throw new EOFException("SCP returned unexpected EOF");
  573         }
  574         String msg = readString();
  575         if (r == (byte) '\02') {
  576           throw new IOException(msg);
  577         }
  578         throw new IOException("SCP returned an unexpected error: " + msg);
  579       }
  580   
  581       private void writeOk() throws IOException {
  582         out.write(0);
  583       }
  584   
  585       private void writeError(String reason) throws IOException {
  586         out.write(1);
  587         out.write(reason.getBytes());
  588       }
  589   
  590       private void writeCompleteFile(InputStream file, long size) throws
  591           IOException {
  592         int count = 0;
  593         int read;
  594         try {
  595           while (count < size) {
  596             read = file.read(buffer, 0,
  597                              (int) ( ( (size - count) < buffer.length)
  598                                     ? (size - count) : buffer.length));
  599             if (read == -1) {
  600               throw new EOFException("SCP received an unexpected EOF");
  601             }
  602             count += read;
  603             out.write(buffer, 0, read);
  604           }
  605         }
  606         finally {
  607           file.close();
  608         }
  609       }
  610   
  611       private void readCompleteFile(FileOutputStream file, long size) throws
  612           IOException {
  613         int count = 0;
  614         int read;
  615         try {
  616           while (count < size) {
  617             read = in.read(buffer, 0,
  618                            (int) ( ( (size - count) < buffer.length)
  619                                   ? (size - count) : buffer.length));
  620             if (read == -1) {
  621               throw new EOFException("SCP received an unexpected EOF");
  622             }
  623             count += read;
  624             file.write(buffer, 0, read);
  625           }
  626         }
  627         finally {
  628           file.close();
  629         }
  630       }
  631     }
  632   
  633     class ScpInputStream
  634         extends InputStream {
  635       long length;
  636       InputStream in;
  637       long count;
  638       ScpChannel channel;
  639       ScpInputStream(long length, InputStream in, ScpChannel channel) {
  640         this.length = length;
  641         this.in = in;
  642         this.channel = channel;
  643       }
  644   
  645       public int read() throws IOException {
  646         if (count == length) {
  647           return -1;
  648         }
  649         if (count >= length) {
  650           throw new EOFException("End of file.");
  651         }
  652         int r = in.read();
  653         if (r == -1) {
  654           throw new EOFException("Unexpected EOF.");
  655         }
  656         count++;
  657         if (count == length) {
  658           channel.waitForResponse();
  659           channel.writeOk();
  660         }
  661         return r;
  662       }
  663   
  664       public void close() throws IOException {
  665         channel.close();
  666       }
  667     }
  668   }

Save This Page
Home » j2ssh-0.2.9-src » com.sshtools.j2ssh » [javadoc | source]