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