1 package org.apache.maven.wagon.providers.ssh;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.sshd.common.util.DirectoryScanner;
23 import org.apache.sshd.server.Command;
24 import org.apache.sshd.server.Environment;
25 import org.apache.sshd.server.ExitCallback;
26 import org.apache.sshd.server.FileSystemAware;
27 import org.apache.sshd.server.FileSystemView;
28 import org.apache.sshd.server.SshFile;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import java.io.ByteArrayOutputStream;
33 import java.io.EOFException;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.OutputStream;
37 import java.util.Arrays;
38
39
40
41
42
43
44
45
46
47 public class ScpCommand
48 implements Command, Runnable, FileSystemAware
49 {
50
51 protected static final Logger log = LoggerFactory.getLogger( ScpCommand.class );
52
53 protected static final int OK = 0;
54
55 protected static final int WARNING = 1;
56
57 protected static final int ERROR = 2;
58
59 protected String name;
60
61 protected boolean optR;
62
63 protected boolean optT;
64
65 protected boolean optF;
66
67 protected boolean optV;
68
69 protected boolean optD;
70
71 protected boolean optP;
72
73 protected FileSystemView root;
74
75 protected String path;
76
77 protected InputStream in;
78
79 protected OutputStream out;
80
81 protected OutputStream err;
82
83 protected ExitCallback callback;
84
85 protected IOException error;
86
87 public ScpCommand( String[] args )
88 {
89 name = Arrays.asList( args ).toString();
90 if ( log.isDebugEnabled() )
91 {
92 log.debug( "Executing command {}", name );
93 }
94 path = ".";
95 for ( int i = 1; i < args.length; i++ )
96 {
97 if ( args[i].charAt( 0 ) == '-' )
98 {
99 for ( int j = 1; j < args[i].length(); j++ )
100 {
101 switch ( args[i].charAt( j ) )
102 {
103 case 'f':
104 optF = true;
105 break;
106 case 'p':
107 optP = true;
108 break;
109 case 'r':
110 optR = true;
111 break;
112 case 't':
113 optT = true;
114 break;
115 case 'v':
116 optV = true;
117 break;
118 case 'd':
119 optD = true;
120 break;
121
122
123
124 }
125 }
126 }
127 else if ( i == args.length - 1 )
128 {
129 path = args[args.length - 1];
130 }
131 }
132 if ( !optF && !optT )
133 {
134 error = new IOException( "Either -f or -t option should be set" );
135 }
136 }
137
138 public void setInputStream( InputStream in )
139 {
140 this.in = in;
141 }
142
143 public void setOutputStream( OutputStream out )
144 {
145 this.out = out;
146 }
147
148 public void setErrorStream( OutputStream err )
149 {
150 this.err = err;
151 }
152
153 public void setExitCallback( ExitCallback callback )
154 {
155 this.callback = callback;
156 }
157
158 public void start( Environment env )
159 throws IOException
160 {
161 if ( error != null )
162 {
163 throw error;
164 }
165 new Thread( this, "ScpCommand: " + name ).start();
166 }
167
168 public void destroy()
169 {
170 }
171
172 public void run()
173 {
174 int exitValue = OK;
175 String exitMessage = null;
176
177 try
178 {
179 if ( optT )
180 {
181 ack();
182 for (; ; )
183 {
184 String line;
185 boolean isDir = false;
186 int c = readAck( true );
187 switch ( c )
188 {
189 case -1:
190 return;
191 case 'D':
192 isDir = true;
193 case 'C':
194 line = ( (char) c ) + readLine();
195 break;
196 case 'E':
197 readLine();
198 return;
199 default:
200
201 continue;
202 }
203
204 if ( optR && isDir )
205 {
206 writeDir( line, root.getFile( path ) );
207 }
208 else
209 {
210 writeFile( line, root.getFile( path ) );
211 }
212 }
213 }
214 else if ( optF )
215 {
216 String pattern = path;
217 int idx = pattern.indexOf( '*' );
218 if ( idx >= 0 )
219 {
220 String basedir = "";
221 int lastSep = pattern.substring( 0, idx ).lastIndexOf( '/' );
222 if ( lastSep >= 0 )
223 {
224 basedir = pattern.substring( 0, lastSep );
225 pattern = pattern.substring( lastSep + 1 );
226 }
227 String[] included = new DirectoryScanner( basedir, pattern ).scan();
228 for ( String path : included )
229 {
230 SshFile file = root.getFile( basedir + "/" + path );
231 if ( file.isFile() )
232 {
233 readFile( file );
234 }
235 else if ( file.isDirectory() )
236 {
237 if ( !optR )
238 {
239 out.write( WARNING );
240 out.write( ( path + " not a regular file\n" ).getBytes() );
241 }
242 else
243 {
244 readDir( file );
245 }
246 }
247 else
248 {
249 out.write( WARNING );
250 out.write( ( path + " unknown file type\n" ).getBytes() );
251 }
252 }
253 }
254 else
255 {
256 String basedir = "";
257 int lastSep = pattern.lastIndexOf( '/' );
258 if ( lastSep >= 0 )
259 {
260 basedir = pattern.substring( 0, lastSep );
261 pattern = pattern.substring( lastSep + 1 );
262 }
263 SshFile file = root.getFile( basedir + "/" + pattern );
264 if ( !file.doesExist() )
265 {
266 exitValue = WARNING;
267 throw new IOException( file + ": no such file or directory" );
268 }
269 if ( file.isFile() )
270 {
271 readFile( file );
272 }
273 else if ( file.isDirectory() )
274 {
275 if ( !optR )
276 {
277 throw new IOException( file + " not a regular file" );
278 }
279 else
280 {
281 readDir( file );
282 }
283 }
284 else
285 {
286 throw new IOException( file + ": unknown file type" );
287 }
288 }
289 }
290 else
291 {
292 throw new IOException( "Unsupported mode" );
293 }
294 }
295 catch ( IOException e )
296 {
297 try
298 {
299 exitValue = ( exitValue != OK ? exitValue : ERROR );
300 exitMessage = e.getMessage();
301 out.write( exitValue );
302 out.write( exitMessage.getBytes() );
303 out.write( '\n' );
304 out.flush();
305 }
306 catch ( IOException e2 )
307 {
308
309 }
310 log.info( "Error in scp command", e );
311 }
312 finally
313 {
314 if ( callback != null )
315 {
316 callback.onExit( exitValue, exitMessage );
317 }
318 }
319 }
320
321 protected void writeDir( String header, SshFile path )
322 throws IOException
323 {
324 if ( log.isDebugEnabled() )
325 {
326 log.debug( "Writing dir {}", path );
327 }
328 if ( !header.startsWith( "D" ) )
329 {
330 throw new IOException( "Expected a D message but got '" + header + "'" );
331 }
332
333 String perms = header.substring( 1, 5 );
334 int length = Integer.parseInt( header.substring( 6, header.indexOf( ' ', 6 ) ) );
335 String name = header.substring( header.indexOf( ' ', 6 ) + 1 );
336
337 if ( length != 0 )
338 {
339 throw new IOException( "Expected 0 length for directory but got " + length );
340 }
341 SshFile file;
342 if ( path.doesExist() && path.isDirectory() )
343 {
344 file = root.getFile( path, name );
345 }
346 else if ( !path.doesExist() && path.getParentFile().doesExist() && path.getParentFile().isDirectory() )
347 {
348 file = path;
349 }
350 else
351 {
352 throw new IOException( "Can not write to " + path );
353 }
354 if ( !( file.doesExist() && file.isDirectory() ) && !file.mkdir() )
355 {
356 throw new IOException( "Could not create directory " + file );
357 }
358
359 ack();
360
361 for (; ; )
362 {
363 header = readLine();
364 if ( header.startsWith( "C" ) )
365 {
366 writeFile( header, file );
367 }
368 else if ( header.startsWith( "D" ) )
369 {
370 writeDir( header, file );
371 }
372 else if ( header.equals( "E" ) )
373 {
374 ack();
375 break;
376 }
377 else
378 {
379 throw new IOException( "Unexpected message: '" + header + "'" );
380 }
381 }
382
383 }
384
385 protected void writeFile( String header, SshFile path )
386 throws IOException
387 {
388 if ( log.isDebugEnabled() )
389 {
390 log.debug( "Writing file {}", path );
391 }
392 if ( !header.startsWith( "C" ) )
393 {
394 throw new IOException( "Expected a C message but got '" + header + "'" );
395 }
396
397 String perms = header.substring( 1, 5 );
398 long length = Long.parseLong( header.substring( 6, header.indexOf( ' ', 6 ) ) );
399 String name = header.substring( header.indexOf( ' ', 6 ) + 1 );
400
401 SshFile file;
402 if ( path.doesExist() && path.isDirectory() )
403 {
404 file = root.getFile( path, name );
405 }
406 else if ( path.doesExist() && path.isFile() )
407 {
408 file = path;
409 }
410 else if ( !path.doesExist() && path.getParentFile().doesExist() && path.getParentFile().isDirectory() )
411 {
412 file = path;
413 }
414 else
415 {
416 throw new IOException( "Can not write to " + path );
417 }
418 if ( file.doesExist() && file.isDirectory() )
419 {
420 throw new IOException( "File is a directory: " + file );
421 }
422 else if ( file.doesExist() && !file.isWritable() )
423 {
424 throw new IOException( "Can not write to file: " + file );
425 }
426 OutputStream os = file.createOutputStream( 0 );
427 try
428 {
429 ack();
430
431 byte[] buffer = new byte[8192];
432 while ( length > 0 )
433 {
434 int len = (int) Math.min( length, buffer.length );
435 len = in.read( buffer, 0, len );
436 if ( len <= 0 )
437 {
438 throw new IOException( "End of stream reached" );
439 }
440 os.write( buffer, 0, len );
441 length -= len;
442 }
443 }
444 finally
445 {
446 os.close();
447 }
448
449 ack();
450 readAck( false );
451 }
452
453 protected String readLine()
454 throws IOException
455 {
456 ByteArrayOutputStream baos = new ByteArrayOutputStream();
457 for (; ; )
458 {
459 int c = in.read();
460 if ( c == '\n' )
461 {
462 return baos.toString();
463 }
464 else if ( c == -1 )
465 {
466 throw new IOException( "End of stream" );
467 }
468 else
469 {
470 baos.write( c );
471 }
472 }
473 }
474
475 protected void readFile( SshFile path )
476 throws IOException
477 {
478 if ( log.isDebugEnabled() )
479 {
480 log.debug( "Reading file {}", path );
481 }
482 StringBuilder buf = new StringBuilder();
483 buf.append( "C" );
484 buf.append( "0644" );
485 buf.append( " " );
486 buf.append( path.getSize() );
487 buf.append( " " );
488 buf.append( path.getName() );
489 buf.append( "\n" );
490 out.write( buf.toString().getBytes() );
491 out.flush();
492 readAck( false );
493
494 InputStream is = path.createInputStream( 0 );
495 try
496 {
497 byte[] buffer = new byte[8192];
498 for (; ; )
499 {
500 int len = is.read( buffer, 0, buffer.length );
501 if ( len == -1 )
502 {
503 break;
504 }
505 out.write( buffer, 0, len );
506 }
507 }
508 finally
509 {
510 is.close();
511 }
512 ack();
513 readAck( false );
514 }
515
516 protected void readDir( SshFile path )
517 throws IOException
518 {
519 if ( log.isDebugEnabled() )
520 {
521 log.debug( "Reading directory {}", path );
522 }
523 StringBuilder buf = new StringBuilder();
524 buf.append( "D" );
525 buf.append( "0755" );
526 buf.append( " " );
527 buf.append( "0" );
528 buf.append( " " );
529 buf.append( path.getName() );
530 buf.append( "\n" );
531 out.write( buf.toString().getBytes() );
532 out.flush();
533 readAck( false );
534
535 for ( SshFile child : path.listSshFiles() )
536 {
537 if ( child.isFile() )
538 {
539 readFile( child );
540 }
541 else if ( child.isDirectory() )
542 {
543 readDir( child );
544 }
545 }
546
547 out.write( "E\n".getBytes() );
548 out.flush();
549 readAck( false );
550 }
551
552 protected void ack()
553 throws IOException
554 {
555 out.write( 0 );
556 out.flush();
557 }
558
559 protected int readAck( boolean canEof )
560 throws IOException
561 {
562 int c = in.read();
563 switch ( c )
564 {
565 case -1:
566 if ( !canEof )
567 {
568 throw new EOFException();
569 }
570 break;
571 case OK:
572 break;
573 case WARNING:
574 log.warn( "Received warning: " + readLine() );
575 break;
576 case ERROR:
577 throw new IOException( "Received nack: " + readLine() );
578 default:
579 break;
580 }
581 return c;
582 }
583
584 public void setFileSystemView( FileSystemView view )
585 {
586 this.root = view;
587 }
588 }