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 IllegalArgumentException( "Authentication Credentials cannot be null for FTP protocol" );
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 = new FTPClient();
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 void firePutCompleted( Resource resource, File file )
191 {
192 try
193 {
194
195 ftp.completePendingCommand();
196
197 RepositoryPermissions permissions = repository.getPermissions();
198
199 if ( permissions != null && permissions.getGroup() != null )
200 {
201
202 ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() + " " + resource.getName() );
203 }
204
205 if ( permissions != null && permissions.getFileMode() != null )
206 {
207
208 ftp.sendSiteCommand( "CHMOD " + permissions.getFileMode() + " " + resource.getName() );
209 }
210 }
211 catch ( IOException e )
212 {
213
214
215
216 }
217
218 super.firePutCompleted( resource, file );
219 }
220
221 protected void fireGetCompleted( Resource resource, File localFile )
222 {
223 try
224 {
225 ftp.completePendingCommand();
226 }
227 catch ( IOException e )
228 {
229
230
231
232
233 }
234 super.fireGetCompleted( resource, localFile );
235 }
236
237 public void closeConnection()
238 throws ConnectionException
239 {
240 if ( ftp != null && ftp.isConnected() )
241 {
242 try
243 {
244
245 ftp.disconnect();
246 }
247 catch ( IOException e )
248 {
249 throw new ConnectionException( "Failed to close connection to FTP repository", e );
250 }
251 }
252 }
253
254 public void fillOutputData( OutputData outputData )
255 throws TransferFailedException
256 {
257 OutputStream os;
258
259 Resource resource = outputData.getResource();
260
261 RepositoryPermissions permissions = repository.getPermissions();
262
263 try
264 {
265 if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
266 {
267 throw new TransferFailedException(
268 "Required directory: '" + getRepository().getBasedir() + "' " + "is missing" );
269 }
270
271 String[] dirs = PathUtils.dirnames( resource.getName() );
272
273 for ( String dir : dirs )
274 {
275 boolean dirChanged = ftp.changeWorkingDirectory( dir );
276
277 if ( !dirChanged )
278 {
279
280 boolean success = ftp.makeDirectory( dir );
281
282 if ( success )
283 {
284 if ( permissions != null && permissions.getGroup() != null )
285 {
286
287 ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() + " " + dir );
288 }
289
290 if ( permissions != null && permissions.getDirectoryMode() != null )
291 {
292
293 ftp.sendSiteCommand( "CHMOD " + permissions.getDirectoryMode() + " " + dir );
294 }
295
296 dirChanged = ftp.changeWorkingDirectory( dir );
297 }
298 }
299
300 if ( !dirChanged )
301 {
302 throw new TransferFailedException( "Unable to create directory " + dir );
303 }
304 }
305
306
307
308 if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
309 {
310 throw new TransferFailedException( "Unable to return to the base directory" );
311 }
312
313 os = ftp.storeFileStream( resource.getName() );
314
315 if ( os == null )
316 {
317 String msg =
318 "Cannot transfer resource: '" + resource + "'. Output stream is null. FTP Server response: "
319 + ftp.getReplyString();
320
321 throw new TransferFailedException( msg );
322
323 }
324
325 fireTransferDebug( "resource = " + resource );
326
327 }
328 catch ( IOException e )
329 {
330 throw new TransferFailedException( "Error transferring over FTP", e );
331 }
332
333 outputData.setOutputStream( os );
334
335 }
336
337
338
339
340
341 public void fillInputData( InputData inputData )
342 throws TransferFailedException, ResourceDoesNotExistException
343 {
344 InputStream is;
345
346 Resource resource = inputData.getResource();
347
348 try
349 {
350 ftpChangeDirectory( resource );
351
352 String filename = PathUtils.filename( resource.getName() );
353 FTPFile[] ftpFiles = ftp.listFiles( filename );
354
355 if ( ftpFiles == null || ftpFiles.length <= 0 )
356 {
357 throw new ResourceDoesNotExistException( "Could not find file: '" + resource + "'" );
358 }
359
360 long contentLength = ftpFiles[0].getSize();
361
362
363
364
365 Calendar timestamp = ftpFiles[0].getTimestamp();
366 long lastModified = timestamp != null ? timestamp.getTimeInMillis() : 0;
367
368 resource.setContentLength( contentLength );
369
370 resource.setLastModified( lastModified );
371
372 is = ftp.retrieveFileStream( filename );
373 }
374 catch ( IOException e )
375 {
376 throw new TransferFailedException( "Error transferring file via FTP", e );
377 }
378
379 inputData.setInputStream( is );
380 }
381
382 private void ftpChangeDirectory( Resource resource )
383 throws IOException, TransferFailedException, ResourceDoesNotExistException
384 {
385 if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
386 {
387 throw new ResourceDoesNotExistException(
388 "Required directory: '" + getRepository().getBasedir() + "' " + "is missing" );
389 }
390
391 String[] dirs = PathUtils.dirnames( resource.getName() );
392
393 for ( String dir : dirs )
394 {
395 boolean dirChanged = ftp.changeWorkingDirectory( dir );
396
397 if ( !dirChanged )
398 {
399 String msg = "Resource " + resource + " not found. Directory " + dir + " does not exist";
400
401 throw new ResourceDoesNotExistException( msg );
402 }
403 }
404 }
405
406
407
408
409 public class PrintCommandListener
410 implements ProtocolCommandListener
411 {
412 private FtpWagon wagon;
413
414 public PrintCommandListener( FtpWagon wagon )
415 {
416 this.wagon = wagon;
417 }
418
419 public void protocolCommandSent( ProtocolCommandEvent event )
420 {
421 wagon.fireSessionDebug( "Command sent: " + event.getMessage() );
422
423 }
424
425 public void protocolReplyReceived( ProtocolCommandEvent event )
426 {
427 wagon.fireSessionDebug( "Reply received: " + event.getMessage() );
428 }
429 }
430
431 protected void fireSessionDebug( String msg )
432 {
433 super.fireSessionDebug( msg );
434 }
435
436 public List<String> getFileList( String destinationDirectory )
437 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
438 {
439 Resource resource = new Resource( destinationDirectory );
440
441 try
442 {
443 ftpChangeDirectory( resource );
444
445 String filename = PathUtils.filename( resource.getName() );
446 FTPFile[] ftpFiles = ftp.listFiles( filename );
447
448 if ( ftpFiles == null || ftpFiles.length <= 0 )
449 {
450 throw new ResourceDoesNotExistException( "Could not find file: '" + resource + "'" );
451 }
452
453 List<String> ret = new ArrayList<String>();
454 for ( FTPFile file : ftpFiles )
455 {
456 String name = file.getName();
457
458 if ( file.isDirectory() && !name.endsWith( "/" ) )
459 {
460 name += "/";
461 }
462
463 ret.add( name );
464 }
465
466 return ret;
467 }
468 catch ( IOException e )
469 {
470 throw new TransferFailedException( "Error transferring file via FTP", e );
471 }
472 }
473
474 public boolean resourceExists( String resourceName )
475 throws TransferFailedException, AuthorizationException
476 {
477 Resource resource = new Resource( resourceName );
478
479 try
480 {
481 ftpChangeDirectory( resource );
482
483 String filename = PathUtils.filename( resource.getName() );
484 int status = ftp.stat( filename );
485
486 return ( ( status == FTPReply.FILE_STATUS ) || ( status == FTPReply.DIRECTORY_STATUS ) || ( status
487 == FTPReply.FILE_STATUS_OK )
488 || ( status == FTPReply.COMMAND_OK )
489 || ( status == FTPReply.SYSTEM_STATUS ) );
490 }
491 catch ( IOException e )
492 {
493 throw new TransferFailedException( "Error transferring file via FTP", e );
494 }
495 catch ( ResourceDoesNotExistException e )
496 {
497 return false;
498 }
499 }
500
501 public boolean supportsDirectoryCopy()
502 {
503 return true;
504 }
505
506 public void putDirectory( File sourceDirectory, String destinationDirectory )
507 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
508 {
509
510
511 try
512 {
513 if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
514 {
515 RepositoryPermissions permissions = getRepository().getPermissions();
516 if ( !makeFtpDirectoryRecursive( getRepository().getBasedir(), permissions ) )
517 {
518 throw new TransferFailedException(
519 "Required directory: '" + getRepository().getBasedir() + "' " + "could not get created" );
520 }
521
522
523 if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
524 {
525 throw new TransferFailedException( "Required directory: '" + getRepository().getBasedir() + "' "
526 + "is missing and could not get created" );
527 }
528 }
529 }
530 catch ( IOException e )
531 {
532 throw new TransferFailedException( "Cannot change to root path " + getRepository().getBasedir(), e );
533 }
534
535 fireTransferDebug(
536 "Recursively uploading directory " + sourceDirectory.getAbsolutePath() + " as " + destinationDirectory );
537 ftpRecursivePut( sourceDirectory, destinationDirectory );
538 }
539
540 private void ftpRecursivePut( File sourceFile, String fileName )
541 throws TransferFailedException
542 {
543 final RepositoryPermissions permissions = repository.getPermissions();
544
545 fireTransferDebug( "processing = " + sourceFile.getAbsolutePath() + " as " + fileName );
546
547 if ( sourceFile.isDirectory() )
548 {
549 if ( !fileName.equals( "." ) )
550 {
551 try
552 {
553
554 if ( !ftp.changeWorkingDirectory( fileName ) )
555 {
556
557 if ( makeFtpDirectoryRecursive( fileName, permissions ) )
558 {
559 if ( !ftp.changeWorkingDirectory( fileName ) )
560 {
561 throw new TransferFailedException(
562 "Unable to change cwd on ftp server to " + fileName + " when processing "
563 + sourceFile.getAbsolutePath() );
564 }
565 }
566 else
567 {
568 throw new TransferFailedException(
569 "Unable to create directory " + fileName + " when processing "
570 + sourceFile.getAbsolutePath() );
571 }
572 }
573 }
574 catch ( IOException e )
575 {
576 throw new TransferFailedException(
577 "IOException caught while processing path at " + sourceFile.getAbsolutePath(), e );
578 }
579 }
580
581 File[] files = sourceFile.listFiles();
582 if ( files != null && files.length > 0 )
583 {
584 fireTransferDebug( "listing children of = " + sourceFile.getAbsolutePath() + " found " + files.length );
585
586
587 for ( File file : files )
588 {
589 if ( file.isDirectory() )
590 {
591 ftpRecursivePut( file, file.getName() );
592 }
593 }
594 for ( File file : files )
595 {
596 if ( !file.isDirectory() )
597 {
598 ftpRecursivePut( file, file.getName() );
599 }
600 }
601 }
602
603
604 try
605 {
606 ftp.changeToParentDirectory();
607 }
608 catch ( IOException e )
609 {
610 throw new TransferFailedException( "IOException caught while attempting to step up to parent directory"
611 + " after successfully processing "
612 + sourceFile.getAbsolutePath(), e );
613 }
614 }
615 else
616 {
617
618
619 FileInputStream sourceFileStream = null;
620 try
621 {
622 sourceFileStream = new FileInputStream( sourceFile );
623
624
625 if ( ftp.storeFile( fileName, sourceFileStream ) )
626 {
627 if ( permissions != null )
628 {
629
630
631 String group = permissions.getGroup();
632 if ( group != null )
633 {
634 try
635 {
636 ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() );
637 }
638 catch ( IOException e )
639 {
640
641 }
642 }
643 String mode = permissions.getFileMode();
644 if ( mode != null )
645 {
646 try
647 {
648 ftp.sendSiteCommand( "CHMOD " + permissions.getDirectoryMode() );
649 }
650 catch ( IOException e )
651 {
652
653 }
654 }
655 }
656 }
657 else
658 {
659 String msg =
660 "Cannot transfer resource: '" + sourceFile.getAbsolutePath() + "' FTP Server response: "
661 + ftp.getReplyString();
662 throw new TransferFailedException( msg );
663 }
664 }
665 catch ( IOException e )
666 {
667 throw new TransferFailedException(
668 "IOException caught while attempting to upload " + sourceFile.getAbsolutePath(), e );
669 }
670 finally
671 {
672 IOUtils.closeQuietly( sourceFileStream );
673 }
674
675 }
676
677 fireTransferDebug( "completed = " + sourceFile.getAbsolutePath() );
678 }
679
680
681
682
683
684
685
686
687
688 private void setPermissions( RepositoryPermissions permissions )
689 {
690 if ( permissions != null )
691 {
692
693
694 String group = permissions.getGroup();
695 if ( group != null )
696 {
697 try
698 {
699 ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() );
700 }
701 catch ( IOException e )
702 {
703
704 }
705 }
706 String mode = permissions.getDirectoryMode();
707 if ( mode != null )
708 {
709 try
710 {
711 ftp.sendSiteCommand( "CHMOD " + permissions.getDirectoryMode() );
712 }
713 catch ( IOException e )
714 {
715
716 }
717 }
718 }
719 }
720
721
722
723
724
725
726
727
728
729 private boolean makeFtpDirectoryRecursive( String fileName, RepositoryPermissions permissions )
730 throws IOException
731 {
732 if ( fileName == null || fileName.length() == 0
733 || fileName.replace( '/', '_' ).trim().length() == 0 )
734 {
735 return false;
736 }
737
738 int slashPos = fileName.indexOf( "/" );
739 String oldPwd = null;
740 boolean ok = true;
741
742 if ( slashPos == 0 )
743 {
744
745 oldPwd = ftp.printWorkingDirectory();
746
747
748 ftp.changeWorkingDirectory( "/" );
749 fileName = fileName.substring( 1 );
750
751
752 slashPos = fileName.indexOf( "/" );
753 }
754
755 if ( slashPos >= 0 && slashPos < ( fileName.length() - 1 ) )
756 {
757 if ( oldPwd == null )
758 {
759 oldPwd = ftp.printWorkingDirectory();
760 }
761
762 String nextDir = fileName.substring( 0, slashPos );
763
764
765 if ( !ftp.changeWorkingDirectory( nextDir ) )
766 {
767 ok &= ftp.makeDirectory( nextDir );
768 }
769
770 if ( ok )
771 {
772
773 setPermissions( permissions );
774
775 ftp.changeWorkingDirectory( nextDir );
776
777
778 String remainingDirs = fileName.substring( slashPos + 1 );
779 ok &= makeFtpDirectoryRecursive( remainingDirs, permissions );
780 }
781 }
782 else
783 {
784 ok = ftp.makeDirectory( fileName );
785 }
786
787 if ( oldPwd != null )
788 {
789
790 ftp.changeWorkingDirectory( oldPwd );
791 }
792
793 return ok;
794 }
795
796 public String getControlEncoding()
797 {
798 return controlEncoding;
799 }
800
801 public void setControlEncoding( String controlEncoding )
802 {
803 this.controlEncoding = controlEncoding;
804 }
805 }