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 if ( responseCode == HttpURLConnection.HTTP_FORBIDDEN
173 || responseCode == HttpURLConnection.HTTP_UNAUTHORIZED )
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 throw new AuthorizationException( formatAuthorizationMessage( buildUrl( resource ), statusCode,
282 reasonPhrase, getProxyInfo() ) );
283
284 case HttpURLConnection.HTTP_NOT_FOUND:
285 throw new ResourceDoesNotExistException( formatResourceDoesNotExistMessage( buildUrl( resource ),
286 statusCode, reasonPhrase, getProxyInfo() ) );
287
288
289 default:
290 throw new TransferFailedException( formatTransferFailedMessage( buildUrl( resource ),
291 statusCode, reasonPhrase, getProxyInfo() ) ) ;
292 }
293 }
294 catch ( IOException e )
295 {
296 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
297 throw convertHttpUrlConnectionException( e, putConnection, buildUrl( resource ) );
298 }
299 }
300
301 protected void openConnectionInternal()
302 throws ConnectionException, AuthenticationException
303 {
304 final ProxyInfo proxyInfo = getProxyInfo( "http", getRepository().getHost() );
305 if ( proxyInfo != null )
306 {
307 this.proxy = getProxy( proxyInfo );
308 this.proxyInfo = proxyInfo;
309 }
310 authenticator.setWagon( this );
311
312 boolean usePreemptiveAuthentication =
313 Boolean.getBoolean( "maven.wagon.http.preemptiveAuthentication" ) || Boolean.parseBoolean(
314 repository.getParameter( "preemptiveAuthentication" ) ) || this.preemptiveAuthentication;
315
316 setPreemptiveAuthentication( usePreemptiveAuthentication );
317 }
318
319 @SuppressWarnings( "deprecation" )
320 public PasswordAuthentication requestProxyAuthentication()
321 {
322 if ( proxyInfo != null && proxyInfo.getUserName() != null )
323 {
324 String password = "";
325 if ( proxyInfo.getPassword() != null )
326 {
327 password = proxyInfo.getPassword();
328 }
329 return new PasswordAuthentication( proxyInfo.getUserName(), password.toCharArray() );
330 }
331 return null;
332 }
333
334 public PasswordAuthentication requestServerAuthentication()
335 {
336 if ( authenticationInfo != null && authenticationInfo.getUserName() != null )
337 {
338 String password = "";
339 if ( authenticationInfo.getPassword() != null )
340 {
341 password = authenticationInfo.getPassword();
342 }
343 return new PasswordAuthentication( authenticationInfo.getUserName(), password.toCharArray() );
344 }
345 return null;
346 }
347
348 private Proxy getProxy( ProxyInfo proxyInfo )
349 {
350 return new Proxy( getProxyType( proxyInfo ), getSocketAddress( proxyInfo ) );
351 }
352
353 private Type getProxyType( ProxyInfo proxyInfo )
354 {
355 if ( ProxyInfo.PROXY_SOCKS4.equals( proxyInfo.getType() ) || ProxyInfo.PROXY_SOCKS5.equals(
356 proxyInfo.getType() ) )
357 {
358 return Type.SOCKS;
359 }
360 else
361 {
362 return Type.HTTP;
363 }
364 }
365
366 public SocketAddress getSocketAddress( ProxyInfo proxyInfo )
367 {
368 return InetSocketAddress.createUnresolved( proxyInfo.getHost(), proxyInfo.getPort() );
369 }
370
371 public void closeConnection()
372 throws ConnectionException
373 {
374
375 if ( putConnection != null )
376 {
377 putConnection.disconnect();
378 }
379 authenticator.resetWagon();
380 }
381
382 public List<String> getFileList( String destinationDirectory )
383 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
384 {
385 InputData inputData = new InputData();
386
387 if ( destinationDirectory.length() > 0 && !destinationDirectory.endsWith( "/" ) )
388 {
389 destinationDirectory += "/";
390 }
391
392 String url = buildUrl( new Resource( destinationDirectory ) );
393
394 Resource resource = new Resource( destinationDirectory );
395
396 inputData.setResource( resource );
397
398 fillInputData( inputData );
399
400 InputStream is = inputData.getInputStream();
401
402 try
403 {
404
405 if ( is == null )
406 {
407 throw new TransferFailedException(
408 url + " - Could not open input stream for resource: '" + resource + "'" );
409 }
410
411 final List<String> htmlFileList = HtmlFileListParser.parseFileList( url, is );
412 is.close();
413 is = null;
414 return htmlFileList;
415 }
416 catch ( final IOException e )
417 {
418 throw new TransferFailedException( "Failure transferring " + resource.getName(), e );
419 }
420 finally
421 {
422 IOUtils.closeQuietly( is );
423 }
424 }
425
426 public boolean resourceExists( String resourceName )
427 throws TransferFailedException, AuthorizationException
428 {
429 HttpURLConnection headConnection;
430
431 try
432 {
433 Resource resource = new Resource( resourceName );
434 URL url = new URL( buildUrl( resource ) );
435 headConnection = (HttpURLConnection) url.openConnection( this.proxy );
436
437 addHeaders( headConnection );
438
439 headConnection.setRequestMethod( "HEAD" );
440 headConnection.setDoOutput( true );
441
442 int statusCode = headConnection.getResponseCode();
443
444 switch ( statusCode )
445 {
446 case HttpURLConnection.HTTP_OK:
447 return true;
448
449 case HttpURLConnection.HTTP_FORBIDDEN:
450 throw new AuthorizationException( "Access denied to: " + url );
451
452 case HttpURLConnection.HTTP_NOT_FOUND:
453 return false;
454
455 case HttpURLConnection.HTTP_UNAUTHORIZED:
456 throw new AuthorizationException( "Access denied to: " + url );
457
458 default:
459 throw new TransferFailedException(
460 "Failed to look for file: " + buildUrl( resource ) + ". Return code is: " + statusCode );
461 }
462 }
463 catch ( IOException e )
464 {
465 throw new TransferFailedException( "Error transferring file: " + e.getMessage(), e );
466 }
467 }
468
469 public boolean isUseCache()
470 {
471 return useCache;
472 }
473
474 public void setUseCache( boolean useCache )
475 {
476 this.useCache = useCache;
477 }
478
479 public Properties getHttpHeaders()
480 {
481 return httpHeaders;
482 }
483
484 public void setHttpHeaders( Properties httpHeaders )
485 {
486 this.httpHeaders = httpHeaders;
487 }
488
489 void setSystemProperty( String key, String value )
490 {
491 if ( value != null )
492 {
493 System.setProperty( key, value );
494 }
495 else
496 {
497 System.getProperties().remove( key );
498 }
499 }
500
501 public void setPreemptiveAuthentication( boolean preemptiveAuthentication )
502 {
503 this.preemptiveAuthentication = preemptiveAuthentication;
504 }
505
506 public LightweightHttpWagonAuthenticator getAuthenticator()
507 {
508 return authenticator;
509 }
510
511 public void setAuthenticator( LightweightHttpWagonAuthenticator authenticator )
512 {
513 this.authenticator = authenticator;
514 }
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529 private TransferFailedException convertHttpUrlConnectionException( IOException originalIOException,
530 HttpURLConnection urlConnection,
531 String url )
532 {
533
534
535
536
537
538 try
539 {
540
541 String errorResponseMessage = urlConnection.getResponseMessage();
542 int errorResponseCode = urlConnection.getResponseCode();
543 String message = formatTransferFailedMessage( url, errorResponseCode, errorResponseMessage,
544 getProxyInfo() );
545 return new TransferFailedException( message, originalIOException );
546
547 }
548 catch ( IOException errorStreamException )
549 {
550
551 }
552
553
554
555
556 String ioMsg = originalIOException.getMessage();
557 if ( ioMsg != null )
558 {
559 Matcher matcher = IOEXCEPTION_MESSAGE_PATTERN.matcher( ioMsg );
560 if ( matcher.matches() )
561 {
562 String codeStr = matcher.group( 1 );
563 String urlStr = matcher.group( 2 );
564
565 int code = UNKNOWN_STATUS_CODE;
566 try
567 {
568 code = parseInt( codeStr );
569 }
570 catch ( NumberFormatException nfe )
571 {
572
573 }
574
575 String message = formatTransferFailedMessage( urlStr, code, null, getProxyInfo() );
576 return new TransferFailedException( message, originalIOException );
577 }
578 }
579
580 String message = formatTransferFailedMessage( url, UNKNOWN_STATUS_CODE, null, getProxyInfo() );
581 return new TransferFailedException( message, originalIOException );
582 }
583
584 }