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.HtmlFileListParser;
35  import org.codehaus.plexus.util.Base64;
36  
37  import java.io.FileNotFoundException;
38  import java.io.IOException;
39  import java.io.InputStream;
40  import java.io.OutputStream;
41  import java.net.HttpURLConnection;
42  import java.net.InetSocketAddress;
43  import java.net.MalformedURLException;
44  import java.net.PasswordAuthentication;
45  import java.net.Proxy;
46  import java.net.Proxy.Type;
47  import java.net.SocketAddress;
48  import java.net.URL;
49  import java.util.ArrayList;
50  import java.util.List;
51  import java.util.Properties;
52  import java.util.zip.GZIPInputStream;
53  
54  /**
55   * LightweightHttpWagon, using JDK's HttpURLConnection.
56   *
57   * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a>
58   *
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 passed.
92       *
93       * @param path the relative path
94       * @return the complete URL
95       */
96      private String buildUrl( String path )
97      {
98          final String repoUrl = getRepository().getUrl();
99  
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 }