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