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