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