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