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 public class PrintCommandListener
407 implements ProtocolCommandListener
408 {
409 private FtpWagon wagon;
410
411 public PrintCommandListener( FtpWagon wagon )
412 {
413 this.wagon = wagon;
414 }
415
416 public void protocolCommandSent( ProtocolCommandEvent event )
417 {
418 wagon.fireSessionDebug( "Command sent: " + event.getMessage() );
419
420 }
421
422 public void protocolReplyReceived( ProtocolCommandEvent event )
423 {
424 wagon.fireSessionDebug( "Reply received: " + event.getMessage() );
425 }
426 }
427
428 protected void fireSessionDebug( String msg )
429 {
430 super.fireSessionDebug( msg );
431 }
432
433 public List<String> getFileList( String destinationDirectory )
434 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
435 {
436 Resource resource = new Resource( destinationDirectory );
437
438 try
439 {
440 ftpChangeDirectory( resource );
441
442 String filename = PathUtils.filename( resource.getName() );
443 FTPFile[] ftpFiles = ftp.listFiles( filename );
444
445 if ( ftpFiles == null || ftpFiles.length <= 0 )
446 {
447 throw new ResourceDoesNotExistException( "Could not find file: '" + resource + "'" );
448 }
449
450 List<String> ret = new ArrayList<String>();
451 for ( FTPFile file : ftpFiles )
452 {
453 String name = file.getName();
454
455 if ( file.isDirectory() && !name.endsWith( "/" ) )
456 {
457 name += "/";
458 }
459
460 ret.add( name );
461 }
462
463 return ret;
464 }
465 catch ( IOException e )
466 {
467 throw new TransferFailedException( "Error transferring file via FTP", e );
468 }
469 }
470
471 public boolean resourceExists( String resourceName )
472 throws TransferFailedException, AuthorizationException
473 {
474 Resource resource = new Resource( resourceName );
475
476 try
477 {
478 ftpChangeDirectory( resource );
479
480 String filename = PathUtils.filename( resource.getName() );
481 int status = ftp.stat( filename );
482
483 return ( ( status == FTPReply.FILE_STATUS ) || ( status == FTPReply.DIRECTORY_STATUS ) || ( status
484 == FTPReply.FILE_STATUS_OK )
485 || ( status == FTPReply.COMMAND_OK )
486 || ( status == FTPReply.SYSTEM_STATUS ) );
487 }
488 catch ( IOException e )
489 {
490 throw new TransferFailedException( "Error transferring file via FTP", e );
491 }
492 catch ( ResourceDoesNotExistException e )
493 {
494 return false;
495 }
496 }
497
498 public boolean supportsDirectoryCopy()
499 {
500 return true;
501 }
502
503 public void putDirectory( File sourceDirectory, String destinationDirectory )
504 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
505 {
506
507
508 try
509 {
510 if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
511 {
512 RepositoryPermissions permissions = getRepository().getPermissions();
513 if ( !makeFtpDirectoryRecursive( getRepository().getBasedir(), permissions ) )
514 {
515 throw new TransferFailedException(
516 "Required directory: '" + getRepository().getBasedir() + "' " + "could not get created" );
517 }
518
519
520 if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
521 {
522 throw new TransferFailedException( "Required directory: '" + getRepository().getBasedir() + "' "
523 + "is missing and could not get created" );
524 }
525 }
526 }
527 catch ( IOException e )
528 {
529 throw new TransferFailedException( "Cannot change to root path " + getRepository().getBasedir(), e );
530 }
531
532 fireTransferDebug(
533 "Recursively uploading directory " + sourceDirectory.getAbsolutePath() + " as " + destinationDirectory );
534 ftpRecursivePut( sourceDirectory, destinationDirectory );
535 }
536
537 private void ftpRecursivePut( File sourceFile, String fileName )
538 throws TransferFailedException
539 {
540 final RepositoryPermissions permissions = repository.getPermissions();
541
542 fireTransferDebug( "processing = " + sourceFile.getAbsolutePath() + " as " + fileName );
543
544 if ( sourceFile.isDirectory() )
545 {
546 if ( !fileName.equals( "." ) )
547 {
548 try
549 {
550
551 if ( !ftp.changeWorkingDirectory( fileName ) )
552 {
553
554 if ( makeFtpDirectoryRecursive( fileName, permissions ) )
555 {
556 if ( !ftp.changeWorkingDirectory( fileName ) )
557 {
558 throw new TransferFailedException(
559 "Unable to change cwd on ftp server to " + fileName + " when processing "
560 + sourceFile.getAbsolutePath() );
561 }
562 }
563 else
564 {
565 throw new TransferFailedException(
566 "Unable to create directory " + fileName + " when processing "
567 + sourceFile.getAbsolutePath() );
568 }
569 }
570 }
571 catch ( IOException e )
572 {
573 throw new TransferFailedException(
574 "IOException caught while processing path at " + sourceFile.getAbsolutePath(), e );
575 }
576 }
577
578 File[] files = sourceFile.listFiles();
579 if ( files != null && files.length > 0 )
580 {
581 fireTransferDebug( "listing children of = " + sourceFile.getAbsolutePath() + " found " + files.length );
582
583
584 for ( File file : files )
585 {
586 if ( file.isDirectory() )
587 {
588 ftpRecursivePut( file, file.getName() );
589 }
590 }
591 for ( File file : files )
592 {
593 if ( !file.isDirectory() )
594 {
595 ftpRecursivePut( file, file.getName() );
596 }
597 }
598 }
599
600
601 try
602 {
603 ftp.changeToParentDirectory();
604 }
605 catch ( IOException e )
606 {
607 throw new TransferFailedException( "IOException caught while attempting to step up to parent directory"
608 + " after successfully processing "
609 + sourceFile.getAbsolutePath(), e );
610 }
611 }
612 else
613 {
614
615
616 FileInputStream sourceFileStream = null;
617 try
618 {
619 sourceFileStream = new FileInputStream( sourceFile );
620
621
622 if ( ftp.storeFile( fileName, sourceFileStream ) )
623 {
624 if ( permissions != null )
625 {
626
627
628 String group = permissions.getGroup();
629 if ( group != null )
630 {
631 try
632 {
633 ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() );
634 }
635 catch ( IOException e )
636 {
637 }
638 }
639 String mode = permissions.getFileMode();
640 if ( mode != null )
641 {
642 try
643 {
644 ftp.sendSiteCommand( "CHMOD " + permissions.getDirectoryMode() );
645 }
646 catch ( IOException e )
647 {
648 }
649 }
650 }
651 }
652 else
653 {
654 String msg =
655 "Cannot transfer resource: '" + sourceFile.getAbsolutePath() + "' FTP Server response: "
656 + ftp.getReplyString();
657 throw new TransferFailedException( msg );
658 }
659 }
660 catch ( IOException e )
661 {
662 throw new TransferFailedException(
663 "IOException caught while attempting to upload " + sourceFile.getAbsolutePath(), e );
664 }
665 finally
666 {
667 IOUtils.closeQuietly( sourceFileStream );
668 }
669
670 }
671
672 fireTransferDebug( "completed = " + sourceFile.getAbsolutePath() );
673 }
674
675
676
677
678
679
680
681
682
683 private void setPermissions( RepositoryPermissions permissions )
684 {
685 if ( permissions != null )
686 {
687
688
689 String group = permissions.getGroup();
690 if ( group != null )
691 {
692 try
693 {
694 ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() );
695 }
696 catch ( IOException e )
697 {
698 }
699 }
700 String mode = permissions.getDirectoryMode();
701 if ( mode != null )
702 {
703 try
704 {
705 ftp.sendSiteCommand( "CHMOD " + permissions.getDirectoryMode() );
706 }
707 catch ( IOException e )
708 {
709 }
710 }
711 }
712 }
713
714
715
716
717
718
719
720
721
722 private boolean makeFtpDirectoryRecursive( String fileName, RepositoryPermissions permissions )
723 throws IOException
724 {
725 if ( fileName == null || fileName.length() == 0
726 || fileName.replace( '/', '_' ).trim().length() == 0 )
727 {
728 return false;
729 }
730
731 int slashPos = fileName.indexOf( "/" );
732 String oldPwd = null;
733 boolean ok = true;
734
735 if ( slashPos == 0 )
736 {
737
738 oldPwd = ftp.printWorkingDirectory();
739
740
741 ftp.changeWorkingDirectory( "/" );
742 fileName = fileName.substring( 1 );
743
744
745 slashPos = fileName.indexOf( "/" );
746 }
747
748 if ( slashPos >= 0 && slashPos < ( fileName.length() - 1 ) )
749 {
750 if ( oldPwd == null )
751 {
752 oldPwd = ftp.printWorkingDirectory();
753 }
754
755 String nextDir = fileName.substring( 0, slashPos );
756
757
758 if ( !ftp.changeWorkingDirectory( nextDir ) )
759 {
760 ok &= ftp.makeDirectory( nextDir );
761 }
762
763 if ( ok )
764 {
765
766 setPermissions( permissions );
767
768 ftp.changeWorkingDirectory( nextDir );
769
770
771 String remainingDirs = fileName.substring( slashPos + 1 );
772 ok &= makeFtpDirectoryRecursive( remainingDirs, permissions );
773 }
774 }
775 else
776 {
777 ok = ftp.makeDirectory( fileName );
778 }
779
780 if ( oldPwd != null )
781 {
782
783 ftp.changeWorkingDirectory( oldPwd );
784 }
785
786 return ok;
787 }
788
789 public String getControlEncoding()
790 {
791 return controlEncoding;
792 }
793
794 public void setControlEncoding( String controlEncoding )
795 {
796 this.controlEncoding = controlEncoding;
797 }
798 }