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