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