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.commons.io.IOUtils; 023import org.apache.maven.wagon.ConnectionException; 024import org.apache.maven.wagon.InputData; 025import org.apache.maven.wagon.OutputData; 026import org.apache.maven.wagon.ResourceDoesNotExistException; 027import org.apache.maven.wagon.StreamWagon; 028import org.apache.maven.wagon.TransferFailedException; 029import org.apache.maven.wagon.authentication.AuthenticationException; 030import org.apache.maven.wagon.authorization.AuthorizationException; 031import org.apache.maven.wagon.events.TransferEvent; 032import org.apache.maven.wagon.proxy.ProxyInfo; 033import org.apache.maven.wagon.resource.Resource; 034import org.apache.maven.wagon.shared.http.EncodingUtil; 035import org.apache.maven.wagon.shared.http.HtmlFileListParser; 036import org.codehaus.plexus.util.Base64; 037 038import java.io.FileNotFoundException; 039import java.io.IOException; 040import java.io.InputStream; 041import java.io.OutputStream; 042import java.net.HttpURLConnection; 043import java.net.InetSocketAddress; 044import java.net.MalformedURLException; 045import java.net.PasswordAuthentication; 046import java.net.Proxy; 047import java.net.Proxy.Type; 048import java.net.SocketAddress; 049import java.net.URL; 050import java.util.ArrayList; 051import java.util.List; 052import java.util.Properties; 053import java.util.zip.DeflaterInputStream; 054import java.util.zip.GZIPInputStream; 055 056/** 057 * LightweightHttpWagon, using JDK's HttpURLConnection. 058 * 059 * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a> 060 * @plexus.component role="org.apache.maven.wagon.Wagon" role-hint="http" instantiation-strategy="per-lookup" 061 * @see HttpURLConnection 062 */ 063public class LightweightHttpWagon 064 extends StreamWagon 065{ 066 private boolean preemptiveAuthentication; 067 068 private HttpURLConnection putConnection; 069 070 private Proxy proxy = Proxy.NO_PROXY; 071 072 public static final int MAX_REDIRECTS = 10; 073 074 /** 075 * Whether to use any proxy cache or not. 076 * 077 * @plexus.configuration default="false" 078 */ 079 private boolean useCache; 080 081 /** 082 * @plexus.configuration 083 */ 084 private Properties httpHeaders; 085 086 /** 087 * @plexus.requirement 088 */ 089 private volatile LightweightHttpWagonAuthenticator authenticator; 090 091 /** 092 * Builds a complete URL string from the repository URL and the relative path of the resource passed. 093 * 094 * @param resource the resource to extract the relative path from. 095 * @return the complete URL 096 */ 097 private String buildUrl( Resource resource ) 098 { 099 return EncodingUtil.encodeURLToString( getRepository().getUrl(), resource.getName() ); 100 } 101 102 public void fillInputData( InputData inputData ) 103 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 104 { 105 Resource resource = inputData.getResource(); 106 107 String visitingUrl = buildUrl( resource ); 108 try 109 { 110 List<String> visitedUrls = new ArrayList<String>(); 111 112 for ( int redirectCount = 0; redirectCount < MAX_REDIRECTS; redirectCount++ ) 113 { 114 if ( visitedUrls.contains( visitingUrl ) ) 115 { 116 throw new TransferFailedException( "Cyclic http redirect detected. Aborting! " + visitingUrl ); 117 } 118 visitedUrls.add( visitingUrl ); 119 120 URL url = new URL( visitingUrl ); 121 HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection( this.proxy ); 122 123 urlConnection.setRequestProperty( "Accept-Encoding", "gzip,deflate" ); 124 if ( !useCache ) 125 { 126 urlConnection.setRequestProperty( "Pragma", "no-cache" ); 127 } 128 129 addHeaders( urlConnection ); 130 131 // TODO: handle all response codes 132 int responseCode = urlConnection.getResponseCode(); 133 if ( responseCode == HttpURLConnection.HTTP_FORBIDDEN 134 || responseCode == HttpURLConnection.HTTP_UNAUTHORIZED ) 135 { 136 throw new AuthorizationException( "Access denied to: " + buildUrl( resource ) ); 137 } 138 if ( responseCode == HttpURLConnection.HTTP_MOVED_PERM 139 || responseCode == HttpURLConnection.HTTP_MOVED_TEMP ) 140 { 141 visitingUrl = urlConnection.getHeaderField( "Location" ); 142 continue; 143 } 144 145 InputStream is = urlConnection.getInputStream(); 146 String contentEncoding = urlConnection.getHeaderField( "Content-Encoding" ); 147 boolean isGZipped = contentEncoding != null && "gzip".equalsIgnoreCase( contentEncoding ); 148 if ( isGZipped ) 149 { 150 is = new GZIPInputStream( is ); 151 } 152 boolean isDeflated = contentEncoding != null && "deflate".equalsIgnoreCase( contentEncoding ); 153 if ( isDeflated ) 154 { 155 is = new DeflaterInputStream( is ); 156 } 157 inputData.setInputStream( is ); 158 resource.setLastModified( urlConnection.getLastModified() ); 159 resource.setContentLength( urlConnection.getContentLength() ); 160 break; 161 } 162 } 163 catch ( MalformedURLException e ) 164 { 165 throw new ResourceDoesNotExistException( "Invalid repository URL: " + e.getMessage(), e ); 166 } 167 catch ( FileNotFoundException e ) 168 { 169 throw new ResourceDoesNotExistException( "Unable to locate resource in repository", e ); 170 } 171 catch ( IOException e ) 172 { 173 StringBuilder message = new StringBuilder( "Error transferring file: " ); 174 message.append( e.getMessage() ); 175 message.append( " from " + visitingUrl ); 176 if ( getProxyInfo() != null && getProxyInfo().getHost() != null ) 177 { 178 message.append( " with proxyInfo " ).append( getProxyInfo().toString() ); 179 } 180 throw new TransferFailedException( message.toString(), e ); 181 } 182 } 183 184 private void addHeaders( HttpURLConnection urlConnection ) 185 { 186 if ( httpHeaders != null ) 187 { 188 for ( Object header : httpHeaders.keySet() ) 189 { 190 urlConnection.setRequestProperty( (String) header, httpHeaders.getProperty( (String) header ) ); 191 } 192 } 193 setAuthorization( urlConnection ); 194 } 195 196 private void setAuthorization( HttpURLConnection urlConnection ) 197 { 198 if ( preemptiveAuthentication && authenticationInfo != null && authenticationInfo.getUserName() != null ) 199 { 200 String credentials = authenticationInfo.getUserName() + ":" + authenticationInfo.getPassword(); 201 String encoded = new String( Base64.encodeBase64( credentials.getBytes() ) ); 202 urlConnection.setRequestProperty( "Authorization", "Basic " + encoded ); 203 } 204 } 205 206 public void fillOutputData( OutputData outputData ) 207 throws TransferFailedException 208 { 209 Resource resource = outputData.getResource(); 210 try 211 { 212 URL url = new URL( buildUrl( resource ) ); 213 putConnection = (HttpURLConnection) url.openConnection( this.proxy ); 214 215 addHeaders( putConnection ); 216 217 putConnection.setRequestMethod( "PUT" ); 218 putConnection.setDoOutput( true ); 219 outputData.setOutputStream( putConnection.getOutputStream() ); 220 } 221 catch ( IOException e ) 222 { 223 throw new TransferFailedException( "Error transferring file: " + e.getMessage(), e ); 224 } 225 } 226 227 protected void finishPutTransfer( Resource resource, InputStream input, OutputStream output ) 228 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException 229 { 230 try 231 { 232 int statusCode = putConnection.getResponseCode(); 233 234 switch ( statusCode ) 235 { 236 // Success Codes 237 case HttpURLConnection.HTTP_OK: // 200 238 case HttpURLConnection.HTTP_CREATED: // 201 239 case HttpURLConnection.HTTP_ACCEPTED: // 202 240 case HttpURLConnection.HTTP_NO_CONTENT: // 204 241 break; 242 243 case HttpURLConnection.HTTP_FORBIDDEN: 244 throw new AuthorizationException( "Access denied to: " + buildUrl( resource ) ); 245 246 case HttpURLConnection.HTTP_NOT_FOUND: 247 throw new ResourceDoesNotExistException( "File: " + buildUrl( resource ) + " does not exist" ); 248 249 // add more entries here 250 default: 251 throw new TransferFailedException( 252 "Failed to transfer file: " + buildUrl( resource ) + ". Return code is: " + statusCode ); 253 } 254 } 255 catch ( IOException e ) 256 { 257 fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); 258 259 throw new TransferFailedException( "Error transferring file: " + e.getMessage(), e ); 260 } 261 } 262 263 protected void openConnectionInternal() 264 throws ConnectionException, AuthenticationException 265 { 266 final ProxyInfo proxyInfo = getProxyInfo( "http", getRepository().getHost() ); 267 if ( proxyInfo != null ) 268 { 269 this.proxy = getProxy( proxyInfo ); 270 this.proxyInfo = proxyInfo; 271 } 272 authenticator.setWagon( this ); 273 274 boolean usePreemptiveAuthentication = 275 Boolean.getBoolean( "maven.wagon.http.preemptiveAuthentication" ) || Boolean.parseBoolean( 276 repository.getParameter( "preemptiveAuthentication" ) ) || this.preemptiveAuthentication; 277 278 setPreemptiveAuthentication( usePreemptiveAuthentication ); 279 } 280 281 @SuppressWarnings( "deprecation" ) 282 public PasswordAuthentication requestProxyAuthentication() 283 { 284 if ( proxyInfo != null && proxyInfo.getUserName() != null ) 285 { 286 String password = ""; 287 if ( proxyInfo.getPassword() != null ) 288 { 289 password = proxyInfo.getPassword(); 290 } 291 return new PasswordAuthentication( proxyInfo.getUserName(), password.toCharArray() ); 292 } 293 return null; 294 } 295 296 public PasswordAuthentication requestServerAuthentication() 297 { 298 if ( authenticationInfo != null && authenticationInfo.getUserName() != null ) 299 { 300 String password = ""; 301 if ( authenticationInfo.getPassword() != null ) 302 { 303 password = authenticationInfo.getPassword(); 304 } 305 return new PasswordAuthentication( authenticationInfo.getUserName(), password.toCharArray() ); 306 } 307 return null; 308 } 309 310 private Proxy getProxy( ProxyInfo proxyInfo ) 311 { 312 return new Proxy( getProxyType( proxyInfo ), getSocketAddress( proxyInfo ) ); 313 } 314 315 private Type getProxyType( ProxyInfo proxyInfo ) 316 { 317 if ( ProxyInfo.PROXY_SOCKS4.equals( proxyInfo.getType() ) || ProxyInfo.PROXY_SOCKS5.equals( 318 proxyInfo.getType() ) ) 319 { 320 return Type.SOCKS; 321 } 322 else 323 { 324 return Type.HTTP; 325 } 326 } 327 328 public SocketAddress getSocketAddress( ProxyInfo proxyInfo ) 329 { 330 return InetSocketAddress.createUnresolved( proxyInfo.getHost(), proxyInfo.getPort() ); 331 } 332 333 public void closeConnection() 334 throws ConnectionException 335 { 336 //FIXME WAGON-375 use persistent connection feature provided by the jdk 337 if ( putConnection != null ) 338 { 339 putConnection.disconnect(); 340 } 341 authenticator.resetWagon(); 342 } 343 344 public List<String> getFileList( String destinationDirectory ) 345 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 346 { 347 InputData inputData = new InputData(); 348 349 if ( destinationDirectory.length() > 0 && !destinationDirectory.endsWith( "/" ) ) 350 { 351 destinationDirectory += "/"; 352 } 353 354 String url = buildUrl( new Resource( destinationDirectory ) ); 355 356 Resource resource = new Resource( destinationDirectory ); 357 358 inputData.setResource( resource ); 359 360 fillInputData( inputData ); 361 362 InputStream is = inputData.getInputStream(); 363 364 try 365 { 366 367 if ( is == null ) 368 { 369 throw new TransferFailedException( 370 url + " - Could not open input stream for resource: '" + resource + "'" ); 371 } 372 373 final List<String> htmlFileList = HtmlFileListParser.parseFileList( url, is ); 374 is.close(); 375 is = null; 376 return htmlFileList; 377 } 378 catch ( final IOException e ) 379 { 380 throw new TransferFailedException( "Failure transferring " + resource.getName(), e ); 381 } 382 finally 383 { 384 IOUtils.closeQuietly( is ); 385 } 386 } 387 388 public boolean resourceExists( String resourceName ) 389 throws TransferFailedException, AuthorizationException 390 { 391 HttpURLConnection headConnection; 392 393 try 394 { 395 Resource resource = new Resource( resourceName ); 396 URL url = new URL( buildUrl( resource ) ); 397 headConnection = (HttpURLConnection) url.openConnection( this.proxy ); 398 399 addHeaders( headConnection ); 400 401 headConnection.setRequestMethod( "HEAD" ); 402 headConnection.setDoOutput( true ); 403 404 int statusCode = headConnection.getResponseCode(); 405 406 switch ( statusCode ) 407 { 408 case HttpURLConnection.HTTP_OK: 409 return true; 410 411 case HttpURLConnection.HTTP_FORBIDDEN: 412 throw new AuthorizationException( "Access denied to: " + url ); 413 414 case HttpURLConnection.HTTP_NOT_FOUND: 415 return false; 416 417 case HttpURLConnection.HTTP_UNAUTHORIZED: 418 throw new AuthorizationException( "Access denied to: " + url ); 419 420 default: 421 throw new TransferFailedException( 422 "Failed to look for file: " + buildUrl( resource ) + ". Return code is: " + statusCode ); 423 } 424 } 425 catch ( IOException e ) 426 { 427 throw new TransferFailedException( "Error transferring file: " + e.getMessage(), e ); 428 } 429 } 430 431 public boolean isUseCache() 432 { 433 return useCache; 434 } 435 436 public void setUseCache( boolean useCache ) 437 { 438 this.useCache = useCache; 439 } 440 441 public Properties getHttpHeaders() 442 { 443 return httpHeaders; 444 } 445 446 public void setHttpHeaders( Properties httpHeaders ) 447 { 448 this.httpHeaders = httpHeaders; 449 } 450 451 void setSystemProperty( String key, String value ) 452 { 453 if ( value != null ) 454 { 455 System.setProperty( key, value ); 456 } 457 else 458 { 459 System.getProperties().remove( key ); 460 } 461 } 462 463 public void setPreemptiveAuthentication( boolean preemptiveAuthentication ) 464 { 465 this.preemptiveAuthentication = preemptiveAuthentication; 466 } 467 468 public LightweightHttpWagonAuthenticator getAuthenticator() 469 { 470 return authenticator; 471 } 472 473 public void setAuthenticator( LightweightHttpWagonAuthenticator authenticator ) 474 { 475 this.authenticator = authenticator; 476 } 477}