1   package org.apache.maven.wagon.providers.http;
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
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.EncodingUtil;
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.List;
50  import java.util.Properties;
51  import java.util.regex.Matcher;
52  import java.util.regex.Pattern;
53  import java.util.zip.DeflaterInputStream;
54  import java.util.zip.GZIPInputStream;
55  
56  import static java.lang.Integer.parseInt;
57  import static org.apache.maven.wagon.shared.http.HttpMessageUtils.UNKNOWN_STATUS_CODE;
58  import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatAuthorizationMessage;
59  import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatResourceDoesNotExistMessage;
60  import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatTransferFailedMessage;
61  
62  
63  
64  
65  
66  
67  
68  
69  public class LightweightHttpWagon
70      extends StreamWagon
71  {
72      private boolean preemptiveAuthentication;
73  
74      private HttpURLConnection putConnection;
75  
76      private Proxy proxy = Proxy.NO_PROXY;
77  
78      private static final Pattern IOEXCEPTION_MESSAGE_PATTERN = Pattern.compile( "Server returned HTTP response code: "
79              + "(\\d\\d\\d) for URL: (.*)" );
80  
81      public static final int MAX_REDIRECTS = 10;
82  
83      
84  
85  
86  
87  
88      private boolean useCache;
89  
90      
91  
92  
93      private Properties httpHeaders;
94  
95      
96  
97  
98      private volatile LightweightHttpWagonAuthenticator authenticator;
99  
100     
101 
102 
103 
104 
105 
106     private String buildUrl( Resource resource )
107     {
108         return EncodingUtil.encodeURLToString( getRepository().getUrl(), resource.getName() );
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 );
117 
118         List<String> visitedUrls = new ArrayList<>();
119 
120         for ( int redirectCount = 0; redirectCount < MAX_REDIRECTS; redirectCount++ )
121         {
122             if ( visitedUrls.contains( visitingUrl ) )
123             {
124                 
125                 throw new TransferFailedException( "Cyclic http redirect detected. Aborting! " + visitingUrl );
126             }
127             visitedUrls.add( visitingUrl );
128 
129             URL url = null;
130             try
131             {
132                 url = new URL( visitingUrl );
133             }
134             catch ( MalformedURLException e )
135             {
136                 
137                 throw new ResourceDoesNotExistException( "Invalid repository URL: " + e.getMessage(), e );
138             }
139 
140             HttpURLConnection urlConnection = null;
141 
142             try
143             {
144                 urlConnection = ( HttpURLConnection ) url.openConnection( this.proxy );
145             }
146             catch ( IOException e )
147             {
148                 
149                 String message = formatTransferFailedMessage( visitingUrl, UNKNOWN_STATUS_CODE,
150                         null, getProxyInfo() );
151                 
152                 throw new TransferFailedException( message, e );
153             }
154 
155             try
156             {
157 
158                 urlConnection.setRequestProperty( "Accept-Encoding", "gzip,deflate" );
159                 if ( !useCache )
160                 {
161                     urlConnection.setRequestProperty( "Pragma", "no-cache" );
162                 }
163 
164                 addHeaders( urlConnection );
165 
166                 
167                 int responseCode = urlConnection.getResponseCode();
168                 String reasonPhrase = urlConnection.getResponseMessage();
169 
170                 
171                 if ( responseCode == HttpURLConnection.HTTP_FORBIDDEN
172                         || responseCode == HttpURLConnection.HTTP_UNAUTHORIZED
173                         || responseCode == HttpURLConnection.HTTP_PROXY_AUTH )
174                 {
175                     throw new AuthorizationException( formatAuthorizationMessage( buildUrl( resource ),
176                             responseCode, reasonPhrase, getProxyInfo() ) );
177                 }
178                 if ( responseCode == HttpURLConnection.HTTP_MOVED_PERM
179                         || responseCode == HttpURLConnection.HTTP_MOVED_TEMP )
180                 {
181                     visitingUrl = urlConnection.getHeaderField( "Location" );
182                     continue;
183                 }
184 
185                 InputStream is = urlConnection.getInputStream();
186                 String contentEncoding = urlConnection.getHeaderField( "Content-Encoding" );
187                 boolean isGZipped = contentEncoding != null && "gzip".equalsIgnoreCase( contentEncoding );
188                 if ( isGZipped )
189                 {
190                     is = new GZIPInputStream( is );
191                 }
192                 boolean isDeflated = contentEncoding != null && "deflate".equalsIgnoreCase( contentEncoding );
193                 if ( isDeflated )
194                 {
195                     is = new DeflaterInputStream( is );
196                 }
197                 inputData.setInputStream( is );
198                 resource.setLastModified( urlConnection.getLastModified() );
199                 resource.setContentLength( urlConnection.getContentLength() );
200                 break;
201 
202             }
203             catch ( FileNotFoundException e )
204             {
205                 
206                 
207                 throw new ResourceDoesNotExistException( formatResourceDoesNotExistMessage( buildUrl( resource ),
208                         UNKNOWN_STATUS_CODE, null, getProxyInfo() ), e );
209             }
210             catch ( IOException originalIOException )
211             {
212                 throw convertHttpUrlConnectionException( originalIOException, urlConnection, buildUrl( resource ) );
213             }
214 
215         }
216 
217     }
218 
219     private void addHeaders( HttpURLConnection urlConnection )
220     {
221         if ( httpHeaders != null )
222         {
223             for ( Object header : httpHeaders.keySet() )
224             {
225                 urlConnection.setRequestProperty( (String) header, httpHeaders.getProperty( (String) header ) );
226             }
227         }
228         setAuthorization( urlConnection );
229     }
230 
231     private void setAuthorization( HttpURLConnection urlConnection )
232     {
233         if ( preemptiveAuthentication && authenticationInfo != null && authenticationInfo.getUserName() != null )
234         {
235             String credentials = authenticationInfo.getUserName() + ":" + authenticationInfo.getPassword();
236             String encoded = new String( Base64.encodeBase64( credentials.getBytes() ) );
237             urlConnection.setRequestProperty( "Authorization", "Basic " + encoded );
238         }
239     }
240 
241     public void fillOutputData( OutputData outputData )
242         throws TransferFailedException
243     {
244         Resource resource = outputData.getResource();
245         try
246         {
247             URL url = new URL( buildUrl( resource ) );
248             putConnection = (HttpURLConnection) url.openConnection( this.proxy );
249 
250             addHeaders( putConnection );
251 
252             putConnection.setRequestMethod( "PUT" );
253             putConnection.setDoOutput( true );
254             outputData.setOutputStream( putConnection.getOutputStream() );
255         }
256         catch ( IOException e )
257         {
258             throw new TransferFailedException( "Error transferring file: " + e.getMessage(), e );
259         }
260     }
261 
262     protected void finishPutTransfer( Resource resource, InputStream input, OutputStream output )
263         throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
264     {
265         try
266         {
267             String reasonPhrase = putConnection.getResponseMessage();
268             int statusCode = putConnection.getResponseCode();
269 
270             switch ( statusCode )
271             {
272                 
273                 case HttpURLConnection.HTTP_OK: 
274                 case HttpURLConnection.HTTP_CREATED: 
275                 case HttpURLConnection.HTTP_ACCEPTED: 
276                 case HttpURLConnection.HTTP_NO_CONTENT: 
277                     break;
278 
279                 
280                 case HttpURLConnection.HTTP_FORBIDDEN:
281                 case HttpURLConnection.HTTP_UNAUTHORIZED:
282                 case HttpURLConnection.HTTP_PROXY_AUTH:
283                     throw new AuthorizationException( formatAuthorizationMessage( buildUrl( resource ), statusCode,
284                             reasonPhrase, getProxyInfo() ) );
285 
286                 case HttpURLConnection.HTTP_NOT_FOUND:
287                 case HttpURLConnection.HTTP_GONE:
288                     throw new ResourceDoesNotExistException( formatResourceDoesNotExistMessage( buildUrl( resource ),
289                             statusCode, reasonPhrase, getProxyInfo() ) );
290 
291                 
292                 default:
293                     throw new TransferFailedException( formatTransferFailedMessage( buildUrl( resource ),
294                             statusCode, reasonPhrase, getProxyInfo() ) ) ;
295             }
296         }
297         catch ( IOException e )
298         {
299             fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
300             throw convertHttpUrlConnectionException( e, putConnection, buildUrl( resource ) );
301         }
302     }
303 
304     protected void openConnectionInternal()
305         throws ConnectionException, AuthenticationException
306     {
307         final ProxyInfo proxyInfo = getProxyInfo( "http", getRepository().getHost() );
308         if ( proxyInfo != null )
309         {
310             this.proxy = getProxy( proxyInfo );
311             this.proxyInfo = proxyInfo;
312         }
313         authenticator.setWagon( this );
314 
315         boolean usePreemptiveAuthentication =
316             Boolean.getBoolean( "maven.wagon.http.preemptiveAuthentication" ) || Boolean.parseBoolean(
317                 repository.getParameter( "preemptiveAuthentication" ) ) || this.preemptiveAuthentication;
318 
319         setPreemptiveAuthentication( usePreemptiveAuthentication );
320     }
321 
322     @SuppressWarnings( "deprecation" )
323     public PasswordAuthentication requestProxyAuthentication()
324     {
325         if ( proxyInfo != null && proxyInfo.getUserName() != null )
326         {
327             String password = "";
328             if ( proxyInfo.getPassword() != null )
329             {
330                 password = proxyInfo.getPassword();
331             }
332             return new PasswordAuthentication( proxyInfo.getUserName(), password.toCharArray() );
333         }
334         return null;
335     }
336 
337     public PasswordAuthentication requestServerAuthentication()
338     {
339         if ( authenticationInfo != null && authenticationInfo.getUserName() != null )
340         {
341             String password = "";
342             if ( authenticationInfo.getPassword() != null )
343             {
344                 password = authenticationInfo.getPassword();
345             }
346             return new PasswordAuthentication( authenticationInfo.getUserName(), password.toCharArray() );
347         }
348         return null;
349     }
350 
351     private Proxy getProxy( ProxyInfo proxyInfo )
352     {
353         return new Proxy( getProxyType( proxyInfo ), getSocketAddress( proxyInfo ) );
354     }
355 
356     private Type getProxyType( ProxyInfo proxyInfo )
357     {
358         if ( ProxyInfo.PROXY_SOCKS4.equals( proxyInfo.getType() ) || ProxyInfo.PROXY_SOCKS5.equals(
359             proxyInfo.getType() ) )
360         {
361             return Type.SOCKS;
362         }
363         else
364         {
365             return Type.HTTP;
366         }
367     }
368 
369     public SocketAddress getSocketAddress( ProxyInfo proxyInfo )
370     {
371         return InetSocketAddress.createUnresolved( proxyInfo.getHost(), proxyInfo.getPort() );
372     }
373 
374     public void closeConnection()
375         throws ConnectionException
376     {
377         
378         if ( putConnection != null )
379         {
380             putConnection.disconnect();
381         }
382         authenticator.resetWagon();
383     }
384 
385     public boolean resourceExists( String resourceName )
386         throws TransferFailedException, AuthorizationException
387     {
388         HttpURLConnection headConnection;
389 
390         try
391         {
392             Resource resource = new Resource( resourceName );
393             URL url = new URL( buildUrl( resource ) );
394             headConnection = (HttpURLConnection) url.openConnection( this.proxy );
395 
396             addHeaders( headConnection );
397 
398             headConnection.setRequestMethod( "HEAD" );
399 
400             int statusCode = headConnection.getResponseCode();
401             String reasonPhrase = headConnection.getResponseMessage();
402 
403             switch ( statusCode )
404             {
405                 case HttpURLConnection.HTTP_OK:
406                     return true;
407 
408                 case HttpURLConnection.HTTP_NOT_FOUND:
409                 case HttpURLConnection.HTTP_GONE:
410                     return false;
411 
412                 
413                 case HttpURLConnection.HTTP_FORBIDDEN:
414                 case HttpURLConnection.HTTP_UNAUTHORIZED:
415                 case HttpURLConnection.HTTP_PROXY_AUTH:
416                     throw new AuthorizationException( formatAuthorizationMessage( buildUrl( resource ),
417                             statusCode, reasonPhrase, getProxyInfo() ) );
418 
419                 default:
420                     throw new TransferFailedException( formatTransferFailedMessage( buildUrl( resource ),
421                             statusCode, reasonPhrase, getProxyInfo() ) );
422             }
423         }
424         catch ( IOException e )
425         {
426             throw new TransferFailedException( "Error transferring file: " + e.getMessage(), e );
427         }
428     }
429 
430     public boolean isUseCache()
431     {
432         return useCache;
433     }
434 
435     public void setUseCache( boolean useCache )
436     {
437         this.useCache = useCache;
438     }
439 
440     public Properties getHttpHeaders()
441     {
442         return httpHeaders;
443     }
444 
445     public void setHttpHeaders( Properties httpHeaders )
446     {
447         this.httpHeaders = httpHeaders;
448     }
449 
450     void setSystemProperty( String key, String value )
451     {
452         if ( value != null )
453         {
454             System.setProperty( key, value );
455         }
456         else
457         {
458             System.getProperties().remove( key );
459         }
460     }
461 
462     public void setPreemptiveAuthentication( boolean preemptiveAuthentication )
463     {
464         this.preemptiveAuthentication = preemptiveAuthentication;
465     }
466 
467     public LightweightHttpWagonAuthenticator getAuthenticator()
468     {
469         return authenticator;
470     }
471 
472     public void setAuthenticator( LightweightHttpWagonAuthenticator authenticator )
473     {
474         this.authenticator = authenticator;
475     }
476 
477     
478 
479 
480 
481 
482 
483 
484 
485 
486 
487 
488 
489 
490     private TransferFailedException convertHttpUrlConnectionException( IOException originalIOException,
491                                                                        HttpURLConnection urlConnection,
492                                                                        String url )
493     {
494         
495         
496         
497         
498         
499         try
500         {
501             
502             String errorResponseMessage = urlConnection.getResponseMessage(); 
503             int errorResponseCode = urlConnection.getResponseCode(); 
504             String message = formatTransferFailedMessage( url, errorResponseCode, errorResponseMessage,
505                     getProxyInfo() );
506             return new TransferFailedException( message, originalIOException );
507 
508         }
509         catch ( IOException errorStreamException )
510         {
511             
512         }
513 
514         
515         
516         
517         String ioMsg = originalIOException.getMessage();
518         if ( ioMsg != null )
519         {
520             Matcher matcher = IOEXCEPTION_MESSAGE_PATTERN.matcher( ioMsg );
521             if ( matcher.matches() )
522             {
523                 String codeStr = matcher.group( 1 );
524                 String urlStr = matcher.group( 2 );
525 
526                 int code = UNKNOWN_STATUS_CODE;
527                 try
528                 {
529                     code = parseInt( codeStr );
530                 }
531                 catch ( NumberFormatException nfe )
532                 {
533                     
534                 }
535 
536                 String message = formatTransferFailedMessage( urlStr, code, null, getProxyInfo() );
537                 return new TransferFailedException( message, originalIOException );
538             }
539         }
540 
541         String message = formatTransferFailedMessage( url, UNKNOWN_STATUS_CODE, null, getProxyInfo() );
542         return new TransferFailedException( message, originalIOException );
543     }
544 
545 }