001package org.apache.maven.wagon.shared.http; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.http.Header; 023import org.apache.http.HttpEntity; 024import org.apache.http.HttpException; 025import org.apache.http.HttpHost; 026import org.apache.http.HttpResponse; 027import org.apache.http.HttpStatus; 028import org.apache.http.auth.AuthScope; 029import org.apache.http.auth.ChallengeState; 030import org.apache.http.auth.Credentials; 031import org.apache.http.auth.NTCredentials; 032import org.apache.http.auth.UsernamePasswordCredentials; 033import org.apache.http.client.AuthCache; 034import org.apache.http.client.CredentialsProvider; 035import org.apache.http.client.config.CookieSpecs; 036import org.apache.http.client.config.RequestConfig; 037import org.apache.http.client.methods.CloseableHttpResponse; 038import org.apache.http.client.methods.HttpGet; 039import org.apache.http.client.methods.HttpHead; 040import org.apache.http.client.methods.HttpPut; 041import org.apache.http.client.methods.HttpUriRequest; 042import org.apache.http.client.protocol.HttpClientContext; 043import org.apache.http.client.utils.DateUtils; 044import org.apache.http.config.Registry; 045import org.apache.http.config.RegistryBuilder; 046import org.apache.http.conn.HttpClientConnectionManager; 047import org.apache.http.conn.socket.ConnectionSocketFactory; 048import org.apache.http.conn.socket.PlainConnectionSocketFactory; 049import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 050import org.apache.http.conn.ssl.SSLContextBuilder; 051import org.apache.http.conn.ssl.SSLInitializationException; 052import org.apache.http.entity.AbstractHttpEntity; 053import org.apache.http.impl.auth.BasicScheme; 054import org.apache.http.impl.client.BasicAuthCache; 055import org.apache.http.impl.client.BasicCredentialsProvider; 056import org.apache.http.impl.client.CloseableHttpClient; 057import org.apache.http.impl.client.HttpClientBuilder; 058import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 059import org.apache.http.message.BasicHeader; 060import org.apache.http.protocol.HTTP; 061import org.apache.http.util.EntityUtils; 062import org.apache.maven.wagon.InputData; 063import org.apache.maven.wagon.OutputData; 064import org.apache.maven.wagon.PathUtils; 065import org.apache.maven.wagon.ResourceDoesNotExistException; 066import org.apache.maven.wagon.StreamWagon; 067import org.apache.maven.wagon.TransferFailedException; 068import org.apache.maven.wagon.Wagon; 069import org.apache.maven.wagon.authorization.AuthorizationException; 070import org.apache.maven.wagon.events.TransferEvent; 071import org.apache.maven.wagon.proxy.ProxyInfo; 072import org.apache.maven.wagon.repository.Repository; 073import org.apache.maven.wagon.resource.Resource; 074import org.codehaus.plexus.util.StringUtils; 075 076import javax.net.ssl.HttpsURLConnection; 077import javax.net.ssl.SSLContext; 078import java.io.Closeable; 079import java.io.File; 080import java.io.FileInputStream; 081import java.io.IOException; 082import java.io.InputStream; 083import java.io.OutputStream; 084import java.text.SimpleDateFormat; 085import java.util.Date; 086import java.util.Locale; 087import java.util.Map; 088import java.util.Properties; 089import java.util.TimeZone; 090import java.util.concurrent.TimeUnit; 091 092/** 093 * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a> 094 * @author <a href="mailto:james@atlassian.com">James William Dumay</a> 095 */ 096public abstract class AbstractHttpClientWagon 097 extends StreamWagon 098{ 099 private final class RequestEntityImplementation 100 extends AbstractHttpEntity 101 { 102 103 private static final int BUFFER_SIZE = 2048; 104 105 private final Resource resource; 106 107 private final Wagon wagon; 108 109 private InputStream stream; 110 111 private File source; 112 113 private long length = -1; 114 115 private boolean repeatable; 116 117 private RequestEntityImplementation( final InputStream stream, final Resource resource, final Wagon wagon, 118 final File source ) 119 throws TransferFailedException 120 { 121 if ( source != null ) 122 { 123 this.source = source; 124 this.repeatable = true; 125 } 126 else 127 { 128 this.stream = stream; 129 this.repeatable = false; 130 } 131 this.resource = resource; 132 this.length = resource == null ? -1 : resource.getContentLength(); 133 134 this.wagon = wagon; 135 } 136 137 public long getContentLength() 138 { 139 return length; 140 } 141 142 public InputStream getContent() 143 throws IOException, IllegalStateException 144 { 145 if ( this.source != null ) 146 { 147 return new FileInputStream( this.source ); 148 } 149 return stream; 150 } 151 152 public boolean isRepeatable() 153 { 154 return repeatable; 155 } 156 157 public void writeTo( final OutputStream outputStream ) 158 throws IOException 159 { 160 if ( outputStream == null ) 161 { 162 throw new NullPointerException( "outputStream cannot be null" ); 163 } 164 TransferEvent transferEvent = 165 new TransferEvent( wagon, resource, TransferEvent.TRANSFER_PROGRESS, TransferEvent.REQUEST_PUT ); 166 transferEvent.setTimestamp( System.currentTimeMillis() ); 167 InputStream instream = ( this.source != null ) 168 ? new FileInputStream( this.source ) 169 : stream; 170 try 171 { 172 byte[] buffer = new byte[BUFFER_SIZE]; 173 int l; 174 if ( this.length < 0 ) 175 { 176 // until EOF 177 while ( ( l = instream.read( buffer ) ) != -1 ) 178 { 179 fireTransferProgress( transferEvent, buffer, -1 ); 180 outputStream.write( buffer, 0, l ); 181 } 182 } 183 else 184 { 185 // no need to consume more than length 186 long remaining = this.length; 187 while ( remaining > 0 ) 188 { 189 l = instream.read( buffer, 0, (int) Math.min( BUFFER_SIZE, remaining ) ); 190 if ( l == -1 ) 191 { 192 break; 193 } 194 fireTransferProgress( transferEvent, buffer, (int) Math.min( BUFFER_SIZE, remaining ) ); 195 outputStream.write( buffer, 0, l ); 196 remaining -= l; 197 } 198 } 199 } 200 finally 201 { 202 instream.close(); 203 } 204 } 205 206 public boolean isStreaming() 207 { 208 return true; 209 } 210 } 211 212 private static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone( "GMT" ); 213 214 /** 215 * use http(s) connection pool mechanism. 216 * <b>enabled by default</b> 217 */ 218 private static boolean persistentPool = 219 Boolean.valueOf( System.getProperty( "maven.wagon.http.pool", "true" ) ); 220 221 /** 222 * skip failure on certificate validity checks. 223 * <b>disabled by default</b> 224 */ 225 private static final boolean SSL_INSECURE = 226 Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.insecure", "false" ) ); 227 228 /** 229 * if using sslInsecure, certificate date issues will be ignored 230 * <b>disabled by default</b> 231 */ 232 private static final boolean IGNORE_SSL_VALIDITY_DATES = 233 Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.ignore.validity.dates", "false" ) ); 234 235 /** 236 * If enabled, ssl hostname verifier does not check hostname. Disable this will use a browser compat hostname 237 * verifier <b>disabled by default</b> 238 */ 239 private static final boolean SSL_ALLOW_ALL = 240 Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.allowall", "false" ) ); 241 242 243 /** 244 * Maximum concurrent connections per distinct route. 245 * <b>20 by default</b> 246 */ 247 private static final int MAX_CONN_PER_ROUTE = 248 Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxPerRoute", "20" ) ); 249 250 /** 251 * Maximum concurrent connections in total. 252 * <b>40 by default</b> 253 */ 254 private static final int MAX_CONN_TOTAL = 255 Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxTotal", "40" ) ); 256 257 /** 258 * Internal connection manager 259 */ 260 private static HttpClientConnectionManager httpClientConnectionManager = createConnManager(); 261 262 263 /** 264 * See RFC6585 265 */ 266 protected static final int SC_TOO_MANY_REQUESTS = 429; 267 268 /** 269 * For exponential backoff. 270 */ 271 272 /** 273 * Initial seconds to back off when a HTTP 429 received. 274 * Subsequent 429 responses result in exponental backoff. 275 * <b>5 by default</b> 276 * 277 * @since 2.7 278 */ 279 private int initialBackoffSeconds = 280 Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.backoffSeconds", "5" ) ); 281 282 /** 283 * The maximum amount of time we want to back off in the case of 284 * repeated HTTP 429 response codes. 285 * 286 * @since 2.7 287 */ 288 private static final int MAX_BACKOFF_WAIT_SECONDS = 289 Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxBackoffSeconds", "180" ) ); 290 291 292 protected int backoff( int wait, String url ) 293 throws InterruptedException, TransferFailedException 294 { 295 TimeUnit.SECONDS.sleep( wait ); 296 int nextWait = wait * 2; 297 if ( nextWait >= getMaxBackoffWaitSeconds() ) 298 { 299 throw new TransferFailedException( 300 "Waited too long to access: " + url + ". Return code is: " + SC_TOO_MANY_REQUESTS ); 301 } 302 return nextWait; 303 } 304 305 @SuppressWarnings( "checkstyle:linelength" ) 306 private static PoolingHttpClientConnectionManager createConnManager() 307 { 308 309 String sslProtocolsStr = System.getProperty( "https.protocols" ); 310 String cipherSuitesStr = System.getProperty( "https.cipherSuites" ); 311 String[] sslProtocols = sslProtocolsStr != null ? sslProtocolsStr.split( " *, *" ) : null; 312 String[] cipherSuites = cipherSuitesStr != null ? cipherSuitesStr.split( " *, *" ) : null; 313 314 SSLConnectionSocketFactory sslConnectionSocketFactory; 315 if ( SSL_INSECURE ) 316 { 317 try 318 { 319 SSLContext sslContext = new SSLContextBuilder().useSSL().loadTrustMaterial( null, 320 new RelaxedTrustStrategy( 321 IGNORE_SSL_VALIDITY_DATES ) ).build(); 322 sslConnectionSocketFactory = new SSLConnectionSocketFactory( sslContext, sslProtocols, cipherSuites, 323 SSL_ALLOW_ALL 324 ? SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER 325 : SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER ); 326 } 327 catch ( Exception ex ) 328 { 329 throw new SSLInitializationException( ex.getMessage(), ex ); 330 } 331 } 332 else 333 { 334 sslConnectionSocketFactory = 335 new SSLConnectionSocketFactory( HttpsURLConnection.getDefaultSSLSocketFactory(), sslProtocols, 336 cipherSuites, 337 SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER ); 338 } 339 340 Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register( "http", 341 PlainConnectionSocketFactory.INSTANCE ).register( 342 "https", sslConnectionSocketFactory ).build(); 343 344 PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager( registry ); 345 if ( persistentPool ) 346 { 347 connManager.setDefaultMaxPerRoute( MAX_CONN_PER_ROUTE ); 348 connManager.setMaxTotal( MAX_CONN_TOTAL ); 349 } 350 else 351 { 352 connManager.setMaxTotal( 1 ); 353 } 354 return connManager; 355 } 356 357 private static CloseableHttpClient httpClient = createClient(); 358 359 private static CloseableHttpClient createClient() 360 { 361 return HttpClientBuilder.create() // 362 .useSystemProperties() // 363 .disableConnectionState() // 364 .setConnectionManager( httpClientConnectionManager ) // 365 .build(); 366 } 367 368 private CredentialsProvider credentialsProvider; 369 370 private AuthCache authCache; 371 372 private Closeable closeable; 373 374 /** 375 * @plexus.configuration 376 * @deprecated Use httpConfiguration instead. 377 */ 378 private Properties httpHeaders; 379 380 /** 381 * @since 1.0-beta-6 382 */ 383 private HttpConfiguration httpConfiguration; 384 385 /** 386 * Basic auth scope overrides 387 * @since 2.8 388 */ 389 private BasicAuthScope basicAuth; 390 391 /** 392 * Proxy basic auth scope overrides 393 * @since 2.8 394 */ 395 private BasicAuthScope proxyAuth; 396 397 public void openConnectionInternal() 398 { 399 repository.setUrl( getURL( repository ) ); 400 401 credentialsProvider = new BasicCredentialsProvider(); 402 authCache = new BasicAuthCache(); 403 404 if ( authenticationInfo != null ) 405 { 406 407 String username = authenticationInfo.getUserName(); 408 String password = authenticationInfo.getPassword(); 409 410 if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) ) 411 { 412 Credentials creds = new UsernamePasswordCredentials( username, password ); 413 414 String host = getRepository().getHost(); 415 int port = getRepository().getPort(); 416 417 credentialsProvider.setCredentials( getBasicAuthScope().getScope( host, port ), creds ); 418 } 419 } 420 421 ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() ); 422 if ( proxyInfo != null ) 423 { 424 String proxyUsername = proxyInfo.getUserName(); 425 String proxyPassword = proxyInfo.getPassword(); 426 String proxyHost = proxyInfo.getHost(); 427 String proxyNtlmHost = proxyInfo.getNtlmHost(); 428 String proxyNtlmDomain = proxyInfo.getNtlmDomain(); 429 if ( proxyHost != null ) 430 { 431 if ( proxyUsername != null && proxyPassword != null ) 432 { 433 Credentials creds; 434 if ( proxyNtlmHost != null || proxyNtlmDomain != null ) 435 { 436 creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain ); 437 } 438 else 439 { 440 creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword ); 441 } 442 443 int proxyPort = proxyInfo.getPort(); 444 445 AuthScope authScope = getProxyBasicAuthScope().getScope( proxyHost, proxyPort ); 446 credentialsProvider.setCredentials( authScope, creds ); 447 } 448 } 449 } 450 } 451 452 public void closeConnection() 453 { 454 if ( !persistentPool ) 455 { 456 httpClientConnectionManager.closeIdleConnections( 0, TimeUnit.MILLISECONDS ); 457 } 458 459 if ( authCache != null ) 460 { 461 authCache.clear(); 462 authCache = null; 463 } 464 465 if ( credentialsProvider != null ) 466 { 467 credentialsProvider.clear(); 468 credentialsProvider = null; 469 } 470 } 471 472 public static CloseableHttpClient getHttpClient() 473 { 474 return httpClient; 475 } 476 477 public static void setPersistentPool( boolean persistentPool ) 478 { 479 persistentPool = persistentPool; 480 } 481 482 public static void setPoolingHttpClientConnectionManager( 483 PoolingHttpClientConnectionManager poolingHttpClientConnectionManager ) 484 { 485 httpClientConnectionManager = poolingHttpClientConnectionManager; 486 httpClient = createClient(); 487 } 488 489 public void put( File source, String resourceName ) 490 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 491 { 492 Resource resource = new Resource( resourceName ); 493 494 firePutInitiated( resource, source ); 495 496 resource.setContentLength( source.length() ); 497 498 resource.setLastModified( source.lastModified() ); 499 500 put( null, resource, source ); 501 } 502 503 public void putFromStream( final InputStream stream, String destination, long contentLength, long lastModified ) 504 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 505 { 506 Resource resource = new Resource( destination ); 507 508 firePutInitiated( resource, null ); 509 510 resource.setContentLength( contentLength ); 511 512 resource.setLastModified( lastModified ); 513 514 put( stream, resource, null ); 515 } 516 517 private void put( final InputStream stream, Resource resource, File source ) 518 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException 519 { 520 put( resource, source, new RequestEntityImplementation( stream, resource, this, source ) ); 521 } 522 523 private void put( Resource resource, File source, HttpEntity httpEntity ) 524 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException 525 { 526 put( resource, source, httpEntity, buildUrl( resource ) ); 527 } 528 529 /** 530 * Builds a complete URL string from the repository URL and the relative path of the resource passed. 531 * 532 * @param resource the resource to extract the relative path from. 533 * @return the complete URL 534 */ 535 private String buildUrl( Resource resource ) 536 { 537 return EncodingUtil.encodeURLToString( getRepository().getUrl(), resource.getName() ); 538 } 539 540 541 private void put( Resource resource, File source, HttpEntity httpEntity, String url ) 542 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException 543 { 544 put( getInitialBackoffSeconds(), resource, source, httpEntity, url ); 545 } 546 547 548 private void put( int wait, Resource resource, File source, HttpEntity httpEntity, String url ) 549 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException 550 { 551 552 //Parent directories need to be created before posting 553 try 554 { 555 mkdirs( PathUtils.dirname( resource.getName() ) ); 556 } 557 catch ( HttpException he ) 558 { 559 fireTransferError( resource, he, TransferEvent.REQUEST_PUT ); 560 } 561 catch ( IOException e ) 562 { 563 fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); 564 } 565 566 // preemptive for put 567 // TODO: is it a good idea, though? 'Expect-continue' handshake would serve much better 568 569 Repository repo = getRepository(); 570 HttpHost targetHost = new HttpHost( repo.getHost(), repo.getPort(), repo.getProtocol() ); 571 AuthScope targetScope = getBasicAuthScope().getScope( targetHost ); 572 573 if ( credentialsProvider.getCredentials( targetScope ) != null ) 574 { 575 BasicScheme targetAuth = new BasicScheme(); 576 authCache.put( targetHost, targetAuth ); 577 } 578 579 HttpPut putMethod = new HttpPut( url ); 580 581 firePutStarted( resource, source ); 582 583 try 584 { 585 putMethod.setEntity( httpEntity ); 586 587 CloseableHttpResponse response = execute( putMethod ); 588 try 589 { 590 int statusCode = response.getStatusLine().getStatusCode(); 591 String reasonPhrase = ", ReasonPhrase: " + response.getStatusLine().getReasonPhrase() + "."; 592 fireTransferDebug( url + " - Status code: " + statusCode + reasonPhrase ); 593 594 // Check that we didn't run out of retries. 595 switch ( statusCode ) 596 { 597 // Success Codes 598 case HttpStatus.SC_OK: // 200 599 case HttpStatus.SC_CREATED: // 201 600 case HttpStatus.SC_ACCEPTED: // 202 601 case HttpStatus.SC_NO_CONTENT: // 204 602 break; 603 // handle all redirect even if http specs says " the user agent MUST NOT automatically redirect 604 // the request unless it can be confirmed by the user" 605 case HttpStatus.SC_MOVED_PERMANENTLY: // 301 606 case HttpStatus.SC_MOVED_TEMPORARILY: // 302 607 case HttpStatus.SC_SEE_OTHER: // 303 608 put( resource, source, httpEntity, calculateRelocatedUrl( response ) ); 609 return; 610 case HttpStatus.SC_FORBIDDEN: 611 fireSessionConnectionRefused(); 612 throw new AuthorizationException( "Access denied to: " + url + reasonPhrase ); 613 614 case HttpStatus.SC_NOT_FOUND: 615 throw new ResourceDoesNotExistException( "File: " + url + " does not exist" + reasonPhrase ); 616 617 case SC_TOO_MANY_REQUESTS: 618 put( backoff( wait, url ), resource, source, httpEntity, url ); 619 break; 620 //add more entries here 621 default: 622 TransferFailedException e = new TransferFailedException( 623 "Failed to transfer file: " + url + ". Return code is: " + statusCode + reasonPhrase ); 624 fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); 625 throw e; 626 } 627 628 firePutCompleted( resource, source ); 629 630 EntityUtils.consume( response.getEntity() ); 631 } 632 finally 633 { 634 response.close(); 635 } 636 } 637 catch ( IOException e ) 638 { 639 fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); 640 641 throw new TransferFailedException( e.getMessage(), e ); 642 } 643 catch ( HttpException e ) 644 { 645 fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); 646 647 throw new TransferFailedException( e.getMessage(), e ); 648 } 649 catch ( InterruptedException e ) 650 { 651 fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); 652 653 throw new TransferFailedException( e.getMessage(), e ); 654 } 655 656 } 657 658 protected String calculateRelocatedUrl( HttpResponse response ) 659 { 660 Header locationHeader = response.getFirstHeader( "Location" ); 661 String locationField = locationHeader.getValue(); 662 // is it a relative Location or a full ? 663 return locationField.startsWith( "http" ) ? locationField : getURL( getRepository() ) + '/' + locationField; 664 } 665 666 protected void mkdirs( String dirname ) 667 throws HttpException, IOException 668 { 669 // nothing to do 670 } 671 672 public boolean resourceExists( String resourceName ) 673 throws TransferFailedException, AuthorizationException 674 { 675 return resourceExists( getInitialBackoffSeconds(), resourceName ); 676 } 677 678 679 private boolean resourceExists( int wait, String resourceName ) 680 throws TransferFailedException, AuthorizationException 681 { 682 String repositoryUrl = getRepository().getUrl(); 683 String url = repositoryUrl + ( repositoryUrl.endsWith( "/" ) ? "" : "/" ) + resourceName; 684 HttpHead headMethod = new HttpHead( url ); 685 try 686 { 687 CloseableHttpResponse response = execute( headMethod ); 688 try 689 { 690 int statusCode = response.getStatusLine().getStatusCode(); 691 String reasonPhrase = ", ReasonPhrase: " + response.getStatusLine().getReasonPhrase() + "."; 692 boolean result; 693 switch ( statusCode ) 694 { 695 case HttpStatus.SC_OK: 696 result = true; 697 break; 698 case HttpStatus.SC_NOT_MODIFIED: 699 result = true; 700 break; 701 case HttpStatus.SC_FORBIDDEN: 702 throw new AuthorizationException( "Access denied to: " + url + reasonPhrase ); 703 704 case HttpStatus.SC_UNAUTHORIZED: 705 throw new AuthorizationException( "Not authorized " + reasonPhrase ); 706 707 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: 708 throw new AuthorizationException( "Not authorized by proxy " + reasonPhrase ); 709 710 case HttpStatus.SC_NOT_FOUND: 711 result = false; 712 break; 713 714 case SC_TOO_MANY_REQUESTS: 715 return resourceExists( backoff( wait, resourceName ), resourceName ); 716 717 //add more entries here 718 default: 719 throw new TransferFailedException( 720 "Failed to transfer file: " + url + ". Return code is: " + statusCode + reasonPhrase ); 721 } 722 723 EntityUtils.consume( response.getEntity() ); 724 return result; 725 } 726 finally 727 { 728 response.close(); 729 } 730 } 731 catch ( IOException e ) 732 { 733 throw new TransferFailedException( e.getMessage(), e ); 734 } 735 catch ( HttpException e ) 736 { 737 throw new TransferFailedException( e.getMessage(), e ); 738 } 739 catch ( InterruptedException e ) 740 { 741 throw new TransferFailedException( e.getMessage(), e ); 742 } 743 744 } 745 746 protected CloseableHttpResponse execute( HttpUriRequest httpMethod ) 747 throws HttpException, IOException 748 { 749 setHeaders( httpMethod ); 750 String userAgent = getUserAgent( httpMethod ); 751 if ( userAgent != null ) 752 { 753 httpMethod.setHeader( HTTP.USER_AGENT, userAgent ); 754 } 755 756 RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); 757 // WAGON-273: default the cookie-policy to browser compatible 758 requestConfigBuilder.setCookieSpec( CookieSpecs.BROWSER_COMPATIBILITY ); 759 760 Repository repo = getRepository(); 761 ProxyInfo proxyInfo = getProxyInfo( repo.getProtocol(), repo.getHost() ); 762 if ( proxyInfo != null ) 763 { 764 HttpHost proxy = new HttpHost( proxyInfo.getHost(), proxyInfo.getPort() ); 765 requestConfigBuilder.setProxy( proxy ); 766 } 767 768 HttpMethodConfiguration config = 769 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( httpMethod ); 770 771 if ( config != null ) 772 { 773 ConfigurationUtils.copyConfig( config, requestConfigBuilder ); 774 } 775 else 776 { 777 requestConfigBuilder.setSocketTimeout( getReadTimeout() ); 778 if ( httpMethod instanceof HttpPut ) 779 { 780 requestConfigBuilder.setExpectContinueEnabled( true ); 781 } 782 } 783 784 if ( httpMethod instanceof HttpPut ) 785 { 786 requestConfigBuilder.setRedirectsEnabled( false ); 787 } 788 789 HttpClientContext localContext = HttpClientContext.create(); 790 localContext.setCredentialsProvider( credentialsProvider ); 791 localContext.setAuthCache( authCache ); 792 localContext.setRequestConfig( requestConfigBuilder.build() ); 793 794 if ( config != null && config.isUsePreemptive() ) 795 { 796 HttpHost targetHost = new HttpHost( repo.getHost(), repo.getPort(), repo.getProtocol() ); 797 AuthScope targetScope = getBasicAuthScope().getScope( targetHost ); 798 799 if ( credentialsProvider.getCredentials( targetScope ) != null ) 800 { 801 BasicScheme targetAuth = new BasicScheme(); 802 authCache.put( targetHost, targetAuth ); 803 } 804 } 805 806 if ( proxyInfo != null ) 807 { 808 if ( proxyInfo.getHost() != null ) 809 { 810 HttpHost proxyHost = new HttpHost( proxyInfo.getHost(), proxyInfo.getPort() ); 811 AuthScope proxyScope = getProxyBasicAuthScope().getScope( proxyHost ); 812 813 if ( credentialsProvider.getCredentials( proxyScope ) != null ) 814 { 815 /* This is extremely ugly because we need to set challengeState to PROXY, but 816 * the constructor is deprecated. Alternatively, we could subclass BasicScheme 817 * to ProxyBasicScheme and set the state internally in the constructor. 818 */ 819 BasicScheme proxyAuth = new BasicScheme( ChallengeState.PROXY ); 820 authCache.put( proxyHost, proxyAuth ); 821 } 822 } 823 } 824 825 return httpClient.execute( httpMethod, localContext ); 826 } 827 828 public void setHeaders( HttpUriRequest method ) 829 { 830 HttpMethodConfiguration config = 831 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method ); 832 if ( config == null || config.isUseDefaultHeaders() ) 833 { 834 // TODO: merge with the other headers and have some better defaults, unify with lightweight headers 835 method.addHeader( "Cache-control", "no-cache" ); 836 method.addHeader( "Cache-store", "no-store" ); 837 method.addHeader( "Pragma", "no-cache" ); 838 method.addHeader( "Expires", "0" ); 839 method.addHeader( "Accept-Encoding", "gzip" ); 840 } 841 842 if ( httpHeaders != null ) 843 { 844 for ( Map.Entry<Object, Object> entry : httpHeaders.entrySet() ) 845 { 846 method.setHeader( (String) entry.getKey(), (String) entry.getValue() ); 847 } 848 } 849 850 Header[] headers = config == null ? null : config.asRequestHeaders(); 851 if ( headers != null ) 852 { 853 for ( Header header : headers ) 854 { 855 method.setHeader( header ); 856 } 857 } 858 859 Header userAgentHeader = method.getFirstHeader( HTTP.USER_AGENT ); 860 if ( userAgentHeader == null ) 861 { 862 String userAgent = getUserAgent( method ); 863 if ( userAgent != null ) 864 { 865 method.setHeader( HTTP.USER_AGENT, userAgent ); 866 } 867 } 868 } 869 870 protected String getUserAgent( HttpUriRequest method ) 871 { 872 if ( httpHeaders != null ) 873 { 874 String value = (String) httpHeaders.get( "User-Agent" ); 875 if ( value != null ) 876 { 877 return value; 878 } 879 } 880 HttpMethodConfiguration config = 881 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method ); 882 883 if ( config != null ) 884 { 885 return (String) config.getHeaders().get( "User-Agent" ); 886 } 887 return null; 888 } 889 890 /** 891 * getUrl 892 * Implementors can override this to remove unwanted parts of the url such as role-hints 893 * 894 * @param repository 895 * @return 896 */ 897 protected String getURL( Repository repository ) 898 { 899 return repository.getUrl(); 900 } 901 902 public HttpConfiguration getHttpConfiguration() 903 { 904 return httpConfiguration; 905 } 906 907 public void setHttpConfiguration( HttpConfiguration httpConfiguration ) 908 { 909 this.httpConfiguration = httpConfiguration; 910 } 911 912 /** 913 * Get the override values for standard HttpClient AuthScope 914 * 915 * @return the basicAuth 916 */ 917 public BasicAuthScope getBasicAuthScope() 918 { 919 if ( basicAuth == null ) 920 { 921 basicAuth = new BasicAuthScope(); 922 } 923 return basicAuth; 924 } 925 926 /** 927 * Set the override values for standard HttpClient AuthScope 928 * 929 * @param basicAuth the AuthScope to set 930 */ 931 public void setBasicAuthScope( BasicAuthScope basicAuth ) 932 { 933 this.basicAuth = basicAuth; 934 } 935 936 /** 937 * Get the override values for proxy HttpClient AuthScope 938 * 939 * @return the proxyAuth 940 */ 941 public BasicAuthScope getProxyBasicAuthScope() 942 { 943 if ( proxyAuth == null ) 944 { 945 proxyAuth = new BasicAuthScope(); 946 } 947 return proxyAuth; 948 } 949 950 /** 951 * Set the override values for proxy HttpClient AuthScope 952 * 953 * @param proxyAuth the AuthScope to set 954 */ 955 public void setProxyBasicAuthScope( BasicAuthScope proxyAuth ) 956 { 957 this.proxyAuth = proxyAuth; 958 } 959 960 public void fillInputData( InputData inputData ) 961 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 962 { 963 fillInputData( getInitialBackoffSeconds(), inputData ); 964 } 965 966 private void fillInputData( int wait, InputData inputData ) 967 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 968 { 969 Resource resource = inputData.getResource(); 970 971 String repositoryUrl = getRepository().getUrl(); 972 String url = repositoryUrl + ( repositoryUrl.endsWith( "/" ) ? "" : "/" ) + resource.getName(); 973 HttpGet getMethod = new HttpGet( url ); 974 long timestamp = resource.getLastModified(); 975 if ( timestamp > 0 ) 976 { 977 SimpleDateFormat fmt = new SimpleDateFormat( "EEE, dd-MMM-yy HH:mm:ss zzz", Locale.US ); 978 fmt.setTimeZone( GMT_TIME_ZONE ); 979 Header hdr = new BasicHeader( "If-Modified-Since", fmt.format( new Date( timestamp ) ) ); 980 fireTransferDebug( "sending ==> " + hdr + "(" + timestamp + ")" ); 981 getMethod.addHeader( hdr ); 982 } 983 984 try 985 { 986 CloseableHttpResponse response = execute( getMethod ); 987 closeable = response; 988 int statusCode = response.getStatusLine().getStatusCode(); 989 990 String reasonPhrase = ", ReasonPhrase:" + response.getStatusLine().getReasonPhrase() + "."; 991 992 fireTransferDebug( url + " - Status code: " + statusCode + reasonPhrase ); 993 994 switch ( statusCode ) 995 { 996 case HttpStatus.SC_OK: 997 break; 998 999 case HttpStatus.SC_NOT_MODIFIED: 1000 // return, leaving last modified set to original value so getIfNewer should return unmodified 1001 return; 1002 case HttpStatus.SC_FORBIDDEN: 1003 fireSessionConnectionRefused(); 1004 throw new AuthorizationException( "Access denied to: " + url + " " + reasonPhrase ); 1005 1006 case HttpStatus.SC_UNAUTHORIZED: 1007 fireSessionConnectionRefused(); 1008 throw new AuthorizationException( "Not authorized " + reasonPhrase ); 1009 1010 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: 1011 fireSessionConnectionRefused(); 1012 throw new AuthorizationException( "Not authorized by proxy " + reasonPhrase ); 1013 1014 case HttpStatus.SC_NOT_FOUND: 1015 throw new ResourceDoesNotExistException( "File: " + url + " " + reasonPhrase ); 1016 1017 case SC_TOO_MANY_REQUESTS: 1018 fillInputData( backoff( wait, url ), inputData ); 1019 break; 1020 1021 // add more entries here 1022 default: 1023 cleanupGetTransfer( resource ); 1024 TransferFailedException e = new TransferFailedException( 1025 "Failed to transfer file: " + url + ". Return code is: " + statusCode + " " + reasonPhrase ); 1026 fireTransferError( resource, e, TransferEvent.REQUEST_GET ); 1027 throw e; 1028 } 1029 1030 Header contentLengthHeader = response.getFirstHeader( "Content-Length" ); 1031 1032 if ( contentLengthHeader != null ) 1033 { 1034 try 1035 { 1036 long contentLength = Long.parseLong( contentLengthHeader.getValue() ); 1037 1038 resource.setContentLength( contentLength ); 1039 } 1040 catch ( NumberFormatException e ) 1041 { 1042 fireTransferDebug( 1043 "error parsing content length header '" + contentLengthHeader.getValue() + "' " + e ); 1044 } 1045 } 1046 1047 Header lastModifiedHeader = response.getFirstHeader( "Last-Modified" ); 1048 if ( lastModifiedHeader != null ) 1049 { 1050 Date lastModified = DateUtils.parseDate( lastModifiedHeader.getValue() ); 1051 if ( lastModified != null ) 1052 { 1053 resource.setLastModified( lastModified.getTime() ); 1054 fireTransferDebug( "last-modified = " + lastModifiedHeader.getValue() + " (" 1055 + lastModified.getTime() + ")" ); 1056 } 1057 } 1058 1059 HttpEntity entity = response.getEntity(); 1060 if ( entity != null ) 1061 { 1062 inputData.setInputStream( entity.getContent() ); 1063 } 1064 } 1065 catch ( IOException e ) 1066 { 1067 fireTransferError( resource, e, TransferEvent.REQUEST_GET ); 1068 1069 throw new TransferFailedException( e.getMessage(), e ); 1070 } 1071 catch ( HttpException e ) 1072 { 1073 fireTransferError( resource, e, TransferEvent.REQUEST_GET ); 1074 1075 throw new TransferFailedException( e.getMessage(), e ); 1076 } 1077 catch ( InterruptedException e ) 1078 { 1079 fireTransferError( resource, e, TransferEvent.REQUEST_GET ); 1080 1081 throw new TransferFailedException( e.getMessage(), e ); 1082 } 1083 1084 } 1085 1086 protected void cleanupGetTransfer( Resource resource ) 1087 { 1088 if ( closeable != null ) 1089 { 1090 try 1091 { 1092 closeable.close(); 1093 } 1094 catch ( IOException ignore ) 1095 { 1096 // ignore 1097 } 1098 1099 } 1100 } 1101 1102 1103 @Override 1104 public void putFromStream( InputStream stream, String destination ) 1105 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 1106 { 1107 putFromStream( stream, destination, -1, -1 ); 1108 } 1109 1110 @Override 1111 protected void putFromStream( InputStream stream, Resource resource ) 1112 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException 1113 { 1114 putFromStream( stream, resource.getName(), -1, -1 ); 1115 } 1116 1117 public Properties getHttpHeaders() 1118 { 1119 return httpHeaders; 1120 } 1121 1122 public void setHttpHeaders( Properties httpHeaders ) 1123 { 1124 this.httpHeaders = httpHeaders; 1125 } 1126 1127 @Override 1128 public void fillOutputData( OutputData outputData ) 1129 throws TransferFailedException 1130 { 1131 // no needed in this implementation but throw an Exception if used 1132 throw new IllegalStateException( "this wagon http client must not use fillOutputData" ); 1133 } 1134 1135 protected CredentialsProvider getCredentialsProvider() 1136 { 1137 return credentialsProvider; 1138 } 1139 1140 protected AuthCache getAuthCache() 1141 { 1142 return authCache; 1143 } 1144 1145 public int getInitialBackoffSeconds() 1146 { 1147 return initialBackoffSeconds; 1148 } 1149 1150 public void setInitialBackoffSeconds( int initialBackoffSeconds ) 1151 { 1152 this.initialBackoffSeconds = initialBackoffSeconds; 1153 } 1154 1155 public static int getMaxBackoffWaitSeconds() 1156 { 1157 return MAX_BACKOFF_WAIT_SECONDS; 1158 } 1159}