1 package org.apache.maven.wagon.providers.http;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.ByteArrayInputStream;
23 import java.io.Closeable;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.OutputStream;
29 import java.nio.ByteBuffer;
30 import java.text.SimpleDateFormat;
31 import java.util.Date;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.Properties;
35 import java.util.TimeZone;
36 import java.util.concurrent.TimeUnit;
37
38 import javax.net.ssl.HttpsURLConnection;
39 import javax.net.ssl.SSLContext;
40
41 import org.apache.http.Header;
42 import org.apache.http.HttpEntity;
43 import org.apache.http.HttpException;
44 import org.apache.http.HttpHost;
45 import org.apache.http.HttpResponse;
46 import org.apache.http.HttpStatus;
47 import org.apache.http.auth.AUTH;
48 import org.apache.http.auth.AuthScope;
49 import org.apache.http.auth.Credentials;
50 import org.apache.http.auth.MalformedChallengeException;
51 import org.apache.http.auth.NTCredentials;
52 import org.apache.http.auth.UsernamePasswordCredentials;
53 import org.apache.http.client.AuthCache;
54 import org.apache.http.client.CredentialsProvider;
55 import org.apache.http.client.config.CookieSpecs;
56 import org.apache.http.client.config.RequestConfig;
57 import org.apache.http.client.methods.CloseableHttpResponse;
58 import org.apache.http.client.methods.HttpGet;
59 import org.apache.http.client.methods.HttpHead;
60 import org.apache.http.client.methods.HttpPut;
61 import org.apache.http.client.methods.HttpUriRequest;
62 import org.apache.http.client.protocol.HttpClientContext;
63 import org.apache.http.client.utils.DateUtils;
64 import org.apache.http.config.Registry;
65 import org.apache.http.config.RegistryBuilder;
66 import org.apache.http.conn.socket.ConnectionSocketFactory;
67 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
68 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
69 import org.apache.http.conn.ssl.SSLContextBuilder;
70 import org.apache.http.conn.ssl.SSLInitializationException;
71 import org.apache.http.entity.AbstractHttpEntity;
72 import org.apache.http.impl.auth.BasicScheme;
73 import org.apache.http.impl.client.BasicAuthCache;
74 import org.apache.http.impl.client.BasicCredentialsProvider;
75 import org.apache.http.impl.client.CloseableHttpClient;
76 import org.apache.http.impl.client.HttpClientBuilder;
77 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
78 import org.apache.http.message.BasicHeader;
79 import org.apache.http.protocol.HTTP;
80 import org.apache.http.util.EntityUtils;
81 import org.apache.maven.wagon.InputData;
82 import org.apache.maven.wagon.OutputData;
83 import org.apache.maven.wagon.PathUtils;
84 import org.apache.maven.wagon.ResourceDoesNotExistException;
85 import org.apache.maven.wagon.StreamWagon;
86 import org.apache.maven.wagon.TransferFailedException;
87 import org.apache.maven.wagon.Wagon;
88 import org.apache.maven.wagon.authorization.AuthorizationException;
89 import org.apache.maven.wagon.events.TransferEvent;
90 import org.apache.maven.wagon.proxy.ProxyInfo;
91 import org.apache.maven.wagon.repository.Repository;
92 import org.apache.maven.wagon.resource.Resource;
93 import org.apache.maven.wagon.shared.http.EncodingUtil;
94 import org.codehaus.plexus.util.IOUtil;
95 import org.codehaus.plexus.util.StringUtils;
96
97 import javax.net.ssl.HttpsURLConnection;
98 import javax.net.ssl.SSLContext;
99 import java.io.ByteArrayInputStream;
100 import java.io.Closeable;
101 import java.io.File;
102 import java.io.FileInputStream;
103 import java.io.IOException;
104 import java.io.InputStream;
105 import java.io.OutputStream;
106 import java.nio.ByteBuffer;
107 import java.text.SimpleDateFormat;
108 import java.util.Date;
109 import java.util.Locale;
110 import java.util.Map;
111 import java.util.Properties;
112 import java.util.TimeZone;
113 import java.util.concurrent.TimeUnit;
114
115
116
117
118
119 public abstract class AbstractHttpClientWagon
120 extends StreamWagon
121 {
122 private final class RequestEntityImplementation
123 extends AbstractHttpEntity
124 {
125
126 private final static int BUFFER_SIZE = 2048;
127
128 private final Resource resource;
129
130 private final Wagon wagon;
131
132 private ByteBuffer byteBuffer;
133
134 private File source;
135
136 private long length = -1;
137
138 private RequestEntityImplementation( final InputStream stream, final Resource resource, final Wagon wagon,
139 final File source )
140 throws TransferFailedException
141 {
142 if ( source != null )
143 {
144 this.source = source;
145 }
146 else
147 {
148 try
149 {
150 byte[] bytes = IOUtil.toByteArray( stream );
151 byteBuffer = ByteBuffer.allocate( bytes.length );
152 byteBuffer.put( bytes );
153 }
154 catch ( IOException e )
155 {
156 throw new TransferFailedException( e.getMessage(), e );
157 }
158 }
159 this.resource = resource;
160 this.length = resource == null ? -1 : resource.getContentLength();
161
162 this.wagon = wagon;
163 }
164
165 public long getContentLength()
166 {
167 return length;
168 }
169
170 public InputStream getContent()
171 throws IOException, IllegalStateException
172 {
173 if ( this.source != null )
174 {
175 return new FileInputStream( this.source );
176 }
177 return new ByteArrayInputStream( this.byteBuffer.array() );
178 }
179
180 public boolean isRepeatable()
181 {
182 return true;
183 }
184
185 public void writeTo( final OutputStream outstream )
186 throws IOException
187 {
188 if ( outstream == null )
189 {
190 throw new IllegalArgumentException( "Output stream may not be null" );
191 }
192 TransferEvent transferEvent =
193 new TransferEvent( wagon, resource, TransferEvent.TRANSFER_PROGRESS, TransferEvent.REQUEST_PUT );
194 transferEvent.setTimestamp( System.currentTimeMillis() );
195 InputStream instream = ( this.source != null )
196 ? new FileInputStream( this.source )
197 : new ByteArrayInputStream( this.byteBuffer.array() );
198 try
199 {
200 byte[] buffer = new byte[BUFFER_SIZE];
201 int l;
202 if ( this.length < 0 )
203 {
204
205 while ( ( l = instream.read( buffer ) ) != -1 )
206 {
207 fireTransferProgress( transferEvent, buffer, -1 );
208 outstream.write( buffer, 0, l );
209 }
210 }
211 else
212 {
213
214 long remaining = this.length;
215 while ( remaining > 0 )
216 {
217 l = instream.read( buffer, 0, (int) Math.min( BUFFER_SIZE, remaining ) );
218 if ( l == -1 )
219 {
220 break;
221 }
222 fireTransferProgress( transferEvent, buffer, (int) Math.min( BUFFER_SIZE, remaining ) );
223 outstream.write( buffer, 0, l );
224 remaining -= l;
225 }
226 }
227 }
228 finally
229 {
230 instream.close();
231 }
232 }
233
234 public boolean isStreaming()
235 {
236 return true;
237 }
238 }
239
240 private static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone( "GMT" );
241
242
243
244
245
246 private final static boolean PERSISTENT_POOL =
247 Boolean.valueOf( System.getProperty( "maven.wagon.http.pool", "true" ) );
248
249
250
251
252
253 private final static boolean SSL_INSECURE =
254 Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.insecure", "false" ) );
255
256
257
258
259
260 private final static boolean IGNORE_SSL_VALIDITY_DATES =
261 Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.ignore.validity.dates", "false" ) );
262
263
264
265
266
267 private final static boolean SSL_ALLOW_ALL =
268 Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.allowall", "false" ) );
269
270
271
272
273
274
275 private final static int MAX_CONN_PER_ROUTE =
276 Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxPerRoute", "20" ) );
277
278
279
280
281
282 private final static int MAX_CONN_TOTAL =
283 Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxTotal", "40" ) );
284
285
286
287
288 private static final PoolingHttpClientConnectionManager CONN_MAN = createConnManager();
289
290
291
292
293
294 protected static final int SC_TOO_MANY_REQUESTS = 429;
295
296
297
298
299
300
301
302
303
304
305
306
307 private int initialBackoffSeconds =
308 Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.backoffSeconds", "5" ) );
309
310
311
312
313
314
315
316 private final static int maxBackoffWaitSeconds =
317 Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxBackoffSeconds", "180" ) );
318
319
320 protected int backoff( int wait, String url )
321 throws InterruptedException, TransferFailedException
322 {
323 TimeUnit.SECONDS.sleep( wait );
324 int nextWait = wait * 2;
325 if ( nextWait >= getMaxBackoffWaitSeconds() )
326 {
327 throw new TransferFailedException(
328 "Waited too long to access: " + url + ". Return code is: " + SC_TOO_MANY_REQUESTS );
329 }
330 return nextWait;
331 }
332
333
334 private static PoolingHttpClientConnectionManager createConnManager()
335 {
336
337 String sslProtocolsStr = System.getProperty( "https.protocols" );
338 String cipherSuitesStr = System.getProperty( "https.cipherSuites" );
339 String[] sslProtocols = sslProtocolsStr != null ? sslProtocolsStr.split( " *, *" ) : null;
340 String[] cipherSuites = cipherSuitesStr != null ? cipherSuitesStr.split( " *, *" ) : null;
341
342 SSLConnectionSocketFactory sslConnectionSocketFactory;
343 if ( SSL_INSECURE )
344 {
345 try
346 {
347 SSLContext sslContext = new SSLContextBuilder().useSSL().loadTrustMaterial( null,
348 new RelaxedTrustStrategy(
349 IGNORE_SSL_VALIDITY_DATES ) ).build();
350 sslConnectionSocketFactory = new SSLConnectionSocketFactory( sslContext, sslProtocols, cipherSuites,
351 SSL_ALLOW_ALL
352 ? SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
353 : SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER );
354 }
355 catch ( Exception ex )
356 {
357 throw new SSLInitializationException( ex.getMessage(), ex );
358 }
359 }
360 else
361 {
362 sslConnectionSocketFactory =
363 new SSLConnectionSocketFactory( HttpsURLConnection.getDefaultSSLSocketFactory(), sslProtocols,
364 cipherSuites,
365 SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER );
366 }
367
368 Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register( "http",
369 PlainConnectionSocketFactory.INSTANCE ).register(
370 "https", sslConnectionSocketFactory ).build();
371
372 PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager( registry );
373 if ( PERSISTENT_POOL )
374 {
375 connManager.setDefaultMaxPerRoute( MAX_CONN_PER_ROUTE );
376 connManager.setMaxTotal( MAX_CONN_TOTAL );
377 }
378 else
379 {
380 connManager.setMaxTotal( 1 );
381 }
382 return connManager;
383 }
384
385 private static CloseableHttpClient CLIENT = createClient();
386
387 private static CloseableHttpClient createClient()
388 {
389 return HttpClientBuilder.create()
390 .useSystemProperties()
391 .disableConnectionState()
392 .setConnectionManager( CONN_MAN )
393 .build();
394 }
395
396 private static String DEFAULT_USER_AGENT = getDefaultUserAgent();
397
398 private static String getDefaultUserAgent()
399 {
400 Properties props = new Properties();
401
402 InputStream is = AbstractHttpClientWagon.class.getResourceAsStream(
403 "/META-INF/maven/org.apache.maven.wagon/wagon-http/pom.properties" );
404 if ( is != null )
405 {
406 try
407 {
408 props.load( is );
409 }
410 catch ( IOException ignore )
411 {
412 }
413 finally
414 {
415 IOUtil.close( is );
416 }
417 }
418
419 String ver = props.getProperty( "version", "unknown-version" );
420 return "Apache-Maven-Wagon/" + ver + " (Java " + System.getProperty( "java.version" ) + "; ";
421 }
422
423
424 private CredentialsProvider credentialsProvider;
425
426 private AuthCache authCache;
427
428 private HttpClientContext localContext;
429
430 private Closeable closeable;
431
432
433
434
435
436 private Properties httpHeaders;
437
438
439
440
441 private HttpConfiguration httpConfiguration;
442
443 public void openConnectionInternal()
444 {
445 repository.setUrl( getURL( repository ) );
446
447 localContext = HttpClientContext.create();
448 credentialsProvider = new BasicCredentialsProvider();
449 authCache = new BasicAuthCache();
450 localContext.setCredentialsProvider( credentialsProvider );
451 localContext.setAuthCache( authCache );
452
453 if ( authenticationInfo != null )
454 {
455
456 String username = authenticationInfo.getUserName();
457 String password = authenticationInfo.getPassword();
458
459 if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) )
460 {
461 Credentials creds = new UsernamePasswordCredentials( username, password );
462
463 String host = getRepository().getHost();
464 int port = getRepository().getPort() > -1 ? getRepository().getPort() : AuthScope.ANY_PORT;
465
466 credentialsProvider.setCredentials( new AuthScope( host, port ), creds );
467 }
468 }
469
470 ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
471 if ( proxyInfo != null )
472 {
473 String proxyUsername = proxyInfo.getUserName();
474 String proxyPassword = proxyInfo.getPassword();
475 String proxyHost = proxyInfo.getHost();
476 String proxyNtlmHost = proxyInfo.getNtlmHost();
477 String proxyNtlmDomain = proxyInfo.getNtlmDomain();
478 if ( proxyHost != null )
479 {
480 if ( proxyUsername != null && proxyPassword != null )
481 {
482 Credentials creds;
483 if ( proxyNtlmHost != null || proxyNtlmDomain != null )
484 {
485 creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain );
486 }
487 else
488 {
489 creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword );
490 }
491
492 int port = proxyInfo.getPort() > -1 ? proxyInfo.getPort() : AuthScope.ANY_PORT;
493
494 AuthScope authScope = new AuthScope( proxyHost, port );
495 credentialsProvider.setCredentials( authScope, creds );
496 }
497 }
498 }
499 }
500
501 public void closeConnection()
502 {
503 if ( !PERSISTENT_POOL )
504 {
505 CONN_MAN.closeIdleConnections( 0, TimeUnit.MILLISECONDS );
506 }
507 }
508
509 public void put( File source, String resourceName )
510 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
511 {
512 Resource resource = new Resource( resourceName );
513
514 firePutInitiated( resource, source );
515
516 resource.setContentLength( source.length() );
517
518 resource.setLastModified( source.lastModified() );
519
520 put( null, resource, source );
521 }
522
523 public void putFromStream( final InputStream stream, String destination, long contentLength, long lastModified )
524 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
525 {
526 Resource resource = new Resource( destination );
527
528 firePutInitiated( resource, null );
529
530 resource.setContentLength( contentLength );
531
532 resource.setLastModified( lastModified );
533
534 put( stream, resource, null );
535 }
536
537 private void put( final InputStream stream, Resource resource, File source )
538 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
539 {
540 put( resource, source, new RequestEntityImplementation( stream, resource, this, source ) );
541 }
542
543 private void put( Resource resource, File source, HttpEntity httpEntity )
544 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
545 {
546 put( resource, source, httpEntity, buildUrl( resource ) );
547 }
548
549
550
551
552
553
554
555 private String buildUrl( Resource resource )
556 {
557 return EncodingUtil.encodeURLToString( getRepository().getUrl(), resource.getName() );
558 }
559
560
561 private void put( Resource resource, File source, HttpEntity httpEntity, String url )
562 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
563 {
564 put( getInitialBackoffSeconds(), resource, source, httpEntity, url );
565 }
566
567
568 private void put( int wait, Resource resource, File source, HttpEntity httpEntity, String url )
569 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
570 {
571
572
573 try
574 {
575 mkdirs( PathUtils.dirname( resource.getName() ) );
576 }
577 catch ( HttpException he )
578 {
579 fireTransferError( resource, he, TransferEvent.REQUEST_GET );
580 }
581 catch ( IOException e )
582 {
583 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
584 }
585
586
587
588
589 Repository repo = getRepository();
590 HttpHost targetHost = new HttpHost( repo.getHost(), repo.getPort(), repo.getProtocol() );
591 AuthScope targetScope = new AuthScope( targetHost );
592
593 if ( credentialsProvider.getCredentials( targetScope ) != null )
594 {
595 BasicScheme targetAuth = new BasicScheme();
596 try
597 {
598 targetAuth.processChallenge( new BasicHeader( AUTH.WWW_AUTH, "BASIC preemptive" ) );
599 authCache.put( targetHost, targetAuth );
600 }
601 catch ( MalformedChallengeException ignore )
602 {
603 }
604 }
605
606 HttpPut putMethod = new HttpPut( url );
607
608 firePutStarted( resource, source );
609
610 try
611 {
612 putMethod.setEntity( httpEntity );
613
614 CloseableHttpResponse response = execute( putMethod );
615 try
616 {
617 int statusCode = response.getStatusLine().getStatusCode();
618 String reasonPhrase = ", ReasonPhrase: " + response.getStatusLine().getReasonPhrase() + ".";
619 fireTransferDebug( url + " - Status code: " + statusCode + reasonPhrase );
620
621
622 switch ( statusCode )
623 {
624
625 case HttpStatus.SC_OK:
626 case HttpStatus.SC_CREATED:
627 case HttpStatus.SC_ACCEPTED:
628 case HttpStatus.SC_NO_CONTENT:
629 break;
630
631 case HttpStatus.SC_MOVED_PERMANENTLY:
632 case HttpStatus.SC_MOVED_TEMPORARILY:
633 case HttpStatus.SC_SEE_OTHER:
634 put( resource, source, httpEntity, calculateRelocatedUrl( response ) );
635 return;
636 case HttpStatus.SC_FORBIDDEN:
637 fireSessionConnectionRefused();
638 throw new AuthorizationException( "Access denied to: " + url + reasonPhrase );
639
640 case HttpStatus.SC_NOT_FOUND:
641 throw new ResourceDoesNotExistException( "File: " + url + " does not exist" + reasonPhrase );
642
643 case SC_TOO_MANY_REQUESTS:
644 put( backoff( wait, url ), resource, source, httpEntity, url );
645 break;
646
647 default:
648 {
649 TransferFailedException e = new TransferFailedException(
650 "Failed to transfer file: " + url + ". Return code is: " + statusCode + reasonPhrase );
651 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
652 throw e;
653 }
654 }
655
656 firePutCompleted( resource, source );
657
658 EntityUtils.consume( response.getEntity() );
659 }
660 finally
661 {
662 response.close();
663 }
664 }
665 catch ( IOException e )
666 {
667 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
668
669 throw new TransferFailedException( e.getMessage(), e );
670 }
671 catch ( HttpException e )
672 {
673 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
674
675 throw new TransferFailedException( e.getMessage(), e );
676 }
677 catch ( InterruptedException e )
678 {
679 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
680
681 throw new TransferFailedException( e.getMessage(), e );
682 }
683
684 }
685
686 protected String calculateRelocatedUrl( HttpResponse response )
687 {
688 Header locationHeader = response.getFirstHeader( "Location" );
689 String locationField = locationHeader.getValue();
690
691 return locationField.startsWith( "http" ) ? locationField : getURL( getRepository() ) + '/' + locationField;
692 }
693
694 protected void mkdirs( String dirname )
695 throws HttpException, IOException
696 {
697
698 }
699
700 public boolean resourceExists( String resourceName )
701 throws TransferFailedException, AuthorizationException
702 {
703 return resourceExists( getInitialBackoffSeconds(), resourceName );
704 }
705
706
707 private boolean resourceExists( int wait, String resourceName )
708 throws TransferFailedException, AuthorizationException
709 {
710 String repositoryUrl = getRepository().getUrl();
711 String url = repositoryUrl + ( repositoryUrl.endsWith( "/" ) ? "" : "/" ) + resourceName;
712 HttpHead headMethod = new HttpHead( url );
713 try
714 {
715 CloseableHttpResponse response = execute( headMethod );
716 try
717 {
718 int statusCode = response.getStatusLine().getStatusCode();
719 String reasonPhrase = ", ReasonPhrase: " + response.getStatusLine().getReasonPhrase() + ".";
720 boolean result;
721 switch ( statusCode )
722 {
723 case HttpStatus.SC_OK:
724 result = true;
725 break;
726 case HttpStatus.SC_NOT_MODIFIED:
727 result = true;
728 break;
729 case HttpStatus.SC_FORBIDDEN:
730 throw new AuthorizationException( "Access denied to: " + url + reasonPhrase );
731
732 case HttpStatus.SC_UNAUTHORIZED:
733 throw new AuthorizationException( "Not authorized " + reasonPhrase );
734
735 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
736 throw new AuthorizationException( "Not authorized by proxy " + reasonPhrase );
737
738 case HttpStatus.SC_NOT_FOUND:
739 result = false;
740 break;
741
742 case SC_TOO_MANY_REQUESTS:
743 return resourceExists( backoff( wait, resourceName ), resourceName );
744
745
746 default:
747 throw new TransferFailedException(
748 "Failed to transfer file: " + url + ". Return code is: " + statusCode + reasonPhrase );
749 }
750
751 EntityUtils.consume( response.getEntity() );
752 return result;
753 }
754 finally
755 {
756 response.close();
757 }
758 }
759 catch ( IOException e )
760 {
761 throw new TransferFailedException( e.getMessage(), e );
762 }
763 catch ( HttpException e )
764 {
765 throw new TransferFailedException( e.getMessage(), e );
766 }
767 catch ( InterruptedException e )
768 {
769 throw new TransferFailedException( e.getMessage(), e );
770 }
771
772 }
773
774 protected CloseableHttpResponse execute( HttpUriRequest httpMethod )
775 throws HttpException, IOException
776 {
777 setHeaders( httpMethod );
778 String userAgent = getUserAgent( httpMethod );
779 if ( userAgent != null )
780 {
781 httpMethod.setHeader( HTTP.USER_AGENT, userAgent );
782 }
783
784 RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
785
786 requestConfigBuilder.setCookieSpec( CookieSpecs.BROWSER_COMPATIBILITY );
787
788 Repository repo = getRepository();
789 ProxyInfo proxyInfo = getProxyInfo( repo.getProtocol(), repo.getHost() );
790 if ( proxyInfo != null )
791 {
792 HttpHost proxy = new HttpHost( proxyInfo.getHost(), proxyInfo.getPort() );
793 requestConfigBuilder.setProxy( proxy );
794 }
795
796 HttpMethodConfiguration config =
797 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( httpMethod );
798
799 if ( config != null )
800 {
801 ConfigurationUtils.copyConfig( config, requestConfigBuilder );
802 }
803 else
804 {
805 requestConfigBuilder.setSocketTimeout( getReadTimeout() );
806 }
807
808 localContext.setRequestConfig( requestConfigBuilder.build() );
809
810 if ( config != null && config.isUsePreemptive() )
811 {
812 HttpHost targetHost = new HttpHost( repo.getHost(), repo.getPort(), repo.getProtocol() );
813 AuthScope targetScope = new AuthScope( targetHost );
814
815 if ( credentialsProvider.getCredentials( targetScope ) != null )
816 {
817 BasicScheme targetAuth = new BasicScheme();
818 targetAuth.processChallenge( new BasicHeader( AUTH.WWW_AUTH, "BASIC preemptive" ) );
819 authCache.put( targetHost, targetAuth );
820 }
821 }
822
823 if ( proxyInfo != null )
824 {
825 if ( proxyInfo.getHost() != null )
826 {
827 HttpHost proxyHost = new HttpHost( proxyInfo.getHost(), proxyInfo.getPort() );
828 AuthScope proxyScope = new AuthScope( proxyHost );
829
830 String proxyUsername = proxyInfo.getUserName();
831 String proxyPassword = proxyInfo.getPassword();
832 String proxyNtlmHost = proxyInfo.getNtlmHost();
833 String proxyNtlmDomain = proxyInfo.getNtlmDomain();
834
835 if ( proxyUsername != null && proxyPassword != null )
836 {
837 Credentials creds;
838 if ( proxyNtlmHost != null || proxyNtlmDomain != null )
839 {
840 creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain );
841 }
842 else
843 {
844 creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword );
845 }
846
847 credentialsProvider.setCredentials( proxyScope, creds );
848 BasicScheme proxyAuth = new BasicScheme();
849 proxyAuth.processChallenge( new BasicHeader( AUTH.PROXY_AUTH, "BASIC preemptive" ) );
850 authCache.put( proxyHost, proxyAuth );
851 }
852 }
853 }
854
855 return CLIENT.execute( httpMethod, localContext );
856 }
857
858 protected void setHeaders( HttpUriRequest method )
859 {
860 HttpMethodConfiguration config =
861 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
862 if ( config == null || config.isUseDefaultHeaders() )
863 {
864
865 method.addHeader( "Cache-control", "no-cache" );
866 method.addHeader( "Cache-store", "no-store" );
867 method.addHeader( "Pragma", "no-cache" );
868 method.addHeader( "Expires", "0" );
869 method.addHeader( "Accept-Encoding", "gzip" );
870 method.addHeader( "User-Agent", DEFAULT_USER_AGENT );
871 }
872
873 if ( httpHeaders != null )
874 {
875 for ( Map.Entry<Object, Object> entry : httpHeaders.entrySet() )
876 {
877 method.addHeader( (String) entry.getKey(), (String) entry.getValue() );
878 }
879 }
880
881 Header[] headers = config == null ? null : config.asRequestHeaders();
882 if ( headers != null )
883 {
884 for ( Header header : headers )
885 {
886 method.addHeader( header );
887 }
888 }
889 }
890
891 protected String getUserAgent( HttpUriRequest method )
892 {
893 if ( httpHeaders != null )
894 {
895 String value = (String) httpHeaders.get( "User-Agent" );
896 if ( value != null )
897 {
898 return value;
899 }
900 }
901 HttpMethodConfiguration config =
902 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
903
904 if ( config != null )
905 {
906 return (String) config.getHeaders().get( "User-Agent" );
907 }
908 return null;
909 }
910
911
912
913
914
915
916
917
918 protected String getURL( Repository repository )
919 {
920 return repository.getUrl();
921 }
922
923 public HttpConfiguration getHttpConfiguration()
924 {
925 return httpConfiguration;
926 }
927
928 public void setHttpConfiguration( HttpConfiguration httpConfiguration )
929 {
930 this.httpConfiguration = httpConfiguration;
931 }
932
933 public void fillInputData( InputData inputData )
934 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
935 {
936 fillInputData( getInitialBackoffSeconds(), inputData );
937 }
938
939 private void fillInputData( int wait, InputData inputData )
940 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
941 {
942 Resource resource = inputData.getResource();
943
944 String repositoryUrl = getRepository().getUrl();
945 String url = repositoryUrl + ( repositoryUrl.endsWith( "/" ) ? "" : "/" ) + resource.getName();
946 HttpGet getMethod = new HttpGet( url );
947 long timestamp = resource.getLastModified();
948 if ( timestamp > 0 )
949 {
950 SimpleDateFormat fmt = new SimpleDateFormat( "EEE, dd-MMM-yy HH:mm:ss zzz", Locale.US );
951 fmt.setTimeZone( GMT_TIME_ZONE );
952 Header hdr = new BasicHeader( "If-Modified-Since", fmt.format( new Date( timestamp ) ) );
953 fireTransferDebug( "sending ==> " + hdr + "(" + timestamp + ")" );
954 getMethod.addHeader( hdr );
955 }
956
957 try
958 {
959 CloseableHttpResponse response = execute( getMethod );
960 closeable = response;
961 int statusCode = response.getStatusLine().getStatusCode();
962
963 String reasonPhrase = ", ReasonPhrase:" + response.getStatusLine().getReasonPhrase() + ".";
964
965 fireTransferDebug( url + " - Status code: " + statusCode + reasonPhrase );
966
967 switch ( statusCode )
968 {
969 case HttpStatus.SC_OK:
970 break;
971
972 case HttpStatus.SC_NOT_MODIFIED:
973
974 return;
975 case HttpStatus.SC_FORBIDDEN:
976 fireSessionConnectionRefused();
977 throw new AuthorizationException( "Access denied to: " + url + " " + reasonPhrase );
978
979 case HttpStatus.SC_UNAUTHORIZED:
980 fireSessionConnectionRefused();
981 throw new AuthorizationException( "Not authorized " + reasonPhrase );
982
983 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
984 fireSessionConnectionRefused();
985 throw new AuthorizationException( "Not authorized by proxy " + reasonPhrase );
986
987 case HttpStatus.SC_NOT_FOUND:
988 throw new ResourceDoesNotExistException( "File: " + url + " " + reasonPhrase );
989
990 case SC_TOO_MANY_REQUESTS:
991 fillInputData( backoff( wait, url ), inputData );
992 break;
993
994
995 default:
996 {
997 cleanupGetTransfer( resource );
998 TransferFailedException e = new TransferFailedException(
999 "Failed to transfer file: " + url + ". Return code is: " + statusCode + " " + reasonPhrase );
1000 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
1001 throw e;
1002 }
1003 }
1004
1005 Header contentLengthHeader = response.getFirstHeader( "Content-Length" );
1006
1007 if ( contentLengthHeader != null )
1008 {
1009 try
1010 {
1011 long contentLength = Long.parseLong( contentLengthHeader.getValue() );
1012
1013 resource.setContentLength( contentLength );
1014 }
1015 catch ( NumberFormatException e )
1016 {
1017 fireTransferDebug(
1018 "error parsing content length header '" + contentLengthHeader.getValue() + "' " + e );
1019 }
1020 }
1021
1022 Header lastModifiedHeader = response.getFirstHeader( "Last-Modified" );
1023 if ( lastModifiedHeader != null )
1024 {
1025 Date lastModified = DateUtils.parseDate( lastModifiedHeader.getValue() );
1026 if ( lastModified != null )
1027 {
1028 resource.setLastModified( lastModified.getTime() );
1029 fireTransferDebug( "last-modified = " + lastModifiedHeader.getValue() +
1030 " (" + lastModified.getTime() + ")" );
1031 }
1032 }
1033
1034 HttpEntity entity = response.getEntity();
1035 if ( entity != null )
1036 {
1037 inputData.setInputStream( entity.getContent() );
1038 }
1039 }
1040 catch ( IOException e )
1041 {
1042 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
1043
1044 throw new TransferFailedException( e.getMessage(), e );
1045 }
1046 catch ( HttpException e )
1047 {
1048 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
1049
1050 throw new TransferFailedException( e.getMessage(), e );
1051 }
1052 catch ( InterruptedException e )
1053 {
1054 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
1055
1056 throw new TransferFailedException( e.getMessage(), e );
1057 }
1058
1059 }
1060
1061 protected void cleanupGetTransfer( Resource resource )
1062 {
1063 if ( closeable != null )
1064 {
1065 try
1066 {
1067 closeable.close();
1068 }
1069 catch ( IOException ignore )
1070 {
1071 }
1072
1073 }
1074 }
1075
1076
1077 @Override
1078 public void putFromStream( InputStream stream, String destination )
1079 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
1080 {
1081 putFromStream( stream, destination, -1, -1 );
1082 }
1083
1084 @Override
1085 protected void putFromStream( InputStream stream, Resource resource )
1086 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
1087 {
1088 putFromStream( stream, resource.getName(), -1, -1 );
1089 }
1090
1091 public Properties getHttpHeaders()
1092 {
1093 return httpHeaders;
1094 }
1095
1096 public void setHttpHeaders( Properties httpHeaders )
1097 {
1098 this.httpHeaders = httpHeaders;
1099 }
1100
1101 @Override
1102 public void fillOutputData( OutputData outputData )
1103 throws TransferFailedException
1104 {
1105
1106 throw new IllegalStateException( "this wagon http client must not use fillOutputData" );
1107 }
1108
1109 public int getInitialBackoffSeconds()
1110 {
1111 return initialBackoffSeconds;
1112 }
1113
1114 public void setInitialBackoffSeconds( int initialBackoffSeconds )
1115 {
1116 this.initialBackoffSeconds = initialBackoffSeconds;
1117 }
1118
1119 public static int getMaxBackoffWaitSeconds()
1120 {
1121 return maxBackoffWaitSeconds;
1122 }
1123 }