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