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