001: package ch.ethz.ssh2;
002:
003: import java.io.BufferedInputStream;
004: import java.io.BufferedOutputStream;
005: import java.io.File;
006: import java.io.FileInputStream;
007: import java.io.FileOutputStream;
008: import java.io.IOException;
009: import java.io.InputStream;
010: import java.io.OutputStream;
011:
012: /**
013: * A very basic <code>SCPClient</code> that can be used to copy files from/to
014: * the SSH-2 server. On the server side, the "scp" program must be in the PATH.
015: * <p>
016: * This scp client is thread safe - you can download (and upload) different sets
017: * of files concurrently without any troubles. The <code>SCPClient</code> is
018: * actually mapping every request to a distinct {@link Session}.
019: *
020: * @author Christian Plattner, plattner@inf.ethz.ch
021: * @version $Id: SCPClient.java,v 1.11 2006/08/02 11:57:12 cplattne Exp $
022: */
023:
024: public class SCPClient {
025: Connection conn;
026:
027: class LenNamePair {
028: long length;
029: String filename;
030: }
031:
032: public SCPClient(Connection conn) {
033: if (conn == null)
034: throw new IllegalArgumentException(
035: "Cannot accept null argument!");
036: this .conn = conn;
037: }
038:
039: private void readResponse(InputStream is) throws IOException {
040: int c = is.read();
041:
042: if (c == 0)
043: return;
044:
045: if (c == -1)
046: throw new IOException("Remote scp terminated unexpectedly.");
047:
048: if ((c != 1) && (c != 2))
049: throw new IOException("Remote scp sent illegal error code.");
050:
051: if (c == 2)
052: throw new IOException("Remote scp terminated with error.");
053:
054: String err = receiveLine(is);
055: throw new IOException("Remote scp terminated with error ("
056: + err + ").");
057: }
058:
059: private String receiveLine(InputStream is) throws IOException {
060: StringBuffer sb = new StringBuffer(30);
061:
062: while (true) {
063: /* This is a random limit - if your path names are longer, then adjust it */
064:
065: if (sb.length() > 8192)
066: throw new IOException("Remote scp sent a too long line");
067:
068: int c = is.read();
069:
070: if (c < 0)
071: throw new IOException(
072: "Remote scp terminated unexpectedly.");
073:
074: if (c == '\n')
075: break;
076:
077: sb.append((char) c);
078:
079: }
080: return sb.toString();
081: }
082:
083: private LenNamePair parseCLine(String line) throws IOException {
084: /* Minimum line: "xxxx y z" ---> 8 chars */
085:
086: long len;
087:
088: if (line.length() < 8)
089: throw new IOException(
090: "Malformed C line sent by remote SCP binary, line too short.");
091:
092: if ((line.charAt(4) != ' ') || (line.charAt(5) == ' '))
093: throw new IOException(
094: "Malformed C line sent by remote SCP binary.");
095:
096: int length_name_sep = line.indexOf(' ', 5);
097:
098: if (length_name_sep == -1)
099: throw new IOException(
100: "Malformed C line sent by remote SCP binary.");
101:
102: String length_substring = line.substring(5, length_name_sep);
103: String name_substring = line.substring(length_name_sep + 1);
104:
105: if ((length_substring.length() <= 0)
106: || (name_substring.length() <= 0))
107: throw new IOException(
108: "Malformed C line sent by remote SCP binary.");
109:
110: if ((6 + length_substring.length() + name_substring.length()) != line
111: .length())
112: throw new IOException(
113: "Malformed C line sent by remote SCP binary.");
114:
115: try {
116: len = Long.parseLong(length_substring);
117: } catch (NumberFormatException e) {
118: throw new IOException(
119: "Malformed C line sent by remote SCP binary, cannot parse file length.");
120: }
121:
122: if (len < 0)
123: throw new IOException(
124: "Malformed C line sent by remote SCP binary, illegal file length.");
125:
126: LenNamePair lnp = new LenNamePair();
127: lnp.length = len;
128: lnp.filename = name_substring;
129:
130: return lnp;
131: }
132:
133: private void sendBytes(Session sess, byte[] data, String fileName,
134: String mode) throws IOException {
135: OutputStream os = sess.getStdin();
136: InputStream is = new BufferedInputStream(sess.getStdout(), 512);
137:
138: readResponse(is);
139:
140: String cline = "C" + mode + " " + data.length + " " + fileName
141: + "\n";
142:
143: os.write(cline.getBytes());
144: os.flush();
145:
146: readResponse(is);
147:
148: os.write(data, 0, data.length);
149: os.write(0);
150: os.flush();
151:
152: readResponse(is);
153:
154: os.write("E\n".getBytes());
155: os.flush();
156: }
157:
158: private void sendFiles(Session sess, String[] files,
159: String[] remoteFiles, String mode) throws IOException {
160: byte[] buffer = new byte[8192];
161:
162: OutputStream os = new BufferedOutputStream(sess.getStdin(),
163: 40000);
164: InputStream is = new BufferedInputStream(sess.getStdout(), 512);
165:
166: readResponse(is);
167:
168: for (int i = 0; i < files.length; i++) {
169: File f = new File(files[i]);
170: long remain = f.length();
171:
172: String remoteName;
173:
174: if ((remoteFiles != null) && (remoteFiles.length > i)
175: && (remoteFiles[i] != null))
176: remoteName = remoteFiles[i];
177: else
178: remoteName = f.getName();
179:
180: String cline = "C" + mode + " " + remain + " " + remoteName
181: + "\n";
182:
183: os.write(cline.getBytes());
184: os.flush();
185:
186: readResponse(is);
187:
188: FileInputStream fis = null;
189:
190: try {
191: fis = new FileInputStream(f);
192:
193: while (remain > 0) {
194: int trans;
195: if (remain > buffer.length)
196: trans = buffer.length;
197: else
198: trans = (int) remain;
199:
200: if (fis.read(buffer, 0, trans) != trans)
201: throw new IOException(
202: "Cannot read enough from local file "
203: + files[i]);
204:
205: os.write(buffer, 0, trans);
206:
207: remain -= trans;
208: }
209: } finally {
210: if (fis != null)
211: fis.close();
212: }
213:
214: os.write(0);
215: os.flush();
216:
217: readResponse(is);
218: }
219:
220: os.write("E\n".getBytes());
221: os.flush();
222: }
223:
224: private void receiveFiles(Session sess, OutputStream[] targets)
225: throws IOException {
226: byte[] buffer = new byte[8192];
227:
228: OutputStream os = new BufferedOutputStream(sess.getStdin(), 512);
229: InputStream is = new BufferedInputStream(sess.getStdout(),
230: 40000);
231:
232: os.write(0x0);
233: os.flush();
234:
235: for (int i = 0; i < targets.length; i++) {
236: LenNamePair lnp = null;
237:
238: while (true) {
239: int c = is.read();
240: if (c < 0)
241: throw new IOException(
242: "Remote scp terminated unexpectedly.");
243:
244: String line = receiveLine(is);
245:
246: if (c == 'T') {
247: /* Ignore modification times */
248:
249: continue;
250: }
251:
252: if ((c == 1) || (c == 2))
253: throw new IOException("Remote SCP error: " + line);
254:
255: if (c == 'C') {
256: lnp = parseCLine(line);
257: break;
258:
259: }
260: throw new IOException("Remote SCP error: " + ((char) c)
261: + line);
262: }
263:
264: os.write(0x0);
265: os.flush();
266:
267: long remain = lnp.length;
268:
269: while (remain > 0) {
270: int trans;
271: if (remain > buffer.length)
272: trans = buffer.length;
273: else
274: trans = (int) remain;
275:
276: int this _time_received = is.read(buffer, 0, trans);
277:
278: if (this _time_received < 0) {
279: throw new IOException(
280: "Remote scp terminated connection unexpectedly");
281: }
282:
283: targets[i].write(buffer, 0, this _time_received);
284:
285: remain -= this _time_received;
286: }
287:
288: readResponse(is);
289:
290: os.write(0x0);
291: os.flush();
292: }
293: }
294:
295: private void receiveFiles(Session sess, String[] files,
296: String target) throws IOException {
297: byte[] buffer = new byte[8192];
298:
299: OutputStream os = new BufferedOutputStream(sess.getStdin(), 512);
300: InputStream is = new BufferedInputStream(sess.getStdout(),
301: 40000);
302:
303: os.write(0x0);
304: os.flush();
305:
306: for (int i = 0; i < files.length; i++) {
307: LenNamePair lnp = null;
308:
309: while (true) {
310: int c = is.read();
311: if (c < 0)
312: throw new IOException(
313: "Remote scp terminated unexpectedly.");
314:
315: String line = receiveLine(is);
316:
317: if (c == 'T') {
318: /* Ignore modification times */
319:
320: continue;
321: }
322:
323: if ((c == 1) || (c == 2))
324: throw new IOException("Remote SCP error: " + line);
325:
326: if (c == 'C') {
327: lnp = parseCLine(line);
328: break;
329:
330: }
331: throw new IOException("Remote SCP error: " + ((char) c)
332: + line);
333: }
334:
335: os.write(0x0);
336: os.flush();
337:
338: File f = new File(target + File.separatorChar
339: + lnp.filename);
340: FileOutputStream fop = null;
341:
342: try {
343: fop = new FileOutputStream(f);
344:
345: long remain = lnp.length;
346:
347: while (remain > 0) {
348: int trans;
349: if (remain > buffer.length)
350: trans = buffer.length;
351: else
352: trans = (int) remain;
353:
354: int this _time_received = is.read(buffer, 0, trans);
355:
356: if (this _time_received < 0) {
357: throw new IOException(
358: "Remote scp terminated connection unexpectedly");
359: }
360:
361: fop.write(buffer, 0, this _time_received);
362:
363: remain -= this _time_received;
364: }
365: } finally {
366: if (fop != null)
367: fop.close();
368: }
369:
370: readResponse(is);
371:
372: os.write(0x0);
373: os.flush();
374: }
375: }
376:
377: /**
378: * Copy a local file to a remote directory, uses mode 0600 when creating
379: * the file on the remote side.
380: *
381: * @param localFile
382: * Path and name of local file.
383: * @param remoteTargetDirectory
384: * Remote target directory. Use an empty string to specify the default directory.
385: *
386: * @throws IOException
387: */
388: public void put(String localFile, String remoteTargetDirectory)
389: throws IOException {
390: put(new String[] { localFile }, remoteTargetDirectory, "0600");
391: }
392:
393: /**
394: * Copy a set of local files to a remote directory, uses mode 0600 when
395: * creating files on the remote side.
396: *
397: * @param localFiles
398: * Paths and names of local file names.
399: * @param remoteTargetDirectory
400: * Remote target directory. Use an empty string to specify the default directory.
401: *
402: * @throws IOException
403: */
404:
405: public void put(String[] localFiles, String remoteTargetDirectory)
406: throws IOException {
407: put(localFiles, remoteTargetDirectory, "0600");
408: }
409:
410: /**
411: * Copy a local file to a remote directory, uses the specified mode when
412: * creating the file on the remote side.
413: *
414: * @param localFile
415: * Path and name of local file.
416: * @param remoteTargetDirectory
417: * Remote target directory. Use an empty string to specify the default directory.
418: * @param mode
419: * a four digit string (e.g., 0644, see "man chmod", "man open")
420: * @throws IOException
421: */
422: public void put(String localFile, String remoteTargetDirectory,
423: String mode) throws IOException {
424: put(new String[] { localFile }, remoteTargetDirectory, mode);
425: }
426:
427: /**
428: * Copy a local file to a remote directory, uses the specified mode and remote filename
429: * when creating the file on the remote side.
430: *
431: * @param localFile
432: * Path and name of local file.
433: * @param remoteFileName
434: * The name of the file which will be created in the remote target directory.
435: * @param remoteTargetDirectory
436: * Remote target directory. Use an empty string to specify the default directory.
437: * @param mode
438: * a four digit string (e.g., 0644, see "man chmod", "man open")
439: * @throws IOException
440: */
441: public void put(String localFile, String remoteFileName,
442: String remoteTargetDirectory, String mode)
443: throws IOException {
444: put(new String[] { localFile },
445: new String[] { remoteFileName }, remoteTargetDirectory,
446: mode);
447: }
448:
449: /**
450: * Create a remote file and copy the contents of the passed byte array into it.
451: * Uses mode 0600 for creating the remote file.
452: *
453: * @param data
454: * the data to be copied into the remote file.
455: * @param remoteFileName
456: * The name of the file which will be created in the remote target directory.
457: * @param remoteTargetDirectory
458: * Remote target directory. Use an empty string to specify the default directory.
459: * @throws IOException
460: */
461:
462: public void put(byte[] data, String remoteFileName,
463: String remoteTargetDirectory) throws IOException {
464: put(data, remoteFileName, remoteTargetDirectory, "0600");
465: }
466:
467: /**
468: * Create a remote file and copy the contents of the passed byte array into it.
469: * The method use the specified mode when creating the file on the remote side.
470: *
471: * @param data
472: * the data to be copied into the remote file.
473: * @param remoteFileName
474: * The name of the file which will be created in the remote target directory.
475: * @param remoteTargetDirectory
476: * Remote target directory. Use an empty string to specify the default directory.
477: * @param mode
478: * a four digit string (e.g., 0644, see "man chmod", "man open")
479: * @throws IOException
480: */
481: public void put(byte[] data, String remoteFileName,
482: String remoteTargetDirectory, String mode)
483: throws IOException {
484: Session sess = null;
485:
486: if ((remoteFileName == null) || (remoteTargetDirectory == null)
487: || (mode == null))
488: throw new IllegalArgumentException("Null argument.");
489:
490: if (mode.length() != 4)
491: throw new IllegalArgumentException("Invalid mode.");
492:
493: for (int i = 0; i < mode.length(); i++)
494: if (Character.isDigit(mode.charAt(i)) == false)
495: throw new IllegalArgumentException("Invalid mode.");
496:
497: remoteTargetDirectory = remoteTargetDirectory.trim();
498: remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory
499: : ".";
500:
501: String cmd = "scp -t -d " + remoteTargetDirectory;
502:
503: try {
504: sess = conn.openSession();
505: sess.execCommand(cmd);
506: sendBytes(sess, data, remoteFileName, mode);
507: } catch (IOException e) {
508: throw (IOException) new IOException(
509: "Error during SCP transfer.").initCause(e);
510: } finally {
511: if (sess != null)
512: sess.close();
513: }
514: }
515:
516: /**
517: * Copy a set of local files to a remote directory, uses the specified mode
518: * when creating the files on the remote side.
519: *
520: * @param localFiles
521: * Paths and names of the local files.
522: * @param remoteTargetDirectory
523: * Remote target directory. Use an empty string to specify the default directory.
524: * @param mode
525: * a four digit string (e.g., 0644, see "man chmod", "man open")
526: * @throws IOException
527: */
528: public void put(String[] localFiles, String remoteTargetDirectory,
529: String mode) throws IOException {
530: put(localFiles, null, remoteTargetDirectory, mode);
531: }
532:
533: public void put(String[] localFiles, String[] remoteFiles,
534: String remoteTargetDirectory, String mode)
535: throws IOException {
536: Session sess = null;
537:
538: /* remoteFiles may be null, indicating that the local filenames shall be used */
539:
540: if ((localFiles == null) || (remoteTargetDirectory == null)
541: || (mode == null))
542: throw new IllegalArgumentException("Null argument.");
543:
544: if (mode.length() != 4)
545: throw new IllegalArgumentException("Invalid mode.");
546:
547: for (int i = 0; i < mode.length(); i++)
548: if (Character.isDigit(mode.charAt(i)) == false)
549: throw new IllegalArgumentException("Invalid mode.");
550:
551: if (localFiles.length == 0)
552: return;
553:
554: remoteTargetDirectory = remoteTargetDirectory.trim();
555: remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory
556: : ".";
557:
558: String cmd = "scp -t -d " + remoteTargetDirectory;
559:
560: for (int i = 0; i < localFiles.length; i++) {
561: if (localFiles[i] == null)
562: throw new IllegalArgumentException(
563: "Cannot accept null filename.");
564: }
565:
566: try {
567: sess = conn.openSession();
568: sess.execCommand(cmd);
569: sendFiles(sess, localFiles, remoteFiles, mode);
570: } catch (IOException e) {
571: throw (IOException) new IOException(
572: "Error during SCP transfer.").initCause(e);
573: } finally {
574: if (sess != null)
575: sess.close();
576: }
577: }
578:
579: /**
580: * Download a file from the remote server to a local directory.
581: *
582: * @param remoteFile
583: * Path and name of the remote file.
584: * @param localTargetDirectory
585: * Local directory to put the downloaded file.
586: *
587: * @throws IOException
588: */
589: public void get(String remoteFile, String localTargetDirectory)
590: throws IOException {
591: get(new String[] { remoteFile }, localTargetDirectory);
592: }
593:
594: /**
595: * Download a file from the remote server and pipe its contents into an <code>OutputStream</code>.
596: * Please note that, to enable flexible usage of this method, the <code>OutputStream</code> will not
597: * be closed nor flushed.
598: *
599: * @param remoteFile
600: * Path and name of the remote file.
601: * @param target
602: * OutputStream where the contents of the file will be sent to.
603: * @throws IOException
604: */
605: public void get(String remoteFile, OutputStream target)
606: throws IOException {
607: get(new String[] { remoteFile }, new OutputStream[] { target });
608: }
609:
610: private void get(String remoteFiles[], OutputStream[] targets)
611: throws IOException {
612: Session sess = null;
613:
614: if ((remoteFiles == null) || (targets == null))
615: throw new IllegalArgumentException("Null argument.");
616:
617: if (remoteFiles.length != targets.length)
618: throw new IllegalArgumentException(
619: "Length of arguments does not match.");
620:
621: if (remoteFiles.length == 0)
622: return;
623:
624: String cmd = "scp -f";
625:
626: for (int i = 0; i < remoteFiles.length; i++) {
627: if (remoteFiles[i] == null)
628: throw new IllegalArgumentException(
629: "Cannot accept null filename.");
630:
631: String tmp = remoteFiles[i].trim();
632:
633: if (tmp.length() == 0)
634: throw new IllegalArgumentException(
635: "Cannot accept empty filename.");
636:
637: cmd += (" " + tmp);
638: }
639:
640: try {
641: sess = conn.openSession();
642: sess.execCommand(cmd);
643: receiveFiles(sess, targets);
644: } catch (IOException e) {
645: throw (IOException) new IOException(
646: "Error during SCP transfer.").initCause(e);
647: } finally {
648: if (sess != null)
649: sess.close();
650: }
651: }
652:
653: /**
654: * Download a set of files from the remote server to a local directory.
655: *
656: * @param remoteFiles
657: * Paths and names of the remote files.
658: * @param localTargetDirectory
659: * Local directory to put the downloaded files.
660: *
661: * @throws IOException
662: */
663: public void get(String remoteFiles[], String localTargetDirectory)
664: throws IOException {
665: Session sess = null;
666:
667: if ((remoteFiles == null) || (localTargetDirectory == null))
668: throw new IllegalArgumentException("Null argument.");
669:
670: if (remoteFiles.length == 0)
671: return;
672:
673: String cmd = "scp -f";
674:
675: for (int i = 0; i < remoteFiles.length; i++) {
676: if (remoteFiles[i] == null)
677: throw new IllegalArgumentException(
678: "Cannot accept null filename.");
679:
680: String tmp = remoteFiles[i].trim();
681:
682: if (tmp.length() == 0)
683: throw new IllegalArgumentException(
684: "Cannot accept empty filename.");
685:
686: cmd += (" " + tmp);
687: }
688:
689: try {
690: sess = conn.openSession();
691: sess.execCommand(cmd);
692: receiveFiles(sess, remoteFiles, localTargetDirectory);
693: } catch (IOException e) {
694: throw (IOException) new IOException(
695: "Error during SCP transfer.").initCause(e);
696: } finally {
697: if (sess != null)
698: sess.close();
699: }
700: }
701: }
|