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