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