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.maven.wagon.ConnectionException;
23  import org.apache.maven.wagon.InputData;
24  import org.apache.maven.wagon.OutputData;
25  import org.apache.maven.wagon.ResourceDoesNotExistException;
26  import org.apache.maven.wagon.StreamWagon;
27  import org.apache.maven.wagon.TransferFailedException;
28  import org.apache.maven.wagon.authentication.AuthenticationException;
29  import org.apache.maven.wagon.authorization.AuthorizationException;
30  import org.apache.maven.wagon.events.TransferEvent;
31  import org.apache.maven.wagon.proxy.ProxyInfo;
32  import org.apache.maven.wagon.resource.Resource;
33  import org.apache.maven.wagon.shared.http.HtmlFileListParser;
34  import org.codehaus.plexus.util.Base64;
35  
36  import java.io.FileNotFoundException;
37  import java.io.IOException;
38  import java.io.InputStream;
39  import java.io.OutputStream;
40  import java.net.HttpURLConnection;
41  import java.net.InetSocketAddress;
42  import java.net.MalformedURLException;
43  import java.net.PasswordAuthentication;
44  import java.net.Proxy;
45  import java.net.Proxy.Type;
46  import java.net.SocketAddress;
47  import java.net.URL;
48  import java.util.ArrayList;
49  import java.util.Iterator;
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   * @version $Id: LightweightHttpWagon.java 1174807 2011-09-23 14:28:36Z olamy $
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 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 ( Iterator<?> i = httpHeaders.keySet().iterator(); i.hasNext(); )
192             {
193                 String header = (String) i.next();
194                 urlConnection.setRequestProperty( header, httpHeaders.getProperty( header ) );
195             }
196         }
197         setAuthorization( urlConnection );
198     }
199 
200     private void setAuthorization( HttpURLConnection urlConnection )
201     {
202         if ( preemptiveAuthentication && authenticationInfo != null && authenticationInfo.getUserName() != null )
203         {
204             String credentials = authenticationInfo.getUserName() + ":" + authenticationInfo.getPassword();
205             String encoded = new String( Base64.encodeBase64( credentials.getBytes() ) );
206             urlConnection.setRequestProperty( "Authorization", "Basic " + encoded );
207         }
208     }
209 
210     public void fillOutputData( OutputData outputData )
211         throws TransferFailedException
212     {
213         Resource resource = outputData.getResource();
214         try
215         {
216             URL url = new URL( buildUrl( resource.getName() ) );
217             putConnection = (HttpURLConnection) url.openConnection( this.proxy );
218 
219             addHeaders( putConnection );
220 
221             putConnection.setRequestMethod( "PUT" );
222             putConnection.setDoOutput( true );
223             outputData.setOutputStream( putConnection.getOutputStream() );
224         }
225         catch ( IOException e )
226         {
227             throw new TransferFailedException( "Error transferring file: " + e.getMessage(), e );
228         }
229     }
230 
231     protected void finishPutTransfer( Resource resource, InputStream input, OutputStream output )
232         throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
233     {
234         try
235         {
236             int statusCode = putConnection.getResponseCode();
237 
238             switch ( statusCode )
239             {
240                 // Success Codes
241                 case HttpURLConnection.HTTP_OK: // 200
242                 case HttpURLConnection.HTTP_CREATED: // 201
243                 case HttpURLConnection.HTTP_ACCEPTED: // 202
244                 case HttpURLConnection.HTTP_NO_CONTENT: // 204
245                     break;
246 
247                 case HttpURLConnection.HTTP_FORBIDDEN:
248                     throw new AuthorizationException( "Access denied to: " + buildUrl( resource.getName() ) );
249 
250                 case HttpURLConnection.HTTP_NOT_FOUND:
251                     throw new ResourceDoesNotExistException(
252                         "File: " + buildUrl( resource.getName() ) + " does not exist" );
253 
254                     // add more entries here
255                 default:
256                     throw new TransferFailedException(
257                         "Failed to transfer file: " + buildUrl( resource.getName() ) + ". Return code is: "
258                             + statusCode );
259             }
260         }
261         catch ( IOException e )
262         {
263             fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
264 
265             throw new TransferFailedException( "Error transferring file: " + e.getMessage(), e );
266         }
267     }
268 
269     protected void openConnectionInternal()
270         throws ConnectionException, AuthenticationException
271     {
272         final ProxyInfo proxyInfo = getProxyInfo( "http", getRepository().getHost() );
273         if ( proxyInfo != null )
274         {
275             this.proxy = getProxy( proxyInfo );
276         }
277         authenticator.setWagon( this );
278 
279         boolean usePreemptiveAuthentication =
280             Boolean.getBoolean( "maven.wagon.http.preemptiveAuthentication" ) || Boolean.parseBoolean(
281                 repository.getParameter( "preemptiveAuthentication" ) ) || this.preemptiveAuthentication;
282 
283         setPreemptiveAuthentication( usePreemptiveAuthentication );
284     }
285 
286     @SuppressWarnings( "deprecation" )
287     public PasswordAuthentication requestProxyAuthentication()
288     {
289         if ( proxyInfo != null && proxyInfo.getUserName() != null )
290         {
291             String password = "";
292             if ( proxyInfo.getPassword() != null )
293             {
294                 password = proxyInfo.getPassword();
295             }
296             return new PasswordAuthentication( proxyInfo.getUserName(), password.toCharArray() );
297         }
298         return null;
299     }
300 
301     public PasswordAuthentication requestServerAuthentication()
302     {
303         if ( authenticationInfo != null && authenticationInfo.getUserName() != null )
304         {
305             String password = "";
306             if ( authenticationInfo.getPassword() != null )
307             {
308                 password = authenticationInfo.getPassword();
309             }
310             return new PasswordAuthentication( authenticationInfo.getUserName(), password.toCharArray() );
311         }
312         return null;
313     }
314 
315     private Proxy getProxy( ProxyInfo proxyInfo )
316     {
317         return new Proxy( getProxyType( proxyInfo ), getSocketAddress( proxyInfo ) );
318     }
319 
320     private Type getProxyType( ProxyInfo proxyInfo )
321     {
322         if ( ProxyInfo.PROXY_SOCKS4.equals( proxyInfo.getType() ) || ProxyInfo.PROXY_SOCKS5.equals(
323             proxyInfo.getType() ) )
324         {
325             return Type.SOCKS;
326         }
327         else
328         {
329             return Type.HTTP;
330         }
331     }
332 
333     public SocketAddress getSocketAddress( ProxyInfo proxyInfo )
334     {
335         return InetSocketAddress.createUnresolved( proxyInfo.getHost(), proxyInfo.getPort() );
336     }
337 
338     public void closeConnection()
339         throws ConnectionException
340     {
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         if ( is == null )
369         {
370             throw new TransferFailedException(
371                 url + " - Could not open input stream for resource: '" + resource + "'" );
372         }
373 
374         return HtmlFileListParser.parseFileList( url, is );
375     }
376 
377     public boolean resourceExists( String resourceName )
378         throws TransferFailedException, AuthorizationException
379     {
380         HttpURLConnection headConnection;
381 
382         try
383         {
384             URL url = new URL( buildUrl( new Resource( resourceName ).getName() ) );
385             headConnection = (HttpURLConnection) url.openConnection( this.proxy );
386 
387             addHeaders( headConnection );
388 
389             headConnection.setRequestMethod( "HEAD" );
390             headConnection.setDoOutput( true );
391 
392             int statusCode = headConnection.getResponseCode();
393 
394             switch ( statusCode )
395             {
396                 case HttpURLConnection.HTTP_OK:
397                     return true;
398 
399                 case HttpURLConnection.HTTP_FORBIDDEN:
400                     throw new AuthorizationException( "Access denied to: " + url );
401 
402                 case HttpURLConnection.HTTP_NOT_FOUND:
403                     return false;
404 
405                 case HttpURLConnection.HTTP_UNAUTHORIZED:
406                     throw new AuthorizationException( "Access denied to: " + url );
407 
408                 default:
409                     throw new TransferFailedException(
410                         "Failed to look for file: " + buildUrl( resourceName ) + ". Return code is: " + statusCode );
411             }
412         }
413         catch ( IOException e )
414         {
415             throw new TransferFailedException( "Error transferring file: " + e.getMessage(), e );
416         }
417     }
418 
419     public boolean isUseCache()
420     {
421         return useCache;
422     }
423 
424     public void setUseCache( boolean useCache )
425     {
426         this.useCache = useCache;
427     }
428 
429     public Properties getHttpHeaders()
430     {
431         return httpHeaders;
432     }
433 
434     public void setHttpHeaders( Properties httpHeaders )
435     {
436         this.httpHeaders = httpHeaders;
437     }
438 
439     void setSystemProperty( String key, String value )
440     {
441         if ( value != null )
442         {
443             System.setProperty( key, value );
444         }
445         else
446         {
447             System.getProperties().remove( key );
448         }
449     }
450 
451     public void setPreemptiveAuthentication( boolean preemptiveAuthentication )
452     {
453         this.preemptiveAuthentication = preemptiveAuthentication;
454     }
455 }