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.codehaus.plexus.util.IOUtil; 075import org.codehaus.plexus.util.StringUtils; 076 077import javax.net.ssl.HttpsURLConnection; 078import javax.net.ssl.SSLContext; 079import java.io.ByteArrayInputStream; 080import java.io.Closeable; 081import java.io.File; 082import java.io.FileInputStream; 083import java.io.IOException; 084import java.io.InputStream; 085import java.io.OutputStream; 086import java.net.URLEncoder; 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 conncurrent 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 conncurrent 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 private static PoolingHttpClientConnectionManager createConnManager() 272 { 273 274 String sslProtocolsStr = System.getProperty( "https.protocols" ); 275 String cipherSuitesStr = System.getProperty( "https.cipherSuites" ); 276 String[] sslProtocols = sslProtocolsStr != null ? sslProtocolsStr.split( " *, *" ) : null; 277 String[] cipherSuites = cipherSuitesStr != null ? cipherSuitesStr.split( " *, *" ) : null; 278 279 SSLConnectionSocketFactory sslConnectionSocketFactory; 280 if ( SSL_INSECURE ) 281 { 282 try 283 { 284 SSLContext sslContext = new SSLContextBuilder().useSSL().loadTrustMaterial( null, 285 new RelaxedTrustStrategy( 286 IGNORE_SSL_VALIDITY_DATES ) ).build(); 287 sslConnectionSocketFactory = new SSLConnectionSocketFactory( sslContext, sslProtocols, cipherSuites, 288 SSL_ALLOW_ALL 289 ? SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER 290 : SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER ); 291 } 292 catch ( Exception ex ) 293 { 294 throw new SSLInitializationException( ex.getMessage(), ex ); 295 } 296 } 297 else 298 { 299 sslConnectionSocketFactory = 300 new SSLConnectionSocketFactory( HttpsURLConnection.getDefaultSSLSocketFactory(), sslProtocols, 301 cipherSuites, 302 SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER ); 303 } 304 305 Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register( "http", 306 PlainConnectionSocketFactory.INSTANCE ).register( 307 "https", sslConnectionSocketFactory ).build(); 308 309 PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager( registry ); 310 if ( PERSISTENT_POOL ) 311 { 312 connManager.setDefaultMaxPerRoute( MAX_CONN_PER_ROUTE ); 313 connManager.setMaxTotal( MAX_CONN_TOTAL ); 314 } 315 else 316 { 317 connManager.setMaxTotal( 1 ); 318 } 319 return connManager; 320 } 321 322 private static CloseableHttpClient CLIENT = createClient(); 323 324 private static CloseableHttpClient createClient() 325 { 326 return HttpClientBuilder.create().useSystemProperties().disableConnectionState().setConnectionManager( 327 CONN_MAN ).build(); 328 } 329 330 private static String DEFAULT_USER_AGENT = getDefaultUserAgent(); 331 332 private static String getDefaultUserAgent() 333 { 334 Properties props = new Properties(); 335 336 InputStream is = AbstractHttpClientWagon.class.getResourceAsStream( 337 "/META-INF/maven/org.apache.maven.wagon/wagon-http/pom.properties" ); 338 if ( is != null ) 339 { 340 try 341 { 342 props.load( is ); 343 } 344 catch ( IOException ignore ) 345 { 346 } 347 finally 348 { 349 IOUtil.close( is ); 350 } 351 } 352 353 String ver = props.getProperty( "version", "unknown-version" ); 354 return "Apache-Maven-Wagon/" + ver + " (Java " + System.getProperty( "java.version" ) + "; "; 355 } 356 357 358 private CredentialsProvider credentialsProvider; 359 360 private AuthCache authCache; 361 362 private HttpClientContext localContext; 363 364 private Closeable closeable; 365 366 /** 367 * @plexus.configuration 368 * @deprecated Use httpConfiguration instead. 369 */ 370 private Properties httpHeaders; 371 372 /** 373 * @since 1.0-beta-6 374 */ 375 private HttpConfiguration httpConfiguration; 376 377 public void openConnectionInternal() 378 { 379 repository.setUrl( getURL( repository ) ); 380 381 localContext = HttpClientContext.create(); 382 credentialsProvider = new BasicCredentialsProvider(); 383 authCache = new BasicAuthCache(); 384 localContext.setCredentialsProvider( credentialsProvider ); 385 localContext.setAuthCache( authCache ); 386 387 if ( authenticationInfo != null ) 388 { 389 390 String username = authenticationInfo.getUserName(); 391 String password = authenticationInfo.getPassword(); 392 393 if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) ) 394 { 395 Credentials creds = new UsernamePasswordCredentials( username, password ); 396 397 String host = getRepository().getHost(); 398 int port = getRepository().getPort() > -1 ? getRepository().getPort() : AuthScope.ANY_PORT; 399 400 credentialsProvider.setCredentials( new AuthScope( host, port ), creds ); 401 } 402 } 403 404 ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() ); 405 if ( proxyInfo != null ) 406 { 407 String proxyUsername = proxyInfo.getUserName(); 408 String proxyPassword = proxyInfo.getPassword(); 409 String proxyHost = proxyInfo.getHost(); 410 String proxyNtlmHost = proxyInfo.getNtlmHost(); 411 String proxyNtlmDomain = proxyInfo.getNtlmDomain(); 412 if ( proxyHost != null ) 413 { 414 if ( proxyUsername != null && proxyPassword != null ) 415 { 416 Credentials creds; 417 if ( proxyNtlmHost != null || proxyNtlmDomain != null ) 418 { 419 creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain ); 420 } 421 else 422 { 423 creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword ); 424 } 425 426 int port = proxyInfo.getPort() > -1 ? proxyInfo.getPort() : AuthScope.ANY_PORT; 427 428 AuthScope authScope = new AuthScope( proxyHost, port ); 429 credentialsProvider.setCredentials( authScope, creds ); 430 } 431 } 432 } 433 } 434 435 public void closeConnection() 436 { 437 if ( !PERSISTENT_POOL ) 438 { 439 CONN_MAN.closeIdleConnections( 0, TimeUnit.MILLISECONDS ); 440 } 441 } 442 443 public void put( File source, String resourceName ) 444 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 445 { 446 Resource resource = new Resource( resourceName ); 447 448 firePutInitiated( resource, source ); 449 450 resource.setContentLength( source.length() ); 451 452 resource.setLastModified( source.lastModified() ); 453 454 put( null, resource, source ); 455 } 456 457 public void putFromStream( final InputStream stream, String destination, long contentLength, long lastModified ) 458 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 459 { 460 Resource resource = new Resource( destination ); 461 462 firePutInitiated( resource, null ); 463 464 resource.setContentLength( contentLength ); 465 466 resource.setLastModified( lastModified ); 467 468 put( stream, resource, null ); 469 } 470 471 private void put( final InputStream stream, Resource resource, File source ) 472 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException 473 { 474 put( resource, source, new RequestEntityImplementation( stream, resource, this, source ) ); 475 } 476 477 private void put( Resource resource, File source, HttpEntity httpEntity ) 478 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException 479 { 480 481 StringBuilder url = new StringBuilder( getURL( getRepository() ) ); 482 String[] parts = StringUtils.split( resource.getName(), "/" ); 483 for ( String part : parts ) 484 { 485 // TODO: Fix encoding... 486 // url += "/" + URLEncoder.encode( parts[i], System.getProperty("file.encoding") ); 487 if ( !url.toString().endsWith( "/" ) ) 488 { 489 url.append( '/' ); 490 } 491 url.append( URLEncoder.encode( part ) ); 492 } 493 put( resource, source, httpEntity, url.toString() ); 494 } 495 496 private void put( Resource resource, File source, HttpEntity httpEntity, String url ) 497 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException 498 { 499 500 //Parent directories need to be created before posting 501 try 502 { 503 mkdirs( PathUtils.dirname( resource.getName() ) ); 504 } 505 catch ( HttpException he ) 506 { 507 fireTransferError( resource, he, TransferEvent.REQUEST_GET ); 508 } 509 catch ( IOException e ) 510 { 511 fireTransferError( resource, e, TransferEvent.REQUEST_GET ); 512 } 513 514 // preemptive for put 515 // TODO: is it a good idea, though? 'Expect-continue' handshake would serve much better 516 517 Repository repo = getRepository(); 518 HttpHost targetHost = new HttpHost( repo.getHost(), repo.getPort(), repo.getProtocol() ); 519 AuthScope targetScope = new AuthScope( targetHost ); 520 521 if ( credentialsProvider.getCredentials( targetScope ) != null ) 522 { 523 BasicScheme targetAuth = new BasicScheme(); 524 try { 525 targetAuth.processChallenge(new BasicHeader(AUTH.WWW_AUTH, "BASIC preemptive")); 526 authCache.put( targetHost, targetAuth ); 527 } 528 catch ( MalformedChallengeException ignore ) 529 { 530 } 531 } 532 533 HttpPut putMethod = new HttpPut( url ); 534 535 firePutStarted( resource, source ); 536 537 try 538 { 539 putMethod.setEntity( httpEntity ); 540 541 CloseableHttpResponse response = execute( putMethod ); 542 try 543 { 544 int statusCode = response.getStatusLine().getStatusCode(); 545 String reasonPhrase = ", ReasonPhrase: " + response.getStatusLine().getReasonPhrase() + "."; 546 fireTransferDebug( url + " - Status code: " + statusCode + reasonPhrase ); 547 548 // Check that we didn't run out of retries. 549 switch ( statusCode ) 550 { 551 // Success Codes 552 case HttpStatus.SC_OK: // 200 553 case HttpStatus.SC_CREATED: // 201 554 case HttpStatus.SC_ACCEPTED: // 202 555 case HttpStatus.SC_NO_CONTENT: // 204 556 break; 557 // 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" 558 case HttpStatus.SC_MOVED_PERMANENTLY: // 301 559 case HttpStatus.SC_MOVED_TEMPORARILY: // 302 560 case HttpStatus.SC_SEE_OTHER: // 303 561 put( resource, source, httpEntity, calculateRelocatedUrl( response ) ); 562 return; 563 case HttpStatus.SC_FORBIDDEN: 564 fireSessionConnectionRefused(); 565 throw new AuthorizationException( "Access denied to: " + url + reasonPhrase ); 566 567 case HttpStatus.SC_NOT_FOUND: 568 throw new ResourceDoesNotExistException( "File: " + url + " does not exist" + reasonPhrase ); 569 570 //add more entries here 571 default: 572 { 573 TransferFailedException e = new TransferFailedException( 574 "Failed to transfer file: " + url + ". Return code is: " + statusCode + reasonPhrase ); 575 fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); 576 throw e; 577 } 578 } 579 580 firePutCompleted( resource, source ); 581 582 EntityUtils.consume( response.getEntity() ); 583 } 584 finally 585 { 586 response.close(); 587 } 588 } 589 catch ( IOException e ) 590 { 591 fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); 592 593 throw new TransferFailedException( e.getMessage(), e ); 594 } 595 catch ( HttpException e ) 596 { 597 fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); 598 599 throw new TransferFailedException( e.getMessage(), e ); 600 } 601 } 602 603 protected String calculateRelocatedUrl( HttpResponse response ) 604 { 605 Header locationHeader = response.getFirstHeader( "Location" ); 606 String locationField = locationHeader.getValue(); 607 // is it a relative Location or a full ? 608 return locationField.startsWith( "http" ) ? locationField : getURL( getRepository() ) + '/' + locationField; 609 } 610 611 protected void mkdirs( String dirname ) 612 throws HttpException, IOException 613 { 614 // nothing to do 615 } 616 617 public boolean resourceExists( String resourceName ) 618 throws TransferFailedException, AuthorizationException 619 { 620 String repositoryUrl = getRepository().getUrl(); 621 String url = repositoryUrl + ( repositoryUrl.endsWith( "/" ) ? "" : "/" ) + resourceName; 622 HttpHead headMethod = new HttpHead( url ); 623 try 624 { 625 CloseableHttpResponse response = execute( headMethod ); 626 try 627 { 628 int statusCode = response.getStatusLine().getStatusCode(); 629 String reasonPhrase = ", ReasonPhrase: " + response.getStatusLine().getReasonPhrase() + "."; 630 boolean result; 631 switch ( statusCode ) 632 { 633 case HttpStatus.SC_OK: 634 result = true; 635 break; 636 case HttpStatus.SC_NOT_MODIFIED: 637 result = true; 638 break; 639 case HttpStatus.SC_FORBIDDEN: 640 throw new AuthorizationException( "Access denied to: " + url + reasonPhrase ); 641 642 case HttpStatus.SC_UNAUTHORIZED: 643 throw new AuthorizationException( "Not authorized " + reasonPhrase ); 644 645 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: 646 throw new AuthorizationException( "Not authorized by proxy " + reasonPhrase ); 647 648 case HttpStatus.SC_NOT_FOUND: 649 result = false; 650 break; 651 //add more entries here 652 default: 653 throw new TransferFailedException( 654 "Failed to transfer file: " + url + ". Return code is: " + statusCode + reasonPhrase ); 655 } 656 657 EntityUtils.consume( response.getEntity() ); 658 return result; 659 } 660 finally 661 { 662 response.close(); 663 } 664 } 665 catch ( IOException e ) 666 { 667 throw new TransferFailedException( e.getMessage(), e ); 668 } 669 catch ( HttpException e ) 670 { 671 throw new TransferFailedException( e.getMessage(), e ); 672 } 673 } 674 675 protected CloseableHttpResponse execute( HttpUriRequest httpMethod ) 676 throws HttpException, IOException 677 { 678 setHeaders( httpMethod ); 679 String userAgent = getUserAgent( httpMethod ); 680 if ( userAgent != null ) 681 { 682 httpMethod.setHeader( HTTP.USER_AGENT, userAgent ); 683 } 684 685 RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); 686 // WAGON-273: default the cookie-policy to browser compatible 687 requestConfigBuilder.setCookieSpec( CookieSpecs.BROWSER_COMPATIBILITY ); 688 689 Repository repo = getRepository(); 690 ProxyInfo proxyInfo = getProxyInfo( repo.getProtocol(), repo.getHost() ); 691 if ( proxyInfo != null ) 692 { 693 HttpHost proxy = new HttpHost( proxyInfo.getHost(), proxyInfo.getPort() ); 694 requestConfigBuilder.setProxy( proxy ); 695 } 696 697 HttpMethodConfiguration config = 698 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( httpMethod ); 699 700 if ( config != null ) 701 { 702 ConfigurationUtils.copyConfig( config, requestConfigBuilder ); 703 } 704 else 705 { 706 requestConfigBuilder.setSocketTimeout( getReadTimeout() ); 707 } 708 709 localContext.setRequestConfig( requestConfigBuilder.build() ); 710 711 if ( config != null && config.isUsePreemptive() ) 712 { 713 HttpHost targetHost = new HttpHost( repo.getHost(), repo.getPort(), repo.getProtocol() ); 714 AuthScope targetScope = new AuthScope( targetHost ); 715 716 if ( credentialsProvider.getCredentials( targetScope ) != null ) 717 { 718 BasicScheme targetAuth = new BasicScheme(); 719 targetAuth.processChallenge( new BasicHeader(AUTH.WWW_AUTH, "BASIC preemptive" ) ); 720 authCache.put( targetHost, targetAuth ); 721 } 722 } 723 724 if ( proxyInfo != null ) 725 { 726 if ( proxyInfo.getHost() != null ) 727 { 728 HttpHost proxyHost = new HttpHost( proxyInfo.getHost(), proxyInfo.getPort() ); 729 AuthScope proxyScope = new AuthScope( proxyHost ); 730 731 String proxyUsername = proxyInfo.getUserName(); 732 String proxyPassword = proxyInfo.getPassword(); 733 String proxyNtlmHost = proxyInfo.getNtlmHost(); 734 String proxyNtlmDomain = proxyInfo.getNtlmDomain(); 735 736 if ( proxyUsername != null && proxyPassword != null ) 737 { 738 Credentials creds; 739 if ( proxyNtlmHost != null || proxyNtlmDomain != null ) 740 { 741 creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain ); 742 } 743 else 744 { 745 creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword ); 746 } 747 748 credentialsProvider.setCredentials( proxyScope, creds ); 749 BasicScheme proxyAuth = new BasicScheme(); 750 proxyAuth.processChallenge( new BasicHeader(AUTH.PROXY_AUTH, "BASIC preemptive" ) ); 751 authCache.put( proxyHost, proxyAuth ); 752 } 753 } 754 } 755 756 return CLIENT.execute( httpMethod, localContext ); 757 } 758 759 protected void setHeaders( HttpUriRequest method ) 760 { 761 HttpMethodConfiguration config = 762 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method ); 763 if ( config == null || config.isUseDefaultHeaders() ) 764 { 765 // TODO: merge with the other headers and have some better defaults, unify with lightweight headers 766 method.addHeader( "Cache-control", "no-cache" ); 767 method.addHeader( "Cache-store", "no-store" ); 768 method.addHeader( "Pragma", "no-cache" ); 769 method.addHeader( "Expires", "0" ); 770 method.addHeader( "Accept-Encoding", "gzip" ); 771 method.addHeader( "User-Agent", DEFAULT_USER_AGENT ); 772 } 773 774 if ( httpHeaders != null ) 775 { 776 for ( Map.Entry<Object, Object> entry : httpHeaders.entrySet() ) 777 { 778 method.addHeader( (String) entry.getKey(), (String) entry.getValue() ); 779 } 780 } 781 782 Header[] headers = config == null ? null : config.asRequestHeaders(); 783 if ( headers != null ) 784 { 785 for ( Header header : headers ) 786 { 787 method.addHeader( header ); 788 } 789 } 790 } 791 792 protected String getUserAgent( HttpUriRequest method ) 793 { 794 if ( httpHeaders != null ) 795 { 796 String value = (String) httpHeaders.get( "User-Agent" ); 797 if ( value != null ) 798 { 799 return value; 800 } 801 } 802 HttpMethodConfiguration config = 803 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method ); 804 805 if ( config != null ) 806 { 807 return (String) config.getHeaders().get( "User-Agent" ); 808 } 809 return null; 810 } 811 812 /** 813 * getUrl 814 * Implementors can override this to remove unwanted parts of the url such as role-hints 815 * 816 * @param repository 817 * @return 818 */ 819 protected String getURL( Repository repository ) 820 { 821 return repository.getUrl(); 822 } 823 824 public HttpConfiguration getHttpConfiguration() 825 { 826 return httpConfiguration; 827 } 828 829 public void setHttpConfiguration( HttpConfiguration httpConfiguration ) 830 { 831 this.httpConfiguration = httpConfiguration; 832 } 833 834 public void fillInputData( InputData inputData ) 835 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 836 { 837 Resource resource = inputData.getResource(); 838 839 String repositoryUrl = getRepository().getUrl(); 840 String url = repositoryUrl + ( repositoryUrl.endsWith( "/" ) ? "" : "/" ) + resource.getName(); 841 HttpGet getMethod = new HttpGet( url ); 842 long timestamp = resource.getLastModified(); 843 if ( timestamp > 0 ) 844 { 845 SimpleDateFormat fmt = new SimpleDateFormat( "EEE, dd-MMM-yy HH:mm:ss zzz", Locale.US ); 846 fmt.setTimeZone( GMT_TIME_ZONE ); 847 Header hdr = new BasicHeader( "If-Modified-Since", fmt.format( new Date( timestamp ) ) ); 848 fireTransferDebug( "sending ==> " + hdr + "(" + timestamp + ")" ); 849 getMethod.addHeader( hdr ); 850 } 851 852 try 853 { 854 CloseableHttpResponse response = execute( getMethod ); 855 closeable = response; 856 int statusCode = response.getStatusLine().getStatusCode(); 857 858 String reasonPhrase = ", ReasonPhrase:" + response.getStatusLine().getReasonPhrase() + "."; 859 860 fireTransferDebug( url + " - Status code: " + statusCode + reasonPhrase ); 861 862 switch ( statusCode ) 863 { 864 case HttpStatus.SC_OK: 865 break; 866 867 case HttpStatus.SC_NOT_MODIFIED: 868 // return, leaving last modified set to original value so getIfNewer should return unmodified 869 return; 870 case HttpStatus.SC_FORBIDDEN: 871 fireSessionConnectionRefused(); 872 throw new AuthorizationException( "Access denied to: " + url + " " + reasonPhrase ); 873 874 case HttpStatus.SC_UNAUTHORIZED: 875 fireSessionConnectionRefused(); 876 throw new AuthorizationException( "Not authorized " + reasonPhrase ); 877 878 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: 879 fireSessionConnectionRefused(); 880 throw new AuthorizationException( "Not authorized by proxy " + reasonPhrase ); 881 882 case HttpStatus.SC_NOT_FOUND: 883 throw new ResourceDoesNotExistException( "File: " + url + " " + reasonPhrase ); 884 885 // add more entries here 886 default: 887 { 888 cleanupGetTransfer( resource ); 889 TransferFailedException e = new TransferFailedException( 890 "Failed to transfer file: " + url + ". Return code is: " + statusCode + " " + reasonPhrase ); 891 fireTransferError( resource, e, TransferEvent.REQUEST_GET ); 892 throw e; 893 } 894 } 895 896 Header contentLengthHeader = response.getFirstHeader( "Content-Length" ); 897 898 if ( contentLengthHeader != null ) 899 { 900 try 901 { 902 long contentLength = Long.parseLong( contentLengthHeader.getValue() ); 903 904 resource.setContentLength( contentLength ); 905 } 906 catch ( NumberFormatException e ) 907 { 908 fireTransferDebug( 909 "error parsing content length header '" + contentLengthHeader.getValue() + "' " + e ); 910 } 911 } 912 913 Header lastModifiedHeader = response.getFirstHeader( "Last-Modified" ); 914 if ( lastModifiedHeader != null ) 915 { 916 Date lastModified = DateUtils.parseDate( lastModifiedHeader.getValue() ); 917 if ( lastModified != null ) 918 { 919 resource.setLastModified( lastModified.getTime() ); 920 fireTransferDebug( "last-modified = " + lastModifiedHeader.getValue() + 921 " (" + lastModified.getTime() + ")" ); 922 } 923 } 924 925 HttpEntity entity = response.getEntity(); 926 if ( entity != null ) 927 { 928 inputData.setInputStream( entity.getContent() ); 929 } 930 } 931 catch ( IOException e ) 932 { 933 fireTransferError( resource, e, TransferEvent.REQUEST_GET ); 934 935 throw new TransferFailedException( e.getMessage(), e ); 936 } 937 catch ( HttpException e ) 938 { 939 fireTransferError( resource, e, TransferEvent.REQUEST_GET ); 940 941 throw new TransferFailedException( e.getMessage(), e ); 942 } 943 } 944 945 protected void cleanupGetTransfer( Resource resource ) 946 { 947 if ( closeable != null ) 948 { 949 try 950 { 951 closeable.close(); 952 } 953 catch ( IOException ignore ) 954 { 955 } 956 957 } 958 } 959 960 961 @Override 962 public void putFromStream( InputStream stream, String destination ) 963 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 964 { 965 putFromStream( stream, destination, -1, -1 ); 966 } 967 968 @Override 969 protected void putFromStream( InputStream stream, Resource resource ) 970 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException 971 { 972 putFromStream( stream, resource.getName(), -1, -1 ); 973 } 974 975 public Properties getHttpHeaders() 976 { 977 return httpHeaders; 978 } 979 980 public void setHttpHeaders( Properties httpHeaders ) 981 { 982 this.httpHeaders = httpHeaders; 983 } 984 985 @Override 986 public void fillOutputData( OutputData outputData ) 987 throws TransferFailedException 988 { 989 // no needed in this implementation but throw an Exception if used 990 throw new IllegalStateException( "this wagon http client must not use fillOutputData" ); 991 } 992}