1 package ch.ethz.ssh2; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.nio.charset.Charset; 6 import java.nio.charset.UnsupportedCharsetException; 7 8 /** 9 * A very basic <code>SCPClient</code> that can be used to copy files from/to 10 * the SSH-2 server. On the server side, the "scp" program must be in the PATH. 11 * <p/> 12 * This scp client is thread safe - you can download (and upload) different sets 13 * of files concurrently without any troubles. The <code>SCPClient</code> is 14 * actually mapping every request to a distinct {@link ch.ethz.ssh2.Session}. 15 * 16 * @author Christian Plattner, plattner@inf.ethz.ch 17 * @version $Id: SCPClient.java 32 2011-05-28 21:56:21Z dkocher@sudo.ch $ 18 */ 19 20 public class SCPClient 21 { 22 Connection conn; 23 24 String charsetName = null; 25 26 /** 27 * Set the charset used to convert between Java Unicode Strings and byte encodings 28 * used by the server for paths and file names. 29 * 30 * @param charset the name of the charset to be used or <code>null</code> to use the platform's 31 * default encoding. 32 * @throws IOException 33 * @see #getCharset() 34 */ setCharset(String charset)35 public void setCharset(String charset) throws IOException 36 { 37 if (charset == null) 38 { 39 charsetName = charset; 40 return; 41 } 42 43 try 44 { 45 Charset.forName(charset); 46 } 47 catch (UnsupportedCharsetException e) 48 { 49 throw (IOException) new IOException("This charset is not supported").initCause(e); 50 } 51 charsetName = charset; 52 } 53 54 /** 55 * The currently used charset for filename encoding/decoding. 56 * 57 * @return The name of the charset (<code>null</code> if the platform's default charset is being used) 58 * @see #setCharset(String) 59 */ getCharset()60 public String getCharset() 61 { 62 return charsetName; 63 } 64 65 public class LenNamePair 66 { 67 public long length; 68 String filename; 69 } 70 SCPClient(Connection conn)71 public SCPClient(Connection conn) 72 { 73 if (conn == null) 74 throw new IllegalArgumentException("Cannot accept null argument!"); 75 this.conn = conn; 76 } 77 readResponse(InputStream is)78 protected void readResponse(InputStream is) throws IOException 79 { 80 int c = is.read(); 81 82 if (c == 0) 83 return; 84 85 if (c == -1) 86 throw new IOException("Remote scp terminated unexpectedly."); 87 88 if ((c != 1) && (c != 2)) 89 throw new IOException("Remote scp sent illegal error code."); 90 91 if (c == 2) 92 throw new IOException("Remote scp terminated with error."); 93 94 String err = receiveLine(is); 95 throw new IOException("Remote scp terminated with error (" + err + ")."); 96 } 97 receiveLine(InputStream is)98 protected String receiveLine(InputStream is) throws IOException 99 { 100 StringBuilder sb = new StringBuilder(30); 101 102 while (true) 103 { 104 /* This is a random limit - if your path names are longer, then adjust it */ 105 106 if (sb.length() > 8192) 107 throw new IOException("Remote scp sent a too long line"); 108 109 int c = is.read(); 110 111 if (c < 0) 112 throw new IOException("Remote scp terminated unexpectedly."); 113 114 if (c == '\n') 115 break; 116 117 sb.append((char) c); 118 119 } 120 return sb.toString(); 121 } 122 parseCLine(String line)123 protected LenNamePair parseCLine(String line) throws IOException 124 { 125 /* Minimum line: "xxxx y z" ---> 8 chars */ 126 127 if (line.length() < 8) 128 throw new IOException("Malformed C line sent by remote SCP binary, line too short."); 129 130 if ((line.charAt(4) != ' ') || (line.charAt(5) == ' ')) 131 throw new IOException("Malformed C line sent by remote SCP binary."); 132 133 int length_name_sep = line.indexOf(' ', 5); 134 135 if (length_name_sep == -1) 136 throw new IOException("Malformed C line sent by remote SCP binary."); 137 138 String length_substring = line.substring(5, length_name_sep); 139 String name_substring = line.substring(length_name_sep + 1); 140 141 if ((length_substring.length() <= 0) || (name_substring.length() <= 0)) 142 throw new IOException("Malformed C line sent by remote SCP binary."); 143 144 if ((6 + length_substring.length() + name_substring.length()) != line.length()) 145 throw new IOException("Malformed C line sent by remote SCP binary."); 146 147 final long len; 148 try 149 { 150 len = Long.parseLong(length_substring); 151 } 152 catch (NumberFormatException e) 153 { 154 throw new IOException("Malformed C line sent by remote SCP binary, cannot parse file length."); 155 } 156 157 if (len < 0) 158 throw new IOException("Malformed C line sent by remote SCP binary, illegal file length."); 159 160 LenNamePair lnp = new LenNamePair(); 161 lnp.length = len; 162 lnp.filename = name_substring; 163 164 return lnp; 165 } 166 167 /** 168 * The session for opened for this SCP transfer must be closed using 169 * SCPOutputStream#close 170 * 171 * @param remoteFile 172 * @param length The size of the file to send 173 * @param remoteTargetDirectory 174 * @param mode 175 * @return 176 * @throws IOException 177 */ put(final String remoteFile, long length, String remoteTargetDirectory, String mode)178 public SCPOutputStream put(final String remoteFile, long length, String remoteTargetDirectory, String mode) 179 throws IOException 180 { 181 Session sess = null; 182 183 if (null == remoteFile) 184 throw new IllegalArgumentException("Null argument."); 185 if (null == remoteTargetDirectory) 186 remoteTargetDirectory = ""; 187 if (null == mode) 188 mode = "0600"; 189 if (mode.length() != 4) 190 throw new IllegalArgumentException("Invalid mode."); 191 192 for (int i = 0; i < mode.length(); i++) 193 if (Character.isDigit(mode.charAt(i)) == false) 194 throw new IllegalArgumentException("Invalid mode."); 195 196 remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : "."; 197 198 String cmd = "scp -t -d \"" + remoteTargetDirectory + "\""; 199 200 sess = conn.openSession(); 201 sess.execCommand(cmd, charsetName); 202 203 return new SCPOutputStream(this, sess, remoteFile, length, mode); 204 } 205 206 /** 207 * The session for opened for this SCP transfer must be closed using 208 * SCPInputStream#close 209 * 210 * @param remoteFile 211 * @return 212 * @throws IOException 213 */ get(final String remoteFile)214 public SCPInputStream get(final String remoteFile) throws IOException 215 { 216 Session sess = null; 217 218 if (null == remoteFile) 219 throw new IllegalArgumentException("Null argument."); 220 221 if (remoteFile.length() == 0) 222 throw new IllegalArgumentException("Cannot accept empty filename."); 223 224 String cmd = "scp -f"; 225 cmd += (" \"" + remoteFile + "\""); 226 227 sess = conn.openSession(); 228 sess.execCommand(cmd, charsetName); 229 230 return new SCPInputStream(this, sess); 231 } 232 }