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.http.Header;
23 import org.apache.http.HttpEntity;
24 import org.apache.http.HttpException;
25 import org.apache.http.HttpHost;
26 import org.apache.http.HttpResponse;
27 import org.apache.http.HttpStatus;
28 import org.apache.http.auth.AuthSchemeProvider;
29 import org.apache.http.auth.AuthScope;
30 import org.apache.http.auth.ChallengeState;
31 import org.apache.http.auth.Credentials;
32 import org.apache.http.auth.NTCredentials;
33 import org.apache.http.auth.UsernamePasswordCredentials;
34 import org.apache.http.client.AuthCache;
35 import org.apache.http.client.CredentialsProvider;
36 import org.apache.http.client.HttpRequestRetryHandler;
37 import org.apache.http.client.ServiceUnavailableRetryStrategy;
38 import org.apache.http.client.config.AuthSchemes;
39 import org.apache.http.client.config.CookieSpecs;
40 import org.apache.http.client.config.RequestConfig;
41 import org.apache.http.client.methods.CloseableHttpResponse;
42 import org.apache.http.client.methods.HttpGet;
43 import org.apache.http.client.methods.HttpHead;
44 import org.apache.http.client.methods.HttpPut;
45 import org.apache.http.client.methods.HttpUriRequest;
46 import org.apache.http.client.protocol.HttpClientContext;
47 import org.apache.http.client.utils.DateUtils;
48 import org.apache.http.config.Registry;
49 import org.apache.http.config.RegistryBuilder;
50 import org.apache.http.conn.HttpClientConnectionManager;
51 import org.apache.http.conn.socket.ConnectionSocketFactory;
52 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
53 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
54 import org.apache.http.conn.ssl.SSLContextBuilder;
55 import org.apache.http.conn.ssl.SSLInitializationException;
56 import org.apache.http.entity.AbstractHttpEntity;
57 import org.apache.http.impl.auth.BasicScheme;
58 import org.apache.http.impl.auth.BasicSchemeFactory;
59 import org.apache.http.impl.auth.DigestSchemeFactory;
60 import org.apache.http.impl.auth.NTLMSchemeFactory;
61 import org.apache.http.impl.client.BasicAuthCache;
62 import org.apache.http.impl.client.BasicCredentialsProvider;
63 import org.apache.http.impl.client.CloseableHttpClient;
64 import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
65 import org.apache.http.impl.client.DefaultServiceUnavailableRetryStrategy;
66 import org.apache.http.impl.client.HttpClientBuilder;
67 import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
68 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
69 import org.apache.http.message.BasicHeader;
70 import org.apache.http.protocol.HTTP;
71 import org.apache.http.util.EntityUtils;
72 import org.apache.maven.wagon.InputData;
73 import org.apache.maven.wagon.OutputData;
74 import org.apache.maven.wagon.PathUtils;
75 import org.apache.maven.wagon.ResourceDoesNotExistException;
76 import org.apache.maven.wagon.StreamWagon;
77 import org.apache.maven.wagon.TransferFailedException;
78 import org.apache.maven.wagon.Wagon;
79 import org.apache.maven.wagon.authorization.AuthorizationException;
80 import org.apache.maven.wagon.events.TransferEvent;
81 import org.apache.maven.wagon.proxy.ProxyInfo;
82 import org.apache.maven.wagon.repository.Repository;
83 import org.apache.maven.wagon.resource.Resource;
84 import org.codehaus.plexus.util.StringUtils;
85
86 import javax.net.ssl.HttpsURLConnection;
87 import javax.net.ssl.SSLContext;
88 import java.io.Closeable;
89 import java.io.File;
90 import java.io.FileInputStream;
91 import java.io.IOException;
92 import java.io.InputStream;
93 import java.io.OutputStream;
94 import java.io.RandomAccessFile;
95 import java.nio.ByteBuffer;
96 import java.nio.channels.Channels;
97 import java.nio.channels.ReadableByteChannel;
98 import java.nio.charset.StandardCharsets;
99 import java.text.SimpleDateFormat;
100 import java.util.ArrayList;
101 import java.util.Collection;
102 import java.util.Date;
103 import java.util.List;
104 import java.util.Locale;
105 import java.util.Map;
106 import java.util.Properties;
107 import java.util.TimeZone;
108 import java.util.concurrent.TimeUnit;
109
110 import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatAuthorizationMessage;
111 import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatResourceDoesNotExistMessage;
112 import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatTransferDebugMessage;
113 import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatTransferFailedMessage;
114
115
116
117
118
119 public abstract class AbstractHttpClientWagon
120 extends StreamWagon
121 {
122 private final class WagonHttpEntity
123 extends AbstractHttpEntity
124 {
125 private final Resource resource;
126
127 private final Wagon wagon;
128
129 private InputStream stream;
130
131 private File source;
132
133 private long length = -1;
134
135 private boolean repeatable;
136
137 private WagonHttpEntity( final InputStream stream, final Resource resource, final Wagon wagon,
138 final File source )
139 throws TransferFailedException
140 {
141 if ( source != null )
142 {
143 this.source = source;
144 this.repeatable = true;
145 }
146 else
147 {
148 this.stream = stream;
149 this.repeatable = false;
150 }
151 this.resource = resource;
152 this.length = resource == null ? -1 : resource.getContentLength();
153
154 this.wagon = wagon;
155 }
156
157 public long getContentLength()
158 {
159 return length;
160 }
161
162 public InputStream getContent()
163 throws IOException, IllegalStateException
164 {
165 if ( this.source != null )
166 {
167 return new FileInputStream( this.source );
168 }
169 return stream;
170 }
171
172 public boolean isRepeatable()
173 {
174 return repeatable;
175 }
176
177 public void writeTo( final OutputStream output )
178 throws IOException
179 {
180 if ( output == null )
181 {
182 throw new NullPointerException( "output cannot be null" );
183 }
184 TransferEvent transferEvent =
185 new TransferEvent( wagon, resource, TransferEvent.TRANSFER_PROGRESS, TransferEvent.REQUEST_PUT );
186 transferEvent.setTimestamp( System.currentTimeMillis() );
187
188 try ( ReadableByteChannel input = ( this.source != null )
189 ? new RandomAccessFile( this.source, "r" ).getChannel()
190 : Channels.newChannel( stream ) )
191 {
192 ByteBuffer buffer = ByteBuffer.allocate( getBufferCapacityForTransfer( this.length ) );
193 int halfBufferCapacity = buffer.capacity() / 2;
194
195 long remaining = this.length < 0L ? Long.MAX_VALUE : this.length;
196 while ( remaining > 0L )
197 {
198 int read = input.read( buffer );
199 if ( read == -1 )
200 {
201
202 if ( buffer.position() != 0 )
203 {
204 buffer.flip();
205 fireTransferProgress( transferEvent, buffer.array(), buffer.limit() );
206 output.write( buffer.array(), 0, buffer.limit() );
207 buffer.clear();
208 }
209
210 break;
211 }
212
213
214
215 if ( buffer.position() < halfBufferCapacity )
216 {
217 continue;
218 }
219
220 buffer.flip();
221 fireTransferProgress( transferEvent, buffer.array(), buffer.limit() );
222 output.write( buffer.array(), 0, buffer.limit() );
223 remaining -= buffer.limit();
224 buffer.clear();
225
226 }
227 output.flush();
228 }
229 }
230
231 public boolean isStreaming()
232 {
233 return true;
234 }
235 }
236
237 private static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone( "GMT" );
238
239
240
241
242
243 private static boolean persistentPool =
244 Boolean.valueOf( System.getProperty( "maven.wagon.http.pool", "true" ) );
245
246
247
248
249
250 private static final boolean SSL_INSECURE =
251 Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.insecure", "false" ) );
252
253
254
255
256
257 private static final boolean IGNORE_SSL_VALIDITY_DATES =
258 Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.ignore.validity.dates", "false" ) );
259
260
261
262
263
264 private static final boolean SSL_ALLOW_ALL =
265 Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.allowall", "false" ) );
266
267
268
269
270
271
272 private static final int MAX_CONN_PER_ROUTE =
273 Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxPerRoute", "20" ) );
274
275
276
277
278
279 private static final int MAX_CONN_TOTAL =
280 Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxTotal", "40" ) );
281
282
283
284
285
286
287
288
289 private static final long CONN_TTL =
290 Long.getLong( "maven.wagon.httpconnectionManager.ttlSeconds", 300L );
291
292
293
294
295 private static HttpClientConnectionManager httpClientConnectionManager = createConnManager();
296
297
298
299
300
301 protected static final int SC_TOO_MANY_REQUESTS = 429;
302
303
304
305
306
307
308
309
310
311
312
313
314 private int initialBackoffSeconds =
315 Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.backoffSeconds", "5" ) );
316
317
318
319
320
321
322
323 private static final int MAX_BACKOFF_WAIT_SECONDS =
324 Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxBackoffSeconds", "180" ) );
325
326 protected int backoff( int wait, String url )
327 throws InterruptedException, TransferFailedException
328 {
329 TimeUnit.SECONDS.sleep( wait );
330 int nextWait = wait * 2;
331 if ( nextWait >= getMaxBackoffWaitSeconds() )
332 {
333 throw new TransferFailedException(
334 "Waited too long to access: " + url + ". Return code is: " + SC_TOO_MANY_REQUESTS );
335 }
336 return nextWait;
337 }
338
339 @SuppressWarnings( "checkstyle:linelength" )
340 private static PoolingHttpClientConnectionManager createConnManager()
341 {
342
343 String sslProtocolsStr = System.getProperty( "https.protocols" );
344 String cipherSuitesStr = System.getProperty( "https.cipherSuites" );
345 String[] sslProtocols = sslProtocolsStr != null ? sslProtocolsStr.split( " *, *" ) : null;
346 String[] cipherSuites = cipherSuitesStr != null ? cipherSuitesStr.split( " *, *" ) : null;
347
348 SSLConnectionSocketFactory sslConnectionSocketFactory;
349 if ( SSL_INSECURE )
350 {
351 try
352 {
353 SSLContext sslContext = new SSLContextBuilder().useSSL().loadTrustMaterial( null,
354 new RelaxedTrustStrategy(
355 IGNORE_SSL_VALIDITY_DATES ) ).build();
356 sslConnectionSocketFactory = new SSLConnectionSocketFactory( sslContext, sslProtocols, cipherSuites,
357 SSL_ALLOW_ALL
358 ? SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
359 : SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER );
360 }
361 catch ( Exception ex )
362 {
363 throw new SSLInitializationException( ex.getMessage(), ex );
364 }
365 }
366 else
367 {
368 sslConnectionSocketFactory =
369 new SSLConnectionSocketFactory( HttpsURLConnection.getDefaultSSLSocketFactory(), sslProtocols,
370 cipherSuites,
371 SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER );
372 }
373
374 Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register( "http",
375 PlainConnectionSocketFactory.INSTANCE ).register(
376 "https", sslConnectionSocketFactory ).build();
377
378 PoolingHttpClientConnectionManager connManager =
379 new PoolingHttpClientConnectionManager( registry, null, null, null, CONN_TTL, TimeUnit.SECONDS );
380 if ( persistentPool )
381 {
382 connManager.setDefaultMaxPerRoute( MAX_CONN_PER_ROUTE );
383 connManager.setMaxTotal( MAX_CONN_TOTAL );
384 }
385 else
386 {
387 connManager.setMaxTotal( 1 );
388 }
389 return connManager;
390 }
391
392
393
394
395
396
397
398
399
400 private static final String RETRY_HANDLER_CLASS =
401 System.getProperty( "maven.wagon.http.retryHandler.class", "standard" );
402
403
404
405
406
407
408
409
410 private static final boolean RETRY_HANDLER_REQUEST_SENT_ENABLED =
411 Boolean.getBoolean( "maven.wagon.http.retryHandler.requestSentEnabled" );
412
413
414
415
416
417
418
419 private static final int RETRY_HANDLER_COUNT =
420 Integer.getInteger( "maven.wagon.http.retryHandler.count", 3 );
421
422
423
424
425
426
427
428 private static final String RETRY_HANDLER_EXCEPTIONS =
429 System.getProperty( "maven.wagon.http.retryHandler.nonRetryableClasses" );
430
431 private static HttpRequestRetryHandler createRetryHandler()
432 {
433 switch ( RETRY_HANDLER_CLASS )
434 {
435 case "default":
436 if ( StringUtils.isEmpty( RETRY_HANDLER_EXCEPTIONS ) )
437 {
438 return new DefaultHttpRequestRetryHandler(
439 RETRY_HANDLER_COUNT, RETRY_HANDLER_REQUEST_SENT_ENABLED );
440 }
441 return new DefaultHttpRequestRetryHandler(
442 RETRY_HANDLER_COUNT, RETRY_HANDLER_REQUEST_SENT_ENABLED, getNonRetryableExceptions() )
443 {
444 };
445 case "standard":
446 return new StandardHttpRequestRetryHandler( RETRY_HANDLER_COUNT, RETRY_HANDLER_REQUEST_SENT_ENABLED );
447 default:
448 try
449 {
450 final ClassLoader classLoader = AbstractHttpClientWagon.class.getClassLoader();
451 return HttpRequestRetryHandler.class.cast( classLoader.loadClass( RETRY_HANDLER_CLASS )
452 .getConstructor().newInstance() );
453 }
454 catch ( final Exception e )
455 {
456 throw new IllegalArgumentException( e );
457 }
458 }
459 }
460
461
462
463
464
465
466
467 private static final String SERVICE_UNAVAILABLE_RETRY_STRATEGY_CLASS =
468 System.getProperty( "maven.wagon.http.serviceUnavailableRetryStrategy.class", "none" );
469
470
471
472
473
474 private static final int SERVICE_UNAVAILABLE_RETRY_STRATEGY_RETRY_INTERVAL =
475 Integer.getInteger( "maven.wagon.http.serviceUnavailableRetryStrategy.retryInterval", 1000 );
476
477
478
479
480
481 private static final int SERVICE_UNAVAILABLE_RETRY_STRATEGY_MAX_RETRIES =
482 Integer.getInteger( "maven.wagon.http.serviceUnavailableRetryStrategy.maxRetries", 5 );
483
484 private static ServiceUnavailableRetryStrategy createServiceUnavailableRetryStrategy()
485 {
486 switch ( SERVICE_UNAVAILABLE_RETRY_STRATEGY_CLASS )
487 {
488 case "none": return null;
489 case "default":
490 return new DefaultServiceUnavailableRetryStrategy(
491 SERVICE_UNAVAILABLE_RETRY_STRATEGY_MAX_RETRIES, SERVICE_UNAVAILABLE_RETRY_STRATEGY_RETRY_INTERVAL );
492 case "standard":
493 return new StandardServiceUnavailableRetryStrategy(
494 SERVICE_UNAVAILABLE_RETRY_STRATEGY_MAX_RETRIES, SERVICE_UNAVAILABLE_RETRY_STRATEGY_RETRY_INTERVAL );
495 default:
496 try
497 {
498 final ClassLoader classLoader = AbstractHttpClientWagon.class.getClassLoader();
499 return ServiceUnavailableRetryStrategy.class.cast(
500 classLoader.loadClass( SERVICE_UNAVAILABLE_RETRY_STRATEGY_CLASS )
501 .getConstructor().newInstance() );
502 }
503 catch ( final Exception e )
504 {
505 throw new IllegalArgumentException( e );
506 }
507 }
508 }
509
510 private static Registry<AuthSchemeProvider> createAuthSchemeRegistry()
511 {
512 return RegistryBuilder.<AuthSchemeProvider>create()
513 .register( AuthSchemes.BASIC, new BasicSchemeFactory( StandardCharsets.UTF_8 ) )
514 .register( AuthSchemes.DIGEST, new DigestSchemeFactory( StandardCharsets.UTF_8 ) )
515 .register( AuthSchemes.NTLM, new NTLMSchemeFactory() )
516 .build();
517 }
518
519 private static Collection<Class<? extends IOException>> getNonRetryableExceptions()
520 {
521 final List<Class<? extends IOException>> exceptions = new ArrayList<>();
522 final ClassLoader loader = AbstractHttpClientWagon.class.getClassLoader();
523 for ( final String ex : RETRY_HANDLER_EXCEPTIONS.split( "," ) )
524 {
525 try
526 {
527 exceptions.add( ( Class<? extends IOException> ) loader.loadClass( ex ) );
528 }
529 catch ( final ClassNotFoundException e )
530 {
531 throw new IllegalArgumentException( e );
532 }
533 }
534 return exceptions;
535 }
536
537 private static CloseableHttpClient httpClient = createClient();
538
539 private static CloseableHttpClient createClient()
540 {
541 return HttpClientBuilder.create()
542 .useSystemProperties()
543 .disableConnectionState()
544 .setConnectionManager( httpClientConnectionManager )
545 .setRetryHandler( createRetryHandler() )
546 .setServiceUnavailableRetryStrategy( createServiceUnavailableRetryStrategy() )
547 .setDefaultAuthSchemeRegistry( createAuthSchemeRegistry() )
548 .build();
549 }
550
551 private CredentialsProvider credentialsProvider;
552
553 private AuthCache authCache;
554
555 private Closeable closeable;
556
557
558
559
560
561 private Properties httpHeaders;
562
563
564
565
566 private HttpConfiguration httpConfiguration;
567
568
569
570
571
572 private BasicAuthScope basicAuth;
573
574
575
576
577
578 private BasicAuthScope proxyAuth;
579
580 public void openConnectionInternal()
581 {
582 repository.setUrl( getURL( repository ) );
583
584 credentialsProvider = new BasicCredentialsProvider();
585 authCache = new BasicAuthCache();
586
587 if ( authenticationInfo != null )
588 {
589
590 String username = authenticationInfo.getUserName();
591 String password = authenticationInfo.getPassword();
592
593 if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) )
594 {
595 Credentials creds = new UsernamePasswordCredentials( username, password );
596
597 String host = getRepository().getHost();
598 int port = getRepository().getPort();
599
600 credentialsProvider.setCredentials( getBasicAuthScope().getScope( host, port ), creds );
601 }
602 }
603
604 ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
605 if ( proxyInfo != null )
606 {
607 String proxyUsername = proxyInfo.getUserName();
608 String proxyPassword = proxyInfo.getPassword();
609 String proxyHost = proxyInfo.getHost();
610 String proxyNtlmHost = proxyInfo.getNtlmHost();
611 String proxyNtlmDomain = proxyInfo.getNtlmDomain();
612 if ( proxyHost != null )
613 {
614 if ( proxyUsername != null && proxyPassword != null )
615 {
616 Credentials creds;
617 if ( proxyNtlmHost != null || proxyNtlmDomain != null )
618 {
619 creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain );
620 }
621 else
622 {
623 creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword );
624 }
625
626 int proxyPort = proxyInfo.getPort();
627
628 AuthScope authScope = getProxyBasicAuthScope().getScope( proxyHost, proxyPort );
629 credentialsProvider.setCredentials( authScope, creds );
630 }
631 }
632 }
633 }
634
635 public void closeConnection()
636 {
637 if ( !persistentPool )
638 {
639 httpClientConnectionManager.closeIdleConnections( 0, TimeUnit.MILLISECONDS );
640 }
641
642 if ( authCache != null )
643 {
644 authCache.clear();
645 authCache = null;
646 }
647
648 if ( credentialsProvider != null )
649 {
650 credentialsProvider.clear();
651 credentialsProvider = null;
652 }
653 }
654
655 public static CloseableHttpClient getHttpClient()
656 {
657 return httpClient;
658 }
659
660 public static void setPersistentPool( boolean persistentPool )
661 {
662 persistentPool = persistentPool;
663 }
664
665 public static void setPoolingHttpClientConnectionManager(
666 PoolingHttpClientConnectionManager poolingHttpClientConnectionManager )
667 {
668 httpClientConnectionManager = poolingHttpClientConnectionManager;
669 httpClient = createClient();
670 }
671
672 public void put( File source, String resourceName )
673 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
674 {
675 Resource resource = new Resource( resourceName );
676
677 firePutInitiated( resource, source );
678
679 resource.setContentLength( source.length() );
680
681 resource.setLastModified( source.lastModified() );
682
683 put( null, resource, source );
684 }
685
686 public void putFromStream( final InputStream stream, String destination, long contentLength, long lastModified )
687 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
688 {
689 Resource resource = new Resource( destination );
690
691 firePutInitiated( resource, null );
692
693 resource.setContentLength( contentLength );
694
695 resource.setLastModified( lastModified );
696
697 put( stream, resource, null );
698 }
699
700 private void put( final InputStream stream, Resource resource, File source )
701 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
702 {
703 put( resource, source, new WagonHttpEntity( stream, resource, this, source ) );
704 }
705
706 private void put( Resource resource, File source, HttpEntity httpEntity )
707 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
708 {
709 put( resource, source, httpEntity, buildUrl( resource ) );
710 }
711
712
713
714
715
716
717
718 private String buildUrl( Resource resource )
719 {
720 return buildUrl( resource.getName() );
721 }
722
723
724
725
726
727
728
729 private String buildUrl( String resourceName )
730 {
731 return EncodingUtil.encodeURLToString( getRepository().getUrl(), resourceName );
732 }
733
734 private void put( Resource resource, File source, HttpEntity httpEntity, String url )
735 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
736 {
737 put( getInitialBackoffSeconds(), resource, source, httpEntity, url );
738 }
739
740
741 private void put( int wait, Resource resource, File source, HttpEntity httpEntity, String url )
742 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
743 {
744
745
746 try
747 {
748 mkdirs( PathUtils.dirname( resource.getName() ) );
749 }
750 catch ( HttpException he )
751 {
752 fireTransferError( resource, he, TransferEvent.REQUEST_PUT );
753 }
754 catch ( IOException e )
755 {
756 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
757 }
758
759
760
761
762 Repository repo = getRepository();
763 HttpHost targetHost = new HttpHost( repo.getHost(), repo.getPort(), repo.getProtocol() );
764 AuthScope targetScope = getBasicAuthScope().getScope( targetHost );
765
766 if ( credentialsProvider.getCredentials( targetScope ) != null )
767 {
768 BasicScheme targetAuth = new BasicScheme();
769 authCache.put( targetHost, targetAuth );
770 }
771
772 HttpPut putMethod = new HttpPut( url );
773
774 firePutStarted( resource, source );
775
776 try
777 {
778 putMethod.setEntity( httpEntity );
779
780 CloseableHttpResponse response = execute( putMethod );
781 try
782 {
783 fireTransferDebug( formatTransferDebugMessage( url, response.getStatusLine().getStatusCode(),
784 response.getStatusLine().getReasonPhrase(), getProxyInfo() ) );
785 int statusCode = response.getStatusLine().getStatusCode();
786
787
788 switch ( statusCode )
789 {
790
791 case HttpStatus.SC_OK:
792 case HttpStatus.SC_CREATED:
793 case HttpStatus.SC_ACCEPTED:
794 case HttpStatus.SC_NO_CONTENT:
795 break;
796
797
798 case HttpStatus.SC_MOVED_PERMANENTLY:
799 case HttpStatus.SC_MOVED_TEMPORARILY:
800 case HttpStatus.SC_SEE_OTHER:
801 put( resource, source, httpEntity, calculateRelocatedUrl( response ) );
802 return;
803
804 case HttpStatus.SC_FORBIDDEN:
805 fireSessionConnectionRefused();
806 throw new AuthorizationException( formatAuthorizationMessage( url,
807 response.getStatusLine().getStatusCode(),
808 response.getStatusLine().getReasonPhrase(), getProxyInfo() ) );
809
810 case HttpStatus.SC_NOT_FOUND:
811 throw new ResourceDoesNotExistException( formatResourceDoesNotExistMessage( url,
812 response.getStatusLine().getStatusCode(),
813 response.getStatusLine().getReasonPhrase(), getProxyInfo() ) );
814
815 case SC_TOO_MANY_REQUESTS:
816 put( backoff( wait, url ), resource, source, httpEntity, url );
817 break;
818
819 default:
820 TransferFailedException e = new TransferFailedException( formatTransferFailedMessage( url,
821 response.getStatusLine().getStatusCode(),
822 response.getStatusLine().getReasonPhrase(), getProxyInfo() ) );
823 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
824 throw e;
825 }
826
827 firePutCompleted( resource, source );
828
829 EntityUtils.consume( response.getEntity() );
830 }
831 finally
832 {
833 response.close();
834 }
835 }
836 catch ( IOException | HttpException | InterruptedException e )
837 {
838 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
839
840 throw new TransferFailedException( formatTransferFailedMessage( url, getProxyInfo() ), e );
841 }
842
843 }
844
845 protected String calculateRelocatedUrl( HttpResponse response )
846 {
847 Header locationHeader = response.getFirstHeader( "Location" );
848 String locationField = locationHeader.getValue();
849
850 return locationField.startsWith( "http" ) ? locationField : getURL( getRepository() ) + '/' + locationField;
851 }
852
853 protected void mkdirs( String dirname )
854 throws HttpException, IOException
855 {
856
857 }
858
859 public boolean resourceExists( String resourceName )
860 throws TransferFailedException, AuthorizationException
861 {
862 return resourceExists( getInitialBackoffSeconds(), resourceName );
863 }
864
865
866 private boolean resourceExists( int wait, String resourceName )
867 throws TransferFailedException, AuthorizationException
868 {
869 String url = buildUrl( resourceName );
870 HttpHead headMethod = new HttpHead( url );
871 try
872 {
873 CloseableHttpResponse response = execute( headMethod );
874 try
875 {
876 int statusCode = response.getStatusLine().getStatusCode();
877 boolean result;
878 switch ( statusCode )
879 {
880 case HttpStatus.SC_OK:
881 result = true;
882 break;
883 case HttpStatus.SC_NOT_MODIFIED:
884 result = true;
885 break;
886
887 case HttpStatus.SC_FORBIDDEN:
888 case HttpStatus.SC_UNAUTHORIZED:
889 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
890 throw new AuthorizationException( formatAuthorizationMessage( url,
891 response.getStatusLine().getStatusCode(),
892 response.getStatusLine().getReasonPhrase(), getProxyInfo() ) );
893
894 case HttpStatus.SC_NOT_FOUND:
895 result = false;
896 break;
897
898 case SC_TOO_MANY_REQUESTS:
899 return resourceExists( backoff( wait, resourceName ), resourceName );
900
901
902 default:
903 throw new TransferFailedException( formatTransferFailedMessage( url,
904 response.getStatusLine().getStatusCode(),
905 response.getStatusLine().getReasonPhrase(), getProxyInfo() ) );
906 }
907
908 EntityUtils.consume( response.getEntity() );
909 return result;
910 }
911 finally
912 {
913 response.close();
914 }
915 }
916 catch ( IOException | HttpException | InterruptedException e )
917 {
918 throw new TransferFailedException( formatTransferFailedMessage( url, getProxyInfo() ), e );
919 }
920
921 }
922
923 protected CloseableHttpResponse execute( HttpUriRequest httpMethod )
924 throws HttpException, IOException
925 {
926 setHeaders( httpMethod );
927 String userAgent = getUserAgent( httpMethod );
928 if ( userAgent != null )
929 {
930 httpMethod.setHeader( HTTP.USER_AGENT, userAgent );
931 }
932
933 RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
934
935 requestConfigBuilder.setCookieSpec( CookieSpecs.BROWSER_COMPATIBILITY );
936
937 Repository repo = getRepository();
938 ProxyInfo proxyInfo = getProxyInfo( repo.getProtocol(), repo.getHost() );
939 if ( proxyInfo != null )
940 {
941 HttpHost proxy = new HttpHost( proxyInfo.getHost(), proxyInfo.getPort() );
942 requestConfigBuilder.setProxy( proxy );
943 }
944
945 HttpMethodConfiguration config =
946 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( httpMethod );
947
948 if ( config != null )
949 {
950 ConfigurationUtils.copyConfig( config, requestConfigBuilder );
951 }
952 else
953 {
954 requestConfigBuilder.setSocketTimeout( getReadTimeout() );
955 if ( httpMethod instanceof HttpPut )
956 {
957 requestConfigBuilder.setExpectContinueEnabled( true );
958 }
959 }
960
961 if ( httpMethod instanceof HttpPut )
962 {
963 requestConfigBuilder.setRedirectsEnabled( false );
964 }
965
966 HttpClientContext localContext = HttpClientContext.create();
967 localContext.setCredentialsProvider( credentialsProvider );
968 localContext.setAuthCache( authCache );
969 localContext.setRequestConfig( requestConfigBuilder.build() );
970
971 if ( config != null && config.isUsePreemptive() )
972 {
973 HttpHost targetHost = new HttpHost( repo.getHost(), repo.getPort(), repo.getProtocol() );
974 AuthScope targetScope = getBasicAuthScope().getScope( targetHost );
975
976 if ( credentialsProvider.getCredentials( targetScope ) != null )
977 {
978 BasicScheme targetAuth = new BasicScheme();
979 authCache.put( targetHost, targetAuth );
980 }
981 }
982
983 if ( proxyInfo != null )
984 {
985 if ( proxyInfo.getHost() != null )
986 {
987 HttpHost proxyHost = new HttpHost( proxyInfo.getHost(), proxyInfo.getPort() );
988 AuthScope proxyScope = getProxyBasicAuthScope().getScope( proxyHost );
989
990 if ( credentialsProvider.getCredentials( proxyScope ) != null )
991 {
992
993
994
995
996 BasicScheme proxyAuth = new BasicScheme( ChallengeState.PROXY );
997 authCache.put( proxyHost, proxyAuth );
998 }
999 }
1000 }
1001
1002 return httpClient.execute( httpMethod, localContext );
1003 }
1004
1005 public void setHeaders( HttpUriRequest method )
1006 {
1007 HttpMethodConfiguration config =
1008 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
1009 if ( config == null || config.isUseDefaultHeaders() )
1010 {
1011
1012 method.addHeader( "Cache-control", "no-cache" );
1013 method.addHeader( "Cache-store", "no-store" );
1014 method.addHeader( "Pragma", "no-cache" );
1015 }
1016
1017 if ( httpHeaders != null )
1018 {
1019 for ( Map.Entry<Object, Object> entry : httpHeaders.entrySet() )
1020 {
1021 method.setHeader( (String) entry.getKey(), (String) entry.getValue() );
1022 }
1023 }
1024
1025 Header[] headers = config == null ? null : config.asRequestHeaders();
1026 if ( headers != null )
1027 {
1028 for ( Header header : headers )
1029 {
1030 method.setHeader( header );
1031 }
1032 }
1033
1034 Header userAgentHeader = method.getFirstHeader( HTTP.USER_AGENT );
1035 if ( userAgentHeader == null )
1036 {
1037 String userAgent = getUserAgent( method );
1038 if ( userAgent != null )
1039 {
1040 method.setHeader( HTTP.USER_AGENT, userAgent );
1041 }
1042 }
1043 }
1044
1045 protected String getUserAgent( HttpUriRequest method )
1046 {
1047 if ( httpHeaders != null )
1048 {
1049 String value = (String) httpHeaders.get( HTTP.USER_AGENT );
1050 if ( value != null )
1051 {
1052 return value;
1053 }
1054 }
1055 HttpMethodConfiguration config =
1056 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
1057
1058 if ( config != null )
1059 {
1060 return (String) config.getHeaders().get( HTTP.USER_AGENT );
1061 }
1062 return null;
1063 }
1064
1065
1066
1067
1068
1069
1070
1071
1072 protected String getURL( Repository repository )
1073 {
1074 return repository.getUrl();
1075 }
1076
1077 public HttpConfiguration getHttpConfiguration()
1078 {
1079 return httpConfiguration;
1080 }
1081
1082 public void setHttpConfiguration( HttpConfiguration httpConfiguration )
1083 {
1084 this.httpConfiguration = httpConfiguration;
1085 }
1086
1087
1088
1089
1090
1091
1092 public BasicAuthScope getBasicAuthScope()
1093 {
1094 if ( basicAuth == null )
1095 {
1096 basicAuth = new BasicAuthScope();
1097 }
1098 return basicAuth;
1099 }
1100
1101
1102
1103
1104
1105
1106 public void setBasicAuthScope( BasicAuthScope basicAuth )
1107 {
1108 this.basicAuth = basicAuth;
1109 }
1110
1111
1112
1113
1114
1115
1116 public BasicAuthScope getProxyBasicAuthScope()
1117 {
1118 if ( proxyAuth == null )
1119 {
1120 proxyAuth = new BasicAuthScope();
1121 }
1122 return proxyAuth;
1123 }
1124
1125
1126
1127
1128
1129
1130 public void setProxyBasicAuthScope( BasicAuthScope proxyAuth )
1131 {
1132 this.proxyAuth = proxyAuth;
1133 }
1134
1135 public void fillInputData( InputData inputData )
1136 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
1137 {
1138 fillInputData( getInitialBackoffSeconds(), inputData );
1139 }
1140
1141 private void fillInputData( int wait, InputData inputData )
1142 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
1143 {
1144 Resource resource = inputData.getResource();
1145
1146 String url = buildUrl( resource );
1147 HttpGet getMethod = new HttpGet( url );
1148 long timestamp = resource.getLastModified();
1149 if ( timestamp > 0 )
1150 {
1151 SimpleDateFormat fmt = new SimpleDateFormat( "EEE, dd-MMM-yy HH:mm:ss zzz", Locale.US );
1152 fmt.setTimeZone( GMT_TIME_ZONE );
1153 Header hdr = new BasicHeader( "If-Modified-Since", fmt.format( new Date( timestamp ) ) );
1154 fireTransferDebug( "sending ==> " + hdr + "(" + timestamp + ")" );
1155 getMethod.addHeader( hdr );
1156 }
1157
1158 try
1159 {
1160 CloseableHttpResponse response = execute( getMethod );
1161 closeable = response;
1162
1163 fireTransferDebug( formatTransferDebugMessage( url, response.getStatusLine().getStatusCode(),
1164 response.getStatusLine().getReasonPhrase(), getProxyInfo() ) );
1165 int statusCode = response.getStatusLine().getStatusCode();
1166
1167 switch ( statusCode )
1168 {
1169 case HttpStatus.SC_OK:
1170 break;
1171
1172 case HttpStatus.SC_NOT_MODIFIED:
1173
1174 return;
1175
1176 case HttpStatus.SC_FORBIDDEN:
1177 case HttpStatus.SC_UNAUTHORIZED:
1178 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
1179 fireSessionConnectionRefused();
1180 throw new AuthorizationException( formatAuthorizationMessage( url,
1181 response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(),
1182 getProxyInfo() ) );
1183
1184 case HttpStatus.SC_NOT_FOUND:
1185 throw new ResourceDoesNotExistException( formatResourceDoesNotExistMessage( url,
1186 response.getStatusLine().getStatusCode(),
1187 response.getStatusLine().getReasonPhrase(), getProxyInfo() ) );
1188
1189 case SC_TOO_MANY_REQUESTS:
1190 fillInputData( backoff( wait, url ), inputData );
1191 break;
1192
1193
1194 default:
1195 cleanupGetTransfer( resource );
1196 TransferFailedException e = new TransferFailedException( formatTransferFailedMessage( url,
1197 response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(),
1198 getProxyInfo() ) );
1199 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
1200 throw e;
1201 }
1202
1203 Header contentLengthHeader = response.getFirstHeader( "Content-Length" );
1204
1205 if ( contentLengthHeader != null )
1206 {
1207 try
1208 {
1209 long contentLength = Long.parseLong( contentLengthHeader.getValue() );
1210
1211 resource.setContentLength( contentLength );
1212 }
1213 catch ( NumberFormatException e )
1214 {
1215 fireTransferDebug(
1216 "error parsing content length header '" + contentLengthHeader.getValue() + "' " + e );
1217 }
1218 }
1219
1220 Header lastModifiedHeader = response.getFirstHeader( "Last-Modified" );
1221 if ( lastModifiedHeader != null )
1222 {
1223 Date lastModified = DateUtils.parseDate( lastModifiedHeader.getValue() );
1224 if ( lastModified != null )
1225 {
1226 resource.setLastModified( lastModified.getTime() );
1227 fireTransferDebug( "last-modified = " + lastModifiedHeader.getValue() + " ("
1228 + lastModified.getTime() + ")" );
1229 }
1230 }
1231
1232 HttpEntity entity = response.getEntity();
1233 if ( entity != null )
1234 {
1235 inputData.setInputStream( entity.getContent() );
1236 }
1237 }
1238 catch ( IOException | HttpException | InterruptedException e )
1239 {
1240 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
1241
1242 throw new TransferFailedException( formatTransferFailedMessage( url, getProxyInfo() ), e );
1243 }
1244
1245 }
1246
1247 protected void cleanupGetTransfer( Resource resource )
1248 {
1249 if ( closeable != null )
1250 {
1251 try
1252 {
1253 closeable.close();
1254 }
1255 catch ( IOException ignore )
1256 {
1257
1258 }
1259
1260 }
1261 }
1262
1263
1264 @Override
1265 public void putFromStream( InputStream stream, String destination )
1266 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
1267 {
1268 putFromStream( stream, destination, -1, -1 );
1269 }
1270
1271 @Override
1272 protected void putFromStream( InputStream stream, Resource resource )
1273 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
1274 {
1275 putFromStream( stream, resource.getName(), -1, -1 );
1276 }
1277
1278 public Properties getHttpHeaders()
1279 {
1280 return httpHeaders;
1281 }
1282
1283 public void setHttpHeaders( Properties httpHeaders )
1284 {
1285 this.httpHeaders = httpHeaders;
1286 }
1287
1288 @Override
1289 public void fillOutputData( OutputData outputData )
1290 throws TransferFailedException
1291 {
1292
1293 throw new IllegalStateException( "this wagon http client must not use fillOutputData" );
1294 }
1295
1296 protected CredentialsProvider getCredentialsProvider()
1297 {
1298 return credentialsProvider;
1299 }
1300
1301 protected AuthCache getAuthCache()
1302 {
1303 return authCache;
1304 }
1305
1306 public int getInitialBackoffSeconds()
1307 {
1308 return initialBackoffSeconds;
1309 }
1310
1311 public void setInitialBackoffSeconds( int initialBackoffSeconds )
1312 {
1313 this.initialBackoffSeconds = initialBackoffSeconds;
1314 }
1315
1316 public static int getMaxBackoffWaitSeconds()
1317 {
1318 return MAX_BACKOFF_WAIT_SECONDS;
1319 }
1320 }