1package ch.ethz.ssh2;
2
3import java.io.IOException;
4import java.io.InputStream;
5import java.nio.charset.Charset;
6import 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
20public 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}