View Javadoc
1   package org.apache.maven.wagon.providers.http;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.commons.io.IOUtils;
23  import org.apache.maven.wagon.ConnectionException;
24  import org.apache.maven.wagon.InputData;
25  import org.apache.maven.wagon.OutputData;
26  import org.apache.maven.wagon.ResourceDoesNotExistException;
27  import org.apache.maven.wagon.StreamWagon;
28  import org.apache.maven.wagon.TransferFailedException;
29  import org.apache.maven.wagon.authentication.AuthenticationException;
30  import org.apache.maven.wagon.authorization.AuthorizationException;
31  import org.apache.maven.wagon.events.TransferEvent;
32  import org.apache.maven.wagon.proxy.ProxyInfo;
33  import org.apache.maven.wagon.resource.Resource;
34  import org.apache.maven.wagon.shared.http.EncodingUtil;
35  import org.apache.maven.wagon.shared.http.HtmlFileListParser;
36  import org.codehaus.plexus.util.Base64;
37  
38  import java.io.FileNotFoundException;
39  import java.io.IOException;
40  import java.io.InputStream;
41  import java.io.OutputStream;
42  import java.net.HttpURLConnection;
43  import java.net.InetSocketAddress;
44  import java.net.MalformedURLException;
45  import java.net.PasswordAuthentication;
46  import java.net.Proxy;
47  import java.net.Proxy.Type;
48  import java.net.SocketAddress;
49  import java.net.URL;
50  import java.util.ArrayList;
51  import java.util.List;
52  import java.util.Properties;
53  import java.util.zip.GZIPInputStream;
54  
55  /**
56   * LightweightHttpWagon, using JDK's HttpURLConnection.
57   *
58   * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a>
59   * @plexus.component role="org.apache.maven.wagon.Wagon" role-hint="http" instantiation-strategy="per-lookup"
60   * @see HttpURLConnection
61   */
62  public class LightweightHttpWagon
63      extends StreamWagon
64  {
65      private boolean preemptiveAuthentication;
66  
67      private HttpURLConnection putConnection;
68  
69      private Proxy proxy = Proxy.NO_PROXY;
70  
71      public static final int MAX_REDIRECTS = 10;
72  
73      /**
74       * Whether to use any proxy cache or not.
75       *
76       * @plexus.configuration default="false"
77       */
78      private boolean useCache;
79  
80      /**
81       * @plexus.configuration
82       */
83      private Properties httpHeaders;
84  
85      /**
86       * @plexus.requirement
87       */
88      private volatile LightweightHttpWagonAuthenticator authenticator;
89  
90      /**
91       * Builds a complete URL string from the repository URL and the relative path of the resource passed.
92       *
93       * @param resource the resource to extract the relative path from.
94       * @return the complete URL
95       */
96      private String buildUrl( Resource resource )
97      {
98          return EncodingUtil.encodeURLToString( getRepository().getUrl(), resource.getName() );
99      }
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 }