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