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