1 package org.apache.maven.wagon.providers.ftp;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.commons.io.IOUtils;
23 import org.apache.commons.net.ProtocolCommandEvent;
24 import org.apache.commons.net.ProtocolCommandListener;
25 import org.apache.commons.net.ftp.FTP;
26 import org.apache.commons.net.ftp.FTPClient;
27 import org.apache.commons.net.ftp.FTPFile;
28 import org.apache.commons.net.ftp.FTPReply;
29 import org.apache.maven.wagon.ConnectionException;
30 import org.apache.maven.wagon.InputData;
31 import org.apache.maven.wagon.OutputData;
32 import org.apache.maven.wagon.PathUtils;
33 import org.apache.maven.wagon.ResourceDoesNotExistException;
34 import org.apache.maven.wagon.StreamWagon;
35 import org.apache.maven.wagon.TransferFailedException;
36 import org.apache.maven.wagon.WagonConstants;
37 import org.apache.maven.wagon.authentication.AuthenticationException;
38 import org.apache.maven.wagon.authentication.AuthenticationInfo;
39 import org.apache.maven.wagon.authorization.AuthorizationException;
40 import org.apache.maven.wagon.repository.RepositoryPermissions;
41 import org.apache.maven.wagon.resource.Resource;
42
43 import java.io.File;
44 import java.io.FileInputStream;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.OutputStream;
48 import java.util.ArrayList;
49 import java.util.Calendar;
50 import java.util.List;
51
52
53
54
55
56
57
58
59
60 public class FtpWagon
61 extends StreamWagon
62 {
63 private FTPClient ftp;
64
65
66
67
68 private boolean passiveMode = true;
69
70
71
72
73 private String controlEncoding = FTP.DEFAULT_CONTROL_ENCODING;
74
75 public boolean isPassiveMode()
76 {
77 return passiveMode;
78 }
79
80 public void setPassiveMode( boolean passiveMode )
81 {
82 this.passiveMode = passiveMode;
83 }
84
85 protected void openConnectionInternal()
86 throws ConnectionException, AuthenticationException
87 {
88 AuthenticationInfo authInfo = getAuthenticationInfo();
89
90 if ( authInfo == null )
91 {
92 throw new NullPointerException( "authenticationInfo cannot be null" );
93 }
94
95 if ( authInfo.getUserName() == null )
96 {
97 authInfo.setUserName( System.getProperty( "user.name" ) );
98 }
99
100 String username = authInfo.getUserName();
101
102 String password = authInfo.getPassword();
103
104 if ( username == null )
105 {
106 throw new AuthenticationException( "Username not specified for repository " + getRepository().getId() );
107 }
108 if ( password == null )
109 {
110 throw new AuthenticationException( "Password not specified for repository " + getRepository().getId() );
111 }
112
113 String host = getRepository().getHost();
114
115 ftp = createClient();
116 ftp.setDefaultTimeout( getTimeout() );
117 ftp.setDataTimeout( getTimeout() );
118 ftp.setControlEncoding( getControlEncoding() );
119
120 ftp.addProtocolCommandListener( new PrintCommandListener( this ) );
121
122 try
123 {
124 if ( getRepository().getPort() != WagonConstants.UNKNOWN_PORT )
125 {
126 ftp.connect( host, getRepository().getPort() );
127 }
128 else
129 {
130 ftp.connect( host );
131 }
132
133
134
135
136 int reply = ftp.getReplyCode();
137
138 if ( !FTPReply.isPositiveCompletion( reply ) )
139 {
140 ftp.disconnect();
141
142 throw new AuthenticationException( "FTP server refused connection." );
143 }
144 }
145 catch ( IOException e )
146 {
147 if ( ftp.isConnected() )
148 {
149 try
150 {
151 fireSessionError( e );
152
153 ftp.disconnect();
154 }
155 catch ( IOException f )
156 {
157
158 }
159 }
160
161 throw new AuthenticationException( "Could not connect to server.", e );
162 }
163
164 try
165 {
166 if ( !ftp.login( username, password ) )
167 {
168 throw new AuthenticationException( "Cannot login to remote system" );
169 }
170
171 fireSessionDebug( "Remote system is " + ftp.getSystemName() );
172
173
174 ftp.setFileType( FTP.BINARY_FILE_TYPE );
175 ftp.setListHiddenFiles( true );
176
177
178
179 if ( isPassiveMode() )
180 {
181 ftp.enterLocalPassiveMode();
182 }
183 }
184 catch ( IOException e )
185 {
186 throw new ConnectionException( "Cannot login to remote system", e );
187 }
188 }
189
190 protected FTPClient createClient()
191 {
192 return new FTPClient();
193 }
194
195 protected void firePutCompleted( Resource resource, File file )
196 {
197 try
198 {
199
200 ftp.completePendingCommand();
201
202 RepositoryPermissions permissions = repository.getPermissions();
203
204 if ( permissions != null && permissions.getGroup() != null )
205 {
206
207 ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() + " " + resource.getName() );
208 }
209
210 if ( permissions != null && permissions.getFileMode() != null )
211 {
212
213 ftp.sendSiteCommand( "CHMOD " + permissions.getFileMode() + " " + resource.getName() );
214 }
215 }
216 catch ( IOException e )
217 {
218
219
220
221 }
222
223 super.firePutCompleted( resource, file );
224 }
225
226 protected void fireGetCompleted( Resource resource, File localFile )
227 {
228 try
229 {
230 ftp.completePendingCommand();
231 }
232 catch ( IOException e )
233 {
234
235
236
237
238 }
239 super.fireGetCompleted( resource, localFile );
240 }
241
242 public void closeConnection()
243 throws ConnectionException
244 {
245 if ( ftp != null && ftp.isConnected() )
246 {
247 try
248 {
249
250 ftp.disconnect();
251 }
252 catch ( IOException e )
253 {
254 throw new ConnectionException( "Failed to close connection to FTP repository", e );
255 }
256 }
257 }
258
259 public void fillOutputData( OutputData outputData )
260 throws TransferFailedException
261 {
262 OutputStream os;
263
264 Resource resource = outputData.getResource();
265
266 RepositoryPermissions permissions = repository.getPermissions();
267
268 try
269 {
270 if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
271 {
272 throw new TransferFailedException(
273 "Required directory: '" + getRepository().getBasedir() + "' " + "is missing" );
274 }
275
276 String[] dirs = PathUtils.dirnames( resource.getName() );
277
278 for ( String dir : dirs )
279 {
280 boolean dirChanged = ftp.changeWorkingDirectory( dir );
281
282 if ( !dirChanged )
283 {
284
285 boolean success = ftp.makeDirectory( dir );
286
287 if ( success )
288 {
289 if ( permissions != null && permissions.getGroup() != null )
290 {
291
292 ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() + " " + dir );
293 }
294
295 if ( permissions != null && permissions.getDirectoryMode() != null )
296 {
297
298 ftp.sendSiteCommand( "CHMOD " + permissions.getDirectoryMode() + " " + dir );
299 }
300
301 dirChanged = ftp.changeWorkingDirectory( dir );
302 }
303 }
304
305 if ( !dirChanged )
306 {
307 throw new TransferFailedException( "Unable to create directory " + dir );
308 }
309 }
310
311
312
313 if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
314 {
315 throw new TransferFailedException( "Unable to return to the base directory" );
316 }
317
318 os = ftp.storeFileStream( resource.getName() );
319
320 if ( os == null )
321 {
322 String msg =
323 "Cannot transfer resource: '" + resource + "'. Output stream is null. FTP Server response: "
324 + ftp.getReplyString();
325
326 throw new TransferFailedException( msg );
327
328 }
329
330 fireTransferDebug( "resource = " + resource );
331
332 }
333 catch ( IOException e )
334 {
335 throw new TransferFailedException( "Error transferring over FTP", e );
336 }
337
338 outputData.setOutputStream( os );
339
340 }
341
342
343
344
345
346 public void fillInputData( InputData inputData )
347 throws TransferFailedException, ResourceDoesNotExistException
348 {
349 InputStream is;
350
351 Resource resource = inputData.getResource();
352
353 try
354 {
355 ftpChangeDirectory( resource );
356
357 String filename = PathUtils.filename( resource.getName() );
358 FTPFile[] ftpFiles = ftp.listFiles( filename );
359
360 if ( ftpFiles == null || ftpFiles.length <= 0 )
361 {
362 throw new ResourceDoesNotExistException( "Could not find file: '" + resource + "'" );
363 }
364
365 long contentLength = ftpFiles[0].getSize();
366
367
368
369
370 Calendar timestamp = ftpFiles[0].getTimestamp();
371 long lastModified = timestamp != null ? timestamp.getTimeInMillis() : 0;
372
373 resource.setContentLength( contentLength );
374
375 resource.setLastModified( lastModified );
376
377 is = ftp.retrieveFileStream( filename );
378 }
379 catch ( IOException e )
380 {
381 throw new TransferFailedException( "Error transferring file via FTP", e );
382 }
383
384 inputData.setInputStream( is );
385 }
386
387 private void ftpChangeDirectory( Resource resource )
388 throws IOException, TransferFailedException, ResourceDoesNotExistException
389 {
390 if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
391 {
392 throw new ResourceDoesNotExistException(
393 "Required directory: '" + getRepository().getBasedir() + "' " + "is missing" );
394 }
395
396 String[] dirs = PathUtils.dirnames( resource.getName() );
397
398 for ( String dir : dirs )
399 {
400 boolean dirChanged = ftp.changeWorkingDirectory( dir );
401
402 if ( !dirChanged )
403 {
404 String msg = "Resource " + resource + " not found. Directory " + dir + " does not exist";
405
406 throw new ResourceDoesNotExistException( msg );
407 }
408 }
409 }
410
411
412
413
414 public class PrintCommandListener
415 implements ProtocolCommandListener
416 {
417 private FtpWagon wagon;
418
419 public PrintCommandListener( FtpWagon wagon )
420 {
421 this.wagon = wagon;
422 }
423
424 public void protocolCommandSent( ProtocolCommandEvent event )
425 {
426 wagon.fireSessionDebug( "Command sent: " + event.getMessage() );
427
428 }
429
430 public void protocolReplyReceived( ProtocolCommandEvent event )
431 {
432 wagon.fireSessionDebug( "Reply received: " + event.getMessage() );
433 }
434 }
435
436 protected void fireSessionDebug( String msg )
437 {
438 super.fireSessionDebug( msg );
439 }
440
441 public List<String> getFileList( String destinationDirectory )
442 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
443 {
444 Resource resource = new Resource( destinationDirectory );
445
446 try
447 {
448 ftpChangeDirectory( resource );
449
450 String filename = PathUtils.filename( resource.getName() );
451 FTPFile[] ftpFiles = ftp.listFiles( filename );
452
453 if ( ftpFiles == null || ftpFiles.length <= 0 )
454 {
455 throw new ResourceDoesNotExistException( "Could not find file: '" + resource + "'" );
456 }
457
458 List<String> ret = new ArrayList<String>();
459 for ( FTPFile file : ftpFiles )
460 {
461 String name = file.getName();
462
463 if ( file.isDirectory() && !name.endsWith( "/" ) )
464 {
465 name += "/";
466 }
467
468 ret.add( name );
469 }
470
471 return ret;
472 }
473 catch ( IOException e )
474 {
475 throw new TransferFailedException( "Error transferring file via FTP", e );
476 }
477 }
478
479 public boolean resourceExists( String resourceName )
480 throws TransferFailedException, AuthorizationException
481 {
482 Resource resource = new Resource( resourceName );
483
484 try
485 {
486 ftpChangeDirectory( resource );
487
488 String filename = PathUtils.filename( resource.getName() );
489 int status = ftp.stat( filename );
490
491 return ( ( status == FTPReply.FILE_STATUS ) || ( status == FTPReply.DIRECTORY_STATUS ) || ( status
492 == FTPReply.FILE_STATUS_OK )
493 || ( status == FTPReply.COMMAND_OK )
494 || ( status == FTPReply.SYSTEM_STATUS ) );
495 }
496 catch ( IOException e )
497 {
498 throw new TransferFailedException( "Error transferring file via FTP", e );
499 }
500 catch ( ResourceDoesNotExistException e )
501 {
502 return false;
503 }
504 }
505
506 public boolean supportsDirectoryCopy()
507 {
508 return true;
509 }
510
511 public void putDirectory( File sourceDirectory, String destinationDirectory )
512 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
513 {
514
515
516 try
517 {
518 if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
519 {
520 RepositoryPermissions permissions = getRepository().getPermissions();
521 if ( !makeFtpDirectoryRecursive( getRepository().getBasedir(), permissions ) )
522 {
523 throw new TransferFailedException(
524 "Required directory: '" + getRepository().getBasedir() + "' " + "could not get created" );
525 }
526
527
528 if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
529 {
530 throw new TransferFailedException( "Required directory: '" + getRepository().getBasedir() + "' "
531 + "is missing and could not get created" );
532 }
533 }
534 }
535 catch ( IOException e )
536 {
537 throw new TransferFailedException( "Cannot change to root path " + getRepository().getBasedir(), e );
538 }
539
540 fireTransferDebug(
541 "Recursively uploading directory " + sourceDirectory.getAbsolutePath() + " as " + destinationDirectory );
542 ftpRecursivePut( sourceDirectory, destinationDirectory );
543 }
544
545 private void ftpRecursivePut( File sourceFile, String fileName )
546 throws TransferFailedException
547 {
548 final RepositoryPermissions permissions = repository.getPermissions();
549
550 fireTransferDebug( "processing = " + sourceFile.getAbsolutePath() + " as " + fileName );
551
552 if ( sourceFile.isDirectory() )
553 {
554 if ( !fileName.equals( "." ) )
555 {
556 try
557 {
558
559 if ( !ftp.changeWorkingDirectory( fileName ) )
560 {
561
562 if ( makeFtpDirectoryRecursive( fileName, permissions ) )
563 {
564 if ( !ftp.changeWorkingDirectory( fileName ) )
565 {
566 throw new TransferFailedException(
567 "Unable to change cwd on ftp server to " + fileName + " when processing "
568 + sourceFile.getAbsolutePath() );
569 }
570 }
571 else
572 {
573 throw new TransferFailedException(
574 "Unable to create directory " + fileName + " when processing "
575 + sourceFile.getAbsolutePath() );
576 }
577 }
578 }
579 catch ( IOException e )
580 {
581 throw new TransferFailedException(
582 "IOException caught while processing path at " + sourceFile.getAbsolutePath(), e );
583 }
584 }
585
586 File[] files = sourceFile.listFiles();
587 if ( files != null && files.length > 0 )
588 {
589 fireTransferDebug( "listing children of = " + sourceFile.getAbsolutePath() + " found " + files.length );
590
591
592 for ( File file : files )
593 {
594 if ( file.isDirectory() )
595 {
596 ftpRecursivePut( file, file.getName() );
597 }
598 }
599 for ( File file : files )
600 {
601 if ( !file.isDirectory() )
602 {
603 ftpRecursivePut( file, file.getName() );
604 }
605 }
606 }
607
608
609 try
610 {
611 ftp.changeToParentDirectory();
612 }
613 catch ( IOException e )
614 {
615 throw new TransferFailedException( "IOException caught while attempting to step up to parent directory"
616 + " after successfully processing "
617 + sourceFile.getAbsolutePath(), e );
618 }
619 }
620 else
621 {
622
623
624 FileInputStream sourceFileStream = null;
625 try
626 {
627 sourceFileStream = new FileInputStream( sourceFile );
628
629
630 if ( ftp.storeFile( fileName, sourceFileStream ) )
631 {
632 if ( permissions != null )
633 {
634
635
636 String group = permissions.getGroup();
637 if ( group != null )
638 {
639 try
640 {
641 ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() );
642 }
643 catch ( IOException e )
644 {
645
646 }
647 }
648 String mode = permissions.getFileMode();
649 if ( mode != null )
650 {
651 try
652 {
653 ftp.sendSiteCommand( "CHMOD " + permissions.getDirectoryMode() );
654 }
655 catch ( IOException e )
656 {
657
658 }
659 }
660 }
661 }
662 else
663 {
664 String msg =
665 "Cannot transfer resource: '" + sourceFile.getAbsolutePath() + "' FTP Server response: "
666 + ftp.getReplyString();
667 throw new TransferFailedException( msg );
668 }
669
670 sourceFileStream.close();
671 sourceFileStream = null;
672 }
673 catch ( IOException e )
674 {
675 throw new TransferFailedException(
676 "IOException caught while attempting to upload " + sourceFile.getAbsolutePath(), e );
677 }
678 finally
679 {
680 IOUtils.closeQuietly( sourceFileStream );
681 }
682
683 }
684
685 fireTransferDebug( "completed = " + sourceFile.getAbsolutePath() );
686 }
687
688
689
690
691
692
693
694
695
696 private void setPermissions( RepositoryPermissions permissions )
697 {
698 if ( permissions != null )
699 {
700
701
702 String group = permissions.getGroup();
703 if ( group != null )
704 {
705 try
706 {
707 ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() );
708 }
709 catch ( IOException e )
710 {
711
712 }
713 }
714 String mode = permissions.getDirectoryMode();
715 if ( mode != null )
716 {
717 try
718 {
719 ftp.sendSiteCommand( "CHMOD " + permissions.getDirectoryMode() );
720 }
721 catch ( IOException e )
722 {
723
724 }
725 }
726 }
727 }
728
729
730
731
732
733
734
735
736
737 private boolean makeFtpDirectoryRecursive( String fileName, RepositoryPermissions permissions )
738 throws IOException
739 {
740 if ( fileName == null || fileName.length() == 0
741 || fileName.replace( '/', '_' ).trim().length() == 0 )
742 {
743 return false;
744 }
745
746 int slashPos = fileName.indexOf( "/" );
747 String oldPwd = null;
748 boolean ok = true;
749
750 if ( slashPos == 0 )
751 {
752
753 oldPwd = ftp.printWorkingDirectory();
754
755
756 ftp.changeWorkingDirectory( "/" );
757 fileName = fileName.substring( 1 );
758
759
760 slashPos = fileName.indexOf( "/" );
761 }
762
763 if ( slashPos >= 0 && slashPos < ( fileName.length() - 1 ) )
764 {
765 if ( oldPwd == null )
766 {
767 oldPwd = ftp.printWorkingDirectory();
768 }
769
770 String nextDir = fileName.substring( 0, slashPos );
771
772
773 if ( !ftp.changeWorkingDirectory( nextDir ) )
774 {
775 ok &= ftp.makeDirectory( nextDir );
776 }
777
778 if ( ok )
779 {
780
781 setPermissions( permissions );
782
783 ftp.changeWorkingDirectory( nextDir );
784
785
786 String remainingDirs = fileName.substring( slashPos + 1 );
787 ok &= makeFtpDirectoryRecursive( remainingDirs, permissions );
788 }
789 }
790 else
791 {
792 ok = ftp.makeDirectory( fileName );
793 }
794
795 if ( oldPwd != null )
796 {
797
798 ftp.changeWorkingDirectory( oldPwd );
799 }
800
801 return ok;
802 }
803
804 public String getControlEncoding()
805 {
806 return controlEncoding;
807 }
808
809 public void setControlEncoding( String controlEncoding )
810 {
811 this.controlEncoding = controlEncoding;
812 }
813 }