Home | History | Annotate | Download | only in ssh2
      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 (at) inf.ethz.ch
     17  * @version $Id: SCPClient.java 32 2011-05-28 21:56:21Z dkocher (at) 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 	 */
     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 	 */
     60 	public String getCharset()
     61 	{
     62 		return charsetName;
     63 	}
     64 
     65 	public class LenNamePair
     66 	{
     67 		public long length;
     68 		String filename;
     69 	}
     70 
     71 	public SCPClient(Connection conn)
     72 	{
     73 		if (conn == null)
     74 			throw new IllegalArgumentException("Cannot accept null argument!");
     75 		this.conn = conn;
     76 	}
     77 
     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 
     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 
    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 	 */
    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 	 */
    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 }