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