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