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