1 package org.apache.maven.wagon.shared.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.httpclient.Credentials;
23 import org.apache.commons.httpclient.Header;
24 import org.apache.commons.httpclient.HostConfiguration;
25 import org.apache.commons.httpclient.HttpClient;
26 import org.apache.commons.httpclient.HttpConnectionManager;
27 import org.apache.commons.httpclient.HttpMethod;
28 import org.apache.commons.httpclient.HttpStatus;
29 import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
30 import org.apache.commons.httpclient.NTCredentials;
31 import org.apache.commons.httpclient.UsernamePasswordCredentials;
32 import org.apache.commons.httpclient.auth.AuthScope;
33 import org.apache.commons.httpclient.cookie.CookiePolicy;
34 import org.apache.commons.httpclient.methods.GetMethod;
35 import org.apache.commons.httpclient.methods.HeadMethod;
36 import org.apache.commons.httpclient.methods.PutMethod;
37 import org.apache.commons.httpclient.methods.RequestEntity;
38 import org.apache.commons.httpclient.params.HttpMethodParams;
39 import org.apache.commons.httpclient.util.DateParseException;
40 import org.apache.commons.httpclient.util.DateUtil;
41 import org.apache.maven.wagon.InputData;
42 import org.apache.maven.wagon.OutputData;
43 import org.apache.maven.wagon.PathUtils;
44 import org.apache.maven.wagon.ResourceDoesNotExistException;
45 import org.apache.maven.wagon.StreamWagon;
46 import org.apache.maven.wagon.TransferFailedException;
47 import org.apache.maven.wagon.Wagon;
48 import org.apache.maven.wagon.authorization.AuthorizationException;
49 import org.apache.maven.wagon.events.TransferEvent;
50 import org.apache.maven.wagon.proxy.ProxyInfo;
51 import org.apache.maven.wagon.repository.Repository;
52 import org.apache.maven.wagon.resource.Resource;
53 import org.codehaus.plexus.util.IOUtil;
54 import org.codehaus.plexus.util.StringUtils;
55
56 import java.io.File;
57 import java.io.FileInputStream;
58 import java.io.FileOutputStream;
59 import java.io.IOException;
60 import java.io.InputStream;
61 import java.io.OutputStream;
62 import java.net.URLEncoder;
63 import java.text.SimpleDateFormat;
64 import java.util.Date;
65 import java.util.Iterator;
66 import java.util.Locale;
67 import java.util.Properties;
68 import java.util.TimeZone;
69 import java.util.zip.GZIPInputStream;
70
71
72
73
74
75 public abstract class AbstractHttpClientWagon
76 extends StreamWagon
77 {
78 private final class RequestEntityImplementation
79 implements RequestEntity
80 {
81 private final Resource resource;
82
83 private final Wagon wagon;
84
85 private final File source;
86
87 private RequestEntityImplementation( final InputStream stream, final Resource resource, final Wagon wagon,
88 final File source )
89 throws TransferFailedException
90 {
91 if ( source != null )
92 {
93 this.source = source;
94 }
95 else
96 {
97 FileOutputStream fos = null;
98 try
99 {
100 this.source = File.createTempFile( "http-wagon.", ".tmp" );
101 this.source.deleteOnExit();
102
103 fos = new FileOutputStream( this.source );
104 IOUtil.copy( stream, fos );
105 }
106 catch ( IOException e )
107 {
108 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
109 throw new TransferFailedException( "Failed to buffer stream contents to temp file for upload.", e );
110 }
111 finally
112 {
113 IOUtil.close( fos );
114 }
115 }
116
117 this.resource = resource;
118 this.wagon = wagon;
119 }
120
121 public long getContentLength()
122 {
123 return resource.getContentLength();
124 }
125
126 public String getContentType()
127 {
128 return null;
129 }
130
131 public boolean isRepeatable()
132 {
133 return true;
134 }
135
136 public void writeRequest( OutputStream output )
137 throws IOException
138 {
139 byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
140
141 TransferEvent transferEvent =
142 new TransferEvent( wagon, resource, TransferEvent.TRANSFER_PROGRESS, TransferEvent.REQUEST_PUT );
143 transferEvent.setTimestamp( System.currentTimeMillis() );
144
145 FileInputStream fin = null;
146 try
147 {
148 fin = new FileInputStream( source );
149 int remaining = Integer.MAX_VALUE;
150 while ( remaining > 0 )
151 {
152 int n = fin.read( buffer, 0, Math.min( buffer.length, remaining ) );
153
154 if ( n == -1 )
155 {
156 break;
157 }
158
159 fireTransferProgress( transferEvent, buffer, n );
160
161 output.write( buffer, 0, n );
162
163 remaining -= n;
164 }
165 }
166 finally
167 {
168 IOUtil.close( fin );
169 }
170
171 output.flush();
172 }
173 }
174
175 protected static final int SC_NULL = -1;
176
177 protected static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone( "GMT" );
178
179 private HttpClient client;
180
181 protected HttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
182
183
184
185
186 private Properties httpHeaders;
187
188
189
190
191 private HttpConfiguration httpConfiguration;
192
193 private HttpMethod getMethod;
194
195 public void openConnectionInternal()
196 {
197 repository.setUrl( getURL( repository ) );
198 client = new HttpClient( connectionManager );
199
200
201 client.getParams().setCookiePolicy( CookiePolicy.BROWSER_COMPATIBILITY );
202
203 String username = null;
204 String password = null;
205
206 if ( authenticationInfo != null )
207 {
208 username = authenticationInfo.getUserName();
209
210 password = authenticationInfo.getPassword();
211
212 client.getParams().setAuthenticationPreemptive( true );
213 }
214
215 String host = getRepository().getHost();
216
217 if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) )
218 {
219 Credentials creds = new UsernamePasswordCredentials( username, password );
220
221 int port = getRepository().getPort() > -1 ? getRepository().getPort() : AuthScope.ANY_PORT;
222
223 AuthScope scope = new AuthScope( host, port );
224 client.getState().setCredentials( scope, creds );
225 }
226
227 HostConfiguration hc = new HostConfiguration();
228
229 ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
230 if ( proxyInfo != null )
231 {
232 String proxyUsername = proxyInfo.getUserName();
233 String proxyPassword = proxyInfo.getPassword();
234 String proxyHost = proxyInfo.getHost();
235 int proxyPort = proxyInfo.getPort();
236 String proxyNtlmHost = proxyInfo.getNtlmHost();
237 String proxyNtlmDomain = proxyInfo.getNtlmDomain();
238 if ( proxyHost != null )
239 {
240 hc.setProxy( proxyHost, proxyPort );
241
242 if ( proxyUsername != null && proxyPassword != null )
243 {
244 Credentials creds;
245 if ( proxyNtlmHost != null || proxyNtlmDomain != null )
246 {
247 creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain );
248 }
249 else
250 {
251 creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword );
252 }
253
254 int port = proxyInfo.getPort() > -1 ? proxyInfo.getPort() : AuthScope.ANY_PORT;
255
256 AuthScope scope = new AuthScope( proxyHost, port );
257 client.getState().setProxyCredentials( scope, creds );
258 }
259 }
260 }
261
262 hc.setHost( host );
263
264
265 client.setHostConfiguration( hc );
266 }
267
268 public void closeConnection()
269 {
270 if ( connectionManager instanceof MultiThreadedHttpConnectionManager )
271 {
272 ( (MultiThreadedHttpConnectionManager) connectionManager ).shutdown();
273 }
274 }
275
276 public void put( File source, String resourceName )
277 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
278 {
279 Resource resource = new Resource( resourceName );
280
281 firePutInitiated( resource, source );
282
283 resource.setContentLength( source.length() );
284
285 resource.setLastModified( source.lastModified() );
286
287 put( null, resource, source );
288 }
289
290 public void putFromStream( final InputStream stream, String destination, long contentLength, long lastModified )
291 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
292 {
293 Resource resource = new Resource( destination );
294
295 firePutInitiated( resource, null );
296
297 resource.setContentLength( contentLength );
298
299 resource.setLastModified( lastModified );
300
301 put( stream, resource, null );
302 }
303
304 private void put( final InputStream stream, Resource resource, File source )
305 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
306 {
307 StringBuilder url = new StringBuilder( getRepository().getUrl() );
308 String[] parts = StringUtils.split( resource.getName(), "/" );
309 for ( String part : parts )
310 {
311
312
313 if ( !url.toString().endsWith( "/" ) )
314 {
315 url.append( '/' );
316 }
317 url.append( URLEncoder.encode( part ) );
318 }
319
320
321 try
322 {
323 mkdirs( PathUtils.dirname( resource.getName() ) );
324 }
325 catch ( IOException e )
326 {
327 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
328 }
329
330 PutMethod putMethod = new PutMethod( url.toString() );
331
332 firePutStarted( resource, source );
333
334 try
335 {
336 putMethod.setRequestEntity( new RequestEntityImplementation( stream, resource, this, source ) );
337
338 int statusCode;
339 try
340 {
341 statusCode = execute( putMethod );
342 }
343 catch ( IOException e )
344 {
345 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
346
347 throw new TransferFailedException( e.getMessage(), e );
348 }
349
350 fireTransferDebug( url + " - Status code: " + statusCode );
351
352
353 switch ( statusCode )
354 {
355
356 case HttpStatus.SC_OK:
357 case HttpStatus.SC_CREATED:
358 case HttpStatus.SC_ACCEPTED:
359 case HttpStatus.SC_NO_CONTENT:
360 break;
361
362 case SC_NULL:
363 {
364 TransferFailedException e = new TransferFailedException( "Failed to transfer file: " + url );
365 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
366 throw e;
367 }
368
369 case HttpStatus.SC_FORBIDDEN:
370 fireSessionConnectionRefused();
371 throw new AuthorizationException( "Access denied to: " + url );
372
373 case HttpStatus.SC_NOT_FOUND:
374 throw new ResourceDoesNotExistException( "File: " + url + " does not exist" );
375
376
377 default:
378 {
379 TransferFailedException e = new TransferFailedException(
380 "Failed to transfer file: " + url + ". Return code is: " + statusCode );
381 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
382 throw e;
383 }
384 }
385
386 firePutCompleted( resource, source );
387 }
388 finally
389 {
390 putMethod.releaseConnection();
391 }
392 }
393
394 protected void mkdirs( String dirname )
395 throws IOException
396 {
397
398 }
399
400 public boolean resourceExists( String resourceName )
401 throws TransferFailedException, AuthorizationException
402 {
403 StringBuilder url = new StringBuilder( getRepository().getUrl() );
404 if ( !url.toString().endsWith( "/" ) )
405 {
406 url.append( '/' );
407 }
408 url.append( resourceName );
409 HeadMethod headMethod = new HeadMethod( url.toString() );
410 int statusCode;
411 try
412 {
413 statusCode = execute( headMethod );
414 }
415 catch ( IOException e )
416 {
417 throw new TransferFailedException( e.getMessage(), e );
418 }
419 try
420 {
421 switch ( statusCode )
422 {
423 case HttpStatus.SC_OK:
424 return true;
425
426 case HttpStatus.SC_NOT_MODIFIED:
427 return true;
428
429 case SC_NULL:
430 throw new TransferFailedException( "Failed to transfer file: " + url );
431
432 case HttpStatus.SC_FORBIDDEN:
433 throw new AuthorizationException( "Access denied to: " + url );
434
435 case HttpStatus.SC_UNAUTHORIZED:
436 throw new AuthorizationException( "Not authorized." );
437
438 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
439 throw new AuthorizationException( "Not authorized by proxy." );
440
441 case HttpStatus.SC_NOT_FOUND:
442 return false;
443
444
445 default:
446 throw new TransferFailedException(
447 "Failed to transfer file: " + url + ". Return code is: " + statusCode );
448 }
449 }
450 finally
451 {
452 headMethod.releaseConnection();
453 }
454 }
455
456 protected int execute( HttpMethod httpMethod )
457 throws IOException
458 {
459 int statusCode;
460
461 setParameters( httpMethod );
462 setHeaders( httpMethod );
463
464 statusCode = client.executeMethod( httpMethod );
465 return statusCode;
466 }
467
468 protected void setParameters( HttpMethod method )
469 {
470 HttpMethodConfiguration config =
471 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
472 if ( config != null )
473 {
474 HttpMethodParams params = config.asMethodParams( method.getParams() );
475 if ( params != null )
476 {
477 method.setParams( params );
478 }
479 }
480
481 if ( config == null || config.getConnectionTimeout() == HttpMethodConfiguration.DEFAULT_CONNECTION_TIMEOUT )
482 {
483 method.getParams().setSoTimeout( getTimeout() );
484 }
485 }
486
487 protected void setHeaders( HttpMethod method )
488 {
489 HttpMethodConfiguration config =
490 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
491 if ( config == null || config.isUseDefaultHeaders() )
492 {
493
494 method.addRequestHeader( "Cache-control", "no-cache" );
495 method.addRequestHeader( "Cache-store", "no-store" );
496 method.addRequestHeader( "Pragma", "no-cache" );
497 method.addRequestHeader( "Expires", "0" );
498 method.addRequestHeader( "Accept-Encoding", "gzip" );
499 }
500
501 if ( httpHeaders != null )
502 {
503 for ( Iterator i = httpHeaders.keySet().iterator(); i.hasNext(); )
504 {
505 String header = (String) i.next();
506 method.addRequestHeader( header, httpHeaders.getProperty( header ) );
507 }
508 }
509
510 Header[] headers = config == null ? null : config.asRequestHeaders();
511 if ( headers != null )
512 {
513 for ( int i = 0; i < headers.length; i++ )
514 {
515 method.addRequestHeader( headers[i] );
516 }
517 }
518 }
519
520
521
522
523
524
525
526
527 protected String getURL( Repository repository )
528 {
529 return repository.getUrl();
530 }
531
532 protected HttpClient getClient()
533 {
534 return client;
535 }
536
537 public void setConnectionManager( HttpConnectionManager connectionManager )
538 {
539 this.connectionManager = connectionManager;
540 }
541
542 public Properties getHttpHeaders()
543 {
544 return httpHeaders;
545 }
546
547 public void setHttpHeaders( Properties httpHeaders )
548 {
549 this.httpHeaders = httpHeaders;
550 }
551
552 public HttpConfiguration getHttpConfiguration()
553 {
554 return httpConfiguration;
555 }
556
557 public void setHttpConfiguration( HttpConfiguration httpConfiguration )
558 {
559 this.httpConfiguration = httpConfiguration;
560 }
561
562 public void fillInputData( InputData inputData )
563 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
564 {
565 Resource resource = inputData.getResource();
566
567 StringBuilder url = new StringBuilder( getRepository().getUrl() );
568 if ( !url.toString().endsWith( "/" ) )
569 {
570 url.append( '/' );
571 }
572 url.append( resource.getName() );
573
574 getMethod = new GetMethod( url.toString() );
575 long timestamp = resource.getLastModified();
576 if ( timestamp > 0 )
577 {
578 SimpleDateFormat fmt = new SimpleDateFormat( "EEE, dd-MMM-yy HH:mm:ss zzz", Locale.US );
579 fmt.setTimeZone( GMT_TIME_ZONE );
580 Header hdr = new Header( "If-Modified-Since", fmt.format( new Date( timestamp ) ) );
581 fireTransferDebug( "sending ==> " + hdr + "(" + timestamp + ")" );
582 getMethod.addRequestHeader( hdr );
583 }
584
585 int statusCode;
586 try
587 {
588 statusCode = execute( getMethod );
589 }
590 catch ( IOException e )
591 {
592 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
593
594 throw new TransferFailedException( e.getMessage(), e );
595 }
596
597 fireTransferDebug( url + " - Status code: " + statusCode );
598
599
600
601 switch ( statusCode )
602 {
603 case HttpStatus.SC_OK:
604 break;
605
606 case HttpStatus.SC_NOT_MODIFIED:
607
608 return;
609
610 case SC_NULL:
611 {
612 TransferFailedException e = new TransferFailedException( "Failed to transfer file: " + url );
613 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
614 throw e;
615 }
616
617 case HttpStatus.SC_FORBIDDEN:
618 fireSessionConnectionRefused();
619 throw new AuthorizationException( "Access denied to: " + url );
620
621 case HttpStatus.SC_UNAUTHORIZED:
622 fireSessionConnectionRefused();
623 throw new AuthorizationException( "Not authorized." );
624
625 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
626 fireSessionConnectionRefused();
627 throw new AuthorizationException( "Not authorized by proxy." );
628
629 case HttpStatus.SC_NOT_FOUND:
630 throw new ResourceDoesNotExistException( "File: " + url + " does not exist" );
631
632
633 default:
634 {
635 cleanupGetTransfer( resource );
636 TransferFailedException e = new TransferFailedException(
637 "Failed to transfer file: " + url + ". Return code is: " + statusCode );
638 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
639 throw e;
640 }
641 }
642
643 InputStream is = null;
644
645 Header contentLengthHeader = getMethod.getResponseHeader( "Content-Length" );
646
647 if ( contentLengthHeader != null )
648 {
649 try
650 {
651 long contentLength = Integer.valueOf( contentLengthHeader.getValue() ).intValue();
652
653 resource.setContentLength( contentLength );
654 }
655 catch ( NumberFormatException e )
656 {
657 fireTransferDebug(
658 "error parsing content length header '" + contentLengthHeader.getValue() + "' " + e );
659 }
660 }
661
662 Header lastModifiedHeader = getMethod.getResponseHeader( "Last-Modified" );
663
664 long lastModified = 0;
665
666 if ( lastModifiedHeader != null )
667 {
668 try
669 {
670 lastModified = DateUtil.parseDate( lastModifiedHeader.getValue() ).getTime();
671
672 resource.setLastModified( lastModified );
673 }
674 catch ( DateParseException e )
675 {
676 fireTransferDebug( "Unable to parse last modified header" );
677 }
678
679 fireTransferDebug( "last-modified = " + lastModifiedHeader.getValue() + " (" + lastModified + ")" );
680 }
681
682 Header contentEncoding = getMethod.getResponseHeader( "Content-Encoding" );
683 boolean isGZipped = contentEncoding != null && "gzip".equalsIgnoreCase( contentEncoding.getValue() );
684
685 try
686 {
687 is = getMethod.getResponseBodyAsStream();
688 if ( isGZipped )
689 {
690 is = new GZIPInputStream( is );
691 }
692 }
693 catch ( IOException e )
694 {
695 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
696
697 String msg =
698 "Error occurred while retrieving from remote repository:" + getRepository() + ": " + e.getMessage();
699
700 throw new TransferFailedException( msg, e );
701 }
702
703 inputData.setInputStream( is );
704 }
705
706 protected void cleanupGetTransfer( Resource resource )
707 {
708 if ( getMethod != null )
709 {
710 getMethod.releaseConnection();
711 }
712 }
713
714 @Override
715 public void putFromStream( InputStream stream, String destination )
716 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
717 {
718 putFromStream( stream, destination, -1, -1 );
719 }
720
721 @Override
722 protected void putFromStream( InputStream stream, Resource resource )
723 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
724 {
725 putFromStream( stream, resource.getName( ), -1, -1 );
726 }
727
728 @Override
729 public void fillOutputData( OutputData outputData )
730 throws TransferFailedException
731 {
732
733 throw new IllegalStateException( "this wagon http client must not use fillOutputData" );
734 }
735 }