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 }