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