1 package org.apache.maven.wagon.providers.webdav;
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.httpclient.Credentials;
23 import org.apache.commons.httpclient.Header;
24 import org.apache.commons.httpclient.HostConfiguration;
25 import org.apache.commons.httpclient.HttpClient;
26 import org.apache.commons.httpclient.HttpConnectionManager;
27 import org.apache.commons.httpclient.HttpMethod;
28 import org.apache.commons.httpclient.HttpStatus;
29 import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
30 import org.apache.commons.httpclient.NTCredentials;
31 import org.apache.commons.httpclient.UsernamePasswordCredentials;
32 import org.apache.commons.httpclient.auth.AuthScope;
33 import org.apache.commons.httpclient.cookie.CookiePolicy;
34 import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
35 import org.apache.commons.httpclient.methods.GetMethod;
36 import org.apache.commons.httpclient.methods.HeadMethod;
37 import org.apache.commons.httpclient.methods.PutMethod;
38 import org.apache.commons.httpclient.methods.RequestEntity;
39 import org.apache.commons.httpclient.params.HttpMethodParams;
40 import org.apache.commons.httpclient.util.DateParseException;
41 import org.apache.commons.httpclient.util.DateUtil;
42 import org.apache.commons.io.IOUtils;
43 import org.apache.commons.lang.StringUtils;
44 import org.apache.maven.wagon.InputData;
45 import org.apache.maven.wagon.OutputData;
46 import org.apache.maven.wagon.PathUtils;
47 import org.apache.maven.wagon.ResourceDoesNotExistException;
48 import org.apache.maven.wagon.StreamWagon;
49 import org.apache.maven.wagon.TransferFailedException;
50 import org.apache.maven.wagon.Wagon;
51 import org.apache.maven.wagon.authorization.AuthorizationException;
52 import org.apache.maven.wagon.events.TransferEvent;
53 import org.apache.maven.wagon.proxy.ProxyInfo;
54 import org.apache.maven.wagon.repository.Repository;
55 import org.apache.maven.wagon.resource.Resource;
56 import org.apache.maven.wagon.shared.http.EncodingUtil;
57 import org.codehaus.plexus.util.IOUtil;
58
59 import java.io.ByteArrayInputStream;
60 import java.io.File;
61 import java.io.FileInputStream;
62 import java.io.IOException;
63 import java.io.InputStream;
64 import java.io.OutputStream;
65 import java.nio.ByteBuffer;
66 import java.text.SimpleDateFormat;
67 import java.util.Date;
68 import java.util.Locale;
69 import java.util.Properties;
70 import java.util.TimeZone;
71 import java.util.zip.GZIPInputStream;
72
73
74
75
76
77 public abstract class AbstractHttpClientWagon
78 extends StreamWagon
79 {
80 private final class RequestEntityImplementation
81 implements RequestEntity
82 {
83 private final Resource resource;
84
85 private final Wagon wagon;
86
87 private File source;
88
89 private ByteBuffer byteBuffer;
90
91 private RequestEntityImplementation( final InputStream stream, final Resource resource, final Wagon wagon,
92 final File source )
93 throws TransferFailedException
94 {
95 if ( source != null )
96 {
97 this.source = source;
98 }
99 else
100 {
101 try
102 {
103 byte[] bytes = IOUtils.toByteArray( stream );
104 this.byteBuffer = ByteBuffer.allocate( bytes.length );
105 this.byteBuffer.put( bytes );
106 }
107 catch ( IOException e )
108 {
109 throw new TransferFailedException( e.getMessage(), e );
110 }
111 }
112
113 this.resource = resource;
114 this.wagon = wagon;
115 }
116
117 public long getContentLength()
118 {
119 return resource.getContentLength();
120 }
121
122 public String getContentType()
123 {
124 return null;
125 }
126
127 public boolean isRepeatable()
128 {
129 return true;
130 }
131
132 public void writeRequest( OutputStream output )
133 throws IOException
134 {
135 byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
136
137 TransferEvent transferEvent =
138 new TransferEvent( wagon, resource, TransferEvent.TRANSFER_PROGRESS, TransferEvent.REQUEST_PUT );
139 transferEvent.setTimestamp( System.currentTimeMillis() );
140
141 InputStream fin = null;
142 try
143 {
144 fin = this.source != null
145 ? new FileInputStream( source )
146 : new ByteArrayInputStream( this.byteBuffer.array() );
147 int remaining = Integer.MAX_VALUE;
148 while ( remaining > 0 )
149 {
150 int n = fin.read( buffer, 0, Math.min( buffer.length, remaining ) );
151
152 if ( n == -1 )
153 {
154 break;
155 }
156
157 fireTransferProgress( transferEvent, buffer, n );
158
159 output.write( buffer, 0, n );
160
161 remaining -= n;
162 }
163 }
164 finally
165 {
166 IOUtils.closeQuietly( fin );
167 }
168
169 output.flush();
170 }
171 }
172
173 protected static final int SC_NULL = -1;
174
175 protected static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone( "GMT" );
176
177 private HttpClient client;
178
179 protected HttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
180
181
182
183
184 private Properties httpHeaders;
185
186
187
188
189 private HttpConfiguration httpConfiguration;
190
191 private HttpMethod getMethod;
192
193 public void openConnectionInternal()
194 {
195 repository.setUrl( getURL( repository ) );
196 client = new HttpClient( connectionManager );
197
198
199 client.getParams().setCookiePolicy( CookiePolicy.BROWSER_COMPATIBILITY );
200
201 String username = null;
202 String password = null;
203 String domain = null;
204
205 if ( authenticationInfo != null )
206 {
207 username = authenticationInfo.getUserName();
208
209 if ( StringUtils.contains( username, "\\" ) )
210 {
211 String[] domainAndUsername = username.split( "\\\\" );
212 domain = domainAndUsername[0];
213 username = domainAndUsername[1];
214 }
215
216 password = authenticationInfo.getPassword();
217
218
219 }
220
221 String host = getRepository().getHost();
222
223 if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) )
224 {
225 Credentials creds;
226 if ( domain != null )
227 {
228 creds = new NTCredentials( username, password, host, domain );
229 }
230 else
231 {
232 creds = new UsernamePasswordCredentials( username, password );
233 }
234
235 int port = getRepository().getPort() > -1 ? getRepository().getPort() : AuthScope.ANY_PORT;
236
237 AuthScope scope = new AuthScope( host, port );
238 client.getState().setCredentials( scope, creds );
239 }
240
241 HostConfiguration hc = new HostConfiguration();
242
243 ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
244 if ( proxyInfo != null )
245 {
246 String proxyUsername = proxyInfo.getUserName();
247 String proxyPassword = proxyInfo.getPassword();
248 String proxyHost = proxyInfo.getHost();
249 int proxyPort = proxyInfo.getPort();
250 String proxyNtlmHost = proxyInfo.getNtlmHost();
251 String proxyNtlmDomain = proxyInfo.getNtlmDomain();
252 if ( proxyHost != null )
253 {
254 hc.setProxy( proxyHost, proxyPort );
255
256 if ( proxyUsername != null && proxyPassword != null )
257 {
258 Credentials creds;
259 if ( proxyNtlmHost != null || proxyNtlmDomain != null )
260 {
261 creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain );
262 }
263 else
264 {
265 creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword );
266 }
267
268 int port = proxyInfo.getPort() > -1 ? proxyInfo.getPort() : AuthScope.ANY_PORT;
269
270 AuthScope scope = new AuthScope( proxyHost, port );
271 client.getState().setProxyCredentials( scope, creds );
272 }
273 }
274 }
275
276 hc.setHost( host );
277
278
279 client.setHostConfiguration( hc );
280 }
281
282 public void closeConnection()
283 {
284 if ( connectionManager instanceof MultiThreadedHttpConnectionManager )
285 {
286 ( (MultiThreadedHttpConnectionManager) connectionManager ).shutdown();
287 }
288 }
289
290 public void put( File source, String resourceName )
291 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
292 {
293 Resource resource = new Resource( resourceName );
294
295 firePutInitiated( resource, source );
296
297 resource.setContentLength( source.length() );
298
299 resource.setLastModified( source.lastModified() );
300
301 put( null, resource, source );
302 }
303
304 public void putFromStream( final InputStream stream, String destination, long contentLength, long lastModified )
305 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
306 {
307 Resource resource = new Resource( destination );
308
309 firePutInitiated( resource, null );
310
311 resource.setContentLength( contentLength );
312
313 resource.setLastModified( lastModified );
314
315 put( stream, resource, null );
316 }
317
318 private void put( final InputStream stream, Resource resource, File source )
319 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
320 {
321 RequestEntityImplementation requestEntityImplementation =
322 new RequestEntityImplementation( stream, resource, this, source );
323
324 put( resource, source, requestEntityImplementation, buildUrl( resource ) );
325
326 }
327
328
329
330
331
332
333
334 private String buildUrl( Resource resource )
335 {
336 return EncodingUtil.encodeURLToString( getRepository().getUrl(), resource.getName() );
337 }
338
339 private void put( Resource resource, File source, RequestEntityImplementation requestEntityImplementation,
340 String url )
341 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
342 {
343
344 client.getParams().setAuthenticationPreemptive( true );
345
346
347 try
348 {
349 mkdirs( PathUtils.dirname( resource.getName() ) );
350 }
351 catch ( IOException e )
352 {
353 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
354 }
355
356 PutMethod putMethod = new PutMethod( url );
357
358 firePutStarted( resource, source );
359
360 try
361 {
362 putMethod.setRequestEntity( requestEntityImplementation );
363
364 int statusCode;
365 try
366 {
367 statusCode = execute( putMethod );
368
369 }
370 catch ( IOException e )
371 {
372 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
373
374 throw new TransferFailedException( e.getMessage(), e );
375 }
376
377 fireTransferDebug( url + " - Status code: " + statusCode );
378
379
380
381 switch ( statusCode )
382 {
383
384 case HttpStatus.SC_OK:
385 case HttpStatus.SC_CREATED:
386 case HttpStatus.SC_ACCEPTED:
387 case HttpStatus.SC_NO_CONTENT:
388 break;
389
390
391
392 case HttpStatus.SC_MOVED_PERMANENTLY:
393 case HttpStatus.SC_MOVED_TEMPORARILY:
394 case HttpStatus.SC_SEE_OTHER:
395 String relocatedUrl = calculateRelocatedUrl( putMethod );
396 fireTransferDebug( "relocate to " + relocatedUrl );
397 put( resource, source, requestEntityImplementation, relocatedUrl );
398 return;
399
400 case SC_NULL:
401 {
402 TransferFailedException e = new TransferFailedException( "Failed to transfer file: " + url );
403 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
404 throw e;
405 }
406
407 case HttpStatus.SC_FORBIDDEN:
408 fireSessionConnectionRefused();
409 throw new AuthorizationException( "Access denied to: " + url );
410
411 case HttpStatus.SC_NOT_FOUND:
412 throw new ResourceDoesNotExistException( "File: " + url + " does not exist" );
413
414
415 default:
416 {
417 TransferFailedException e = new TransferFailedException(
418 "Failed to transfer file: " + url + ". Return code is: " + statusCode );
419 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
420 throw e;
421 }
422 }
423
424
425 firePutCompleted( resource, source );
426 }
427 finally
428 {
429 putMethod.releaseConnection();
430 }
431 }
432
433 protected String calculateRelocatedUrl( EntityEnclosingMethod method )
434 {
435 Header locationHeader = method.getResponseHeader( "Location" );
436 String locationField = locationHeader.getValue();
437
438 return locationField.startsWith( "http" ) ? locationField : getURL( getRepository() ) + '/' + locationField;
439 }
440
441 protected void mkdirs( String dirname )
442 throws IOException
443 {
444
445 }
446
447 public boolean resourceExists( String resourceName )
448 throws TransferFailedException, AuthorizationException
449 {
450 StringBuilder url = new StringBuilder( getRepository().getUrl() );
451 if ( !url.toString().endsWith( "/" ) )
452 {
453 url.append( '/' );
454 }
455 url.append( resourceName );
456 HeadMethod headMethod = new HeadMethod( url.toString() );
457
458 int statusCode;
459 try
460 {
461 statusCode = execute( headMethod );
462 }
463 catch ( IOException e )
464 {
465 throw new TransferFailedException( e.getMessage(), e );
466 }
467 try
468 {
469 switch ( statusCode )
470 {
471 case HttpStatus.SC_OK:
472 return true;
473
474 case HttpStatus.SC_NOT_MODIFIED:
475 return true;
476
477 case SC_NULL:
478 throw new TransferFailedException( "Failed to transfer file: " + url );
479
480 case HttpStatus.SC_FORBIDDEN:
481 throw new AuthorizationException( "Access denied to: " + url );
482
483 case HttpStatus.SC_UNAUTHORIZED:
484 throw new AuthorizationException( "Not authorized." );
485
486 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
487 throw new AuthorizationException( "Not authorized by proxy." );
488
489 case HttpStatus.SC_NOT_FOUND:
490 return false;
491
492
493 default:
494 throw new TransferFailedException(
495 "Failed to transfer file: " + url + ". Return code is: " + statusCode );
496 }
497 }
498 finally
499 {
500 headMethod.releaseConnection();
501 }
502 }
503
504 protected int execute( HttpMethod httpMethod )
505 throws IOException
506 {
507 int statusCode;
508
509 setParameters( httpMethod );
510 setHeaders( httpMethod );
511
512 statusCode = client.executeMethod( httpMethod );
513 return statusCode;
514 }
515
516 protected void setParameters( HttpMethod method )
517 {
518 HttpMethodConfiguration config =
519 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
520 if ( config != null )
521 {
522 HttpMethodParams params = config.asMethodParams( method.getParams() );
523 if ( params != null )
524 {
525 method.setParams( params );
526 }
527 }
528
529 if ( config == null || config.getConnectionTimeout() == HttpMethodConfiguration.DEFAULT_CONNECTION_TIMEOUT )
530 {
531 method.getParams().setSoTimeout( getTimeout() );
532 }
533 }
534
535 protected void setHeaders( HttpMethod method )
536 {
537 HttpMethodConfiguration config =
538 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
539 if ( config == null || config.isUseDefaultHeaders() )
540 {
541
542 method.addRequestHeader( "Cache-control", "no-cache" );
543 method.addRequestHeader( "Cache-store", "no-store" );
544 method.addRequestHeader( "Pragma", "no-cache" );
545 method.addRequestHeader( "Expires", "0" );
546 method.addRequestHeader( "Accept-Encoding", "gzip" );
547 method.addRequestHeader( "User-Agent", DEFAULT_USER_AGENT );
548 }
549
550 if ( httpHeaders != null )
551 {
552 for ( Object header : httpHeaders.keySet() )
553 {
554 if ( "User-Agent".equals( header ) )
555 {
556 method.setRequestHeader( (String) header, httpHeaders.getProperty( (String) header ) );
557 }
558 else
559 {
560 method.addRequestHeader( (String) header, httpHeaders.getProperty( (String) header ) );
561 }
562 }
563 }
564
565 Header[] headers = config == null ? null : config.asRequestHeaders();
566 if ( headers != null )
567 {
568 for ( Header header : headers )
569 {
570 method.addRequestHeader( header );
571 }
572 }
573 }
574
575 private static final String DEFAULT_USER_AGENT = getDefaultUserAgent();
576
577 private static String getDefaultUserAgent()
578 {
579 Properties props = new Properties();
580
581 InputStream is = AbstractHttpClientWagon.class.getResourceAsStream(
582 "/META-INF/maven/org.apache.maven.wagon/wagon-webdav-jackrabbit/pom.properties" );
583 if ( is != null )
584 {
585 try
586 {
587 props.load( is );
588 }
589 catch ( IOException ignore )
590 {
591
592 }
593 finally
594 {
595 IOUtil.close( is );
596 }
597 }
598
599 String ver = props.getProperty( "version", "unknown-version" );
600 return "Apache-Maven-Wagon/" + ver + " (Java " + System.getProperty( "java.version" ) + "; ";
601 }
602
603
604
605
606
607
608
609
610 protected String getURL( Repository repository )
611 {
612 return repository.getUrl();
613 }
614
615 protected HttpClient getClient()
616 {
617 return client;
618 }
619
620 public void setConnectionManager( HttpConnectionManager connectionManager )
621 {
622 this.connectionManager = connectionManager;
623 }
624
625 public Properties getHttpHeaders()
626 {
627 return httpHeaders;
628 }
629
630 public void setHttpHeaders( Properties httpHeaders )
631 {
632 this.httpHeaders = httpHeaders;
633 }
634
635 public HttpConfiguration getHttpConfiguration()
636 {
637 return httpConfiguration;
638 }
639
640 public void setHttpConfiguration( HttpConfiguration httpConfiguration )
641 {
642 this.httpConfiguration = httpConfiguration;
643 }
644
645 public void fillInputData( InputData inputData )
646 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
647 {
648 Resource resource = inputData.getResource();
649
650 StringBuilder url = new StringBuilder( getRepository().getUrl() );
651 if ( !url.toString().endsWith( "/" ) )
652 {
653 url.append( '/' );
654 }
655 url.append( resource.getName() );
656
657 getMethod = new GetMethod( url.toString() );
658
659 long timestamp = resource.getLastModified();
660 if ( timestamp > 0 )
661 {
662 SimpleDateFormat fmt = new SimpleDateFormat( "EEE, dd-MMM-yy HH:mm:ss zzz", Locale.US );
663 fmt.setTimeZone( GMT_TIME_ZONE );
664 Header hdr = new Header( "If-Modified-Since", fmt.format( new Date( timestamp ) ) );
665 fireTransferDebug( "sending ==> " + hdr + "(" + timestamp + ")" );
666 getMethod.addRequestHeader( hdr );
667 }
668
669 int statusCode;
670 try
671 {
672 statusCode = execute( getMethod );
673 }
674 catch ( IOException e )
675 {
676 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
677
678 throw new TransferFailedException( e.getMessage(), e );
679 }
680
681 fireTransferDebug( url + " - Status code: " + statusCode );
682
683
684
685
686 switch ( statusCode )
687 {
688 case HttpStatus.SC_OK:
689 break;
690
691 case HttpStatus.SC_NOT_MODIFIED:
692
693 return;
694
695 case SC_NULL:
696 {
697 TransferFailedException e = new TransferFailedException( "Failed to transfer file: " + url );
698 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
699 throw e;
700 }
701
702 case HttpStatus.SC_FORBIDDEN:
703 fireSessionConnectionRefused();
704 throw new AuthorizationException( "Access denied to: " + url );
705
706 case HttpStatus.SC_UNAUTHORIZED:
707 fireSessionConnectionRefused();
708 throw new AuthorizationException( "Not authorized." );
709
710 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
711 fireSessionConnectionRefused();
712 throw new AuthorizationException( "Not authorized by proxy." );
713
714 case HttpStatus.SC_NOT_FOUND:
715 throw new ResourceDoesNotExistException( "File: " + url + " does not exist" );
716
717
718 default:
719 {
720 cleanupGetTransfer( resource );
721 TransferFailedException e = new TransferFailedException(
722 "Failed to transfer file: " + url + ". Return code is: " + statusCode );
723 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
724 throw e;
725 }
726 }
727
728
729 InputStream is = null;
730
731 Header contentLengthHeader = getMethod.getResponseHeader( "Content-Length" );
732
733 if ( contentLengthHeader != null )
734 {
735 try
736 {
737 long contentLength = Integer.valueOf( contentLengthHeader.getValue() ).intValue();
738
739 resource.setContentLength( contentLength );
740 }
741 catch ( NumberFormatException e )
742 {
743 fireTransferDebug(
744 "error parsing content length header '" + contentLengthHeader.getValue() + "' " + e );
745 }
746 }
747
748 Header lastModifiedHeader = getMethod.getResponseHeader( "Last-Modified" );
749
750 long lastModified = 0;
751
752 if ( lastModifiedHeader != null )
753 {
754 try
755 {
756 lastModified = DateUtil.parseDate( lastModifiedHeader.getValue() ).getTime();
757
758 resource.setLastModified( lastModified );
759 }
760 catch ( DateParseException e )
761 {
762 fireTransferDebug( "Unable to parse last modified header" );
763 }
764
765 fireTransferDebug( "last-modified = " + lastModifiedHeader.getValue() + " (" + lastModified + ")" );
766 }
767
768 Header contentEncoding = getMethod.getResponseHeader( "Content-Encoding" );
769 boolean isGZipped = contentEncoding != null && "gzip".equalsIgnoreCase( contentEncoding.getValue() );
770
771 try
772 {
773 is = getMethod.getResponseBodyAsStream();
774 if ( isGZipped )
775 {
776 is = new GZIPInputStream( is );
777 }
778 }
779 catch ( IOException e )
780 {
781 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
782
783 String msg =
784 "Error occurred while retrieving from remote repository:" + getRepository() + ": " + e.getMessage();
785
786 throw new TransferFailedException( msg, e );
787 }
788
789 inputData.setInputStream( is );
790 }
791
792 protected void cleanupGetTransfer( Resource resource )
793 {
794 if ( getMethod != null )
795 {
796 getMethod.releaseConnection();
797 }
798 }
799
800 @Override
801 public void putFromStream( InputStream stream, String destination )
802 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
803 {
804 putFromStream( stream, destination, -1, -1 );
805 }
806
807 @Override
808 protected void putFromStream( InputStream stream, Resource resource )
809 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
810 {
811 putFromStream( stream, resource.getName(), -1, -1 );
812 }
813
814 @Override
815 public void fillOutputData( OutputData outputData )
816 throws TransferFailedException
817 {
818
819 throw new IllegalStateException( "this wagon http client must not use fillOutputData" );
820 }
821 }