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
213 String host = getRepository().getHost();
214
215 if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) )
216 {
217 Credentials creds = new UsernamePasswordCredentials( username, password );
218
219 int port = getRepository().getPort() > -1 ? getRepository().getPort() : AuthScope.ANY_PORT;
220
221 AuthScope scope = new AuthScope( host, port );
222 client.getState().setCredentials( scope, creds );
223 }
224
225 HostConfiguration hc = new HostConfiguration();
226
227 ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
228 if ( proxyInfo != null )
229 {
230 String proxyUsername = proxyInfo.getUserName();
231 String proxyPassword = proxyInfo.getPassword();
232 String proxyHost = proxyInfo.getHost();
233 int proxyPort = proxyInfo.getPort();
234 String proxyNtlmHost = proxyInfo.getNtlmHost();
235 String proxyNtlmDomain = proxyInfo.getNtlmDomain();
236 if ( proxyHost != null )
237 {
238 hc.setProxy( proxyHost, proxyPort );
239
240 if ( proxyUsername != null && proxyPassword != null )
241 {
242 Credentials creds;
243 if ( proxyNtlmHost != null || proxyNtlmDomain != null )
244 {
245 creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain );
246 }
247 else
248 {
249 creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword );
250 }
251
252 int port = proxyInfo.getPort() > -1 ? proxyInfo.getPort() : AuthScope.ANY_PORT;
253
254 AuthScope scope = new AuthScope( proxyHost, port );
255 client.getState().setProxyCredentials( scope, creds );
256 }
257 }
258 }
259
260 hc.setHost( host );
261
262
263 client.setHostConfiguration( hc );
264 }
265
266 public void closeConnection()
267 {
268 if ( connectionManager instanceof MultiThreadedHttpConnectionManager )
269 {
270 ( (MultiThreadedHttpConnectionManager) connectionManager ).shutdown();
271 }
272 }
273
274 public void put( File source, String resourceName )
275 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
276 {
277 Resource resource = new Resource( resourceName );
278
279 firePutInitiated( resource, source );
280
281 resource.setContentLength( source.length() );
282
283 resource.setLastModified( source.lastModified() );
284
285 put( null, resource, source );
286 }
287
288 public void putFromStream( final InputStream stream, String destination, long contentLength, long lastModified )
289 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
290 {
291 Resource resource = new Resource( destination );
292
293 firePutInitiated( resource, null );
294
295 resource.setContentLength( contentLength );
296
297 resource.setLastModified( lastModified );
298
299 put( stream, resource, null );
300 }
301
302 private void put( final InputStream stream, Resource resource, File source )
303 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
304 {
305 StringBuilder url = new StringBuilder( getRepository().getUrl() );
306 String[] parts = StringUtils.split( resource.getName(), "/" );
307 for ( String part : parts )
308 {
309
310
311 if ( !url.toString().endsWith( "/" ) )
312 {
313 url.append( '/' );
314 }
315 url.append( URLEncoder.encode( part ) );
316 }
317
318
319 try
320 {
321 mkdirs( PathUtils.dirname( resource.getName() ) );
322 }
323 catch ( IOException e )
324 {
325 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
326 }
327
328 PutMethod putMethod = new PutMethod( url.toString() );
329
330 firePutStarted( resource, source );
331
332 try
333 {
334 putMethod.setRequestEntity( new RequestEntityImplementation( stream, resource, this, source ) );
335
336 int statusCode;
337 try
338 {
339 statusCode = execute( putMethod );
340 }
341 catch ( IOException e )
342 {
343 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
344
345 throw new TransferFailedException( e.getMessage(), e );
346 }
347
348 fireTransferDebug( url + " - Status code: " + statusCode );
349
350
351 switch ( statusCode )
352 {
353
354 case HttpStatus.SC_OK:
355 case HttpStatus.SC_CREATED:
356 case HttpStatus.SC_ACCEPTED:
357 case HttpStatus.SC_NO_CONTENT:
358 break;
359
360 case SC_NULL:
361 {
362 TransferFailedException e = new TransferFailedException( "Failed to transfer file: " + url );
363 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
364 throw e;
365 }
366
367 case HttpStatus.SC_FORBIDDEN:
368 fireSessionConnectionRefused();
369 throw new AuthorizationException( "Access denied to: " + url );
370
371 case HttpStatus.SC_NOT_FOUND:
372 throw new ResourceDoesNotExistException( "File: " + url + " does not exist" );
373
374
375 default:
376 {
377 TransferFailedException e = new TransferFailedException(
378 "Failed to transfer file: " + url + ". Return code is: " + statusCode );
379 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
380 throw e;
381 }
382 }
383
384 firePutCompleted( resource, source );
385 }
386 finally
387 {
388 putMethod.releaseConnection();
389 }
390 }
391
392 protected void mkdirs( String dirname )
393 throws IOException
394 {
395
396 }
397
398 public boolean resourceExists( String resourceName )
399 throws TransferFailedException, AuthorizationException
400 {
401 StringBuilder url = new StringBuilder( getRepository().getUrl() );
402 if ( !url.toString().endsWith( "/" ) )
403 {
404 url.append( '/' );
405 }
406 url.append( resourceName );
407 HeadMethod headMethod = new HeadMethod( url.toString() );
408 int statusCode;
409 try
410 {
411 statusCode = execute( headMethod );
412 }
413 catch ( IOException e )
414 {
415 throw new TransferFailedException( e.getMessage(), e );
416 }
417 try
418 {
419 switch ( statusCode )
420 {
421 case HttpStatus.SC_OK:
422 return true;
423
424 case HttpStatus.SC_NOT_MODIFIED:
425 return true;
426
427 case SC_NULL:
428 throw new TransferFailedException( "Failed to transfer file: " + url );
429
430 case HttpStatus.SC_FORBIDDEN:
431 throw new AuthorizationException( "Access denied to: " + url );
432
433 case HttpStatus.SC_UNAUTHORIZED:
434 throw new AuthorizationException( "Not authorized." );
435
436 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
437 throw new AuthorizationException( "Not authorized by proxy." );
438
439 case HttpStatus.SC_NOT_FOUND:
440 return false;
441
442
443 default:
444 throw new TransferFailedException(
445 "Failed to transfer file: " + url + ". Return code is: " + statusCode );
446 }
447 }
448 finally
449 {
450 headMethod.releaseConnection();
451 }
452 }
453
454 protected int execute( HttpMethod httpMethod )
455 throws IOException
456 {
457 int statusCode;
458
459 setParameters( httpMethod );
460 setHeaders( httpMethod );
461
462 statusCode = client.executeMethod( httpMethod );
463 return statusCode;
464 }
465
466 protected void setParameters( HttpMethod method )
467 {
468 HttpMethodConfiguration config =
469 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
470 if ( config != null )
471 {
472 HttpMethodParams params = config.asMethodParams( method.getParams() );
473 if ( params != null )
474 {
475 method.setParams( params );
476 }
477 }
478
479 if ( config == null || config.getConnectionTimeout() == HttpMethodConfiguration.DEFAULT_CONNECTION_TIMEOUT )
480 {
481 method.getParams().setSoTimeout( getTimeout() );
482 }
483 }
484
485 protected void setHeaders( HttpMethod method )
486 {
487 HttpMethodConfiguration config =
488 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
489 if ( config == null || config.isUseDefaultHeaders() )
490 {
491
492 method.addRequestHeader( "Cache-control", "no-cache" );
493 method.addRequestHeader( "Cache-store", "no-store" );
494 method.addRequestHeader( "Pragma", "no-cache" );
495 method.addRequestHeader( "Expires", "0" );
496 method.addRequestHeader( "Accept-Encoding", "gzip" );
497 }
498
499 if ( httpHeaders != null )
500 {
501 for ( Iterator i = httpHeaders.keySet().iterator(); i.hasNext(); )
502 {
503 String header = (String) i.next();
504 method.addRequestHeader( header, httpHeaders.getProperty( header ) );
505 }
506 }
507
508 Header[] headers = config == null ? null : config.asRequestHeaders();
509 if ( headers != null )
510 {
511 for ( int i = 0; i < headers.length; i++ )
512 {
513 method.addRequestHeader( headers[i] );
514 }
515 }
516 }
517
518
519
520
521
522
523
524
525 protected String getURL( Repository repository )
526 {
527 return repository.getUrl();
528 }
529
530 protected HttpClient getClient()
531 {
532 return client;
533 }
534
535 public void setConnectionManager( HttpConnectionManager connectionManager )
536 {
537 this.connectionManager = connectionManager;
538 }
539
540 public Properties getHttpHeaders()
541 {
542 return httpHeaders;
543 }
544
545 public void setHttpHeaders( Properties httpHeaders )
546 {
547 this.httpHeaders = httpHeaders;
548 }
549
550 public HttpConfiguration getHttpConfiguration()
551 {
552 return httpConfiguration;
553 }
554
555 public void setHttpConfiguration( HttpConfiguration httpConfiguration )
556 {
557 this.httpConfiguration = httpConfiguration;
558 }
559
560 public void fillInputData( InputData inputData )
561 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
562 {
563 Resource resource = inputData.getResource();
564
565 StringBuilder url = new StringBuilder( getRepository().getUrl() );
566 if ( !url.toString().endsWith( "/" ) )
567 {
568 url.append( '/' );
569 }
570 url.append( resource.getName() );
571
572 getMethod = new GetMethod( url.toString() );
573 long timestamp = resource.getLastModified();
574 if ( timestamp > 0 )
575 {
576 SimpleDateFormat fmt = new SimpleDateFormat( "EEE, dd-MMM-yy HH:mm:ss zzz", Locale.US );
577 fmt.setTimeZone( GMT_TIME_ZONE );
578 Header hdr = new Header( "If-Modified-Since", fmt.format( new Date( timestamp ) ) );
579 fireTransferDebug( "sending ==> " + hdr + "(" + timestamp + ")" );
580 getMethod.addRequestHeader( hdr );
581 }
582
583 int statusCode;
584 try
585 {
586 statusCode = execute( getMethod );
587 }
588 catch ( IOException e )
589 {
590 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
591
592 throw new TransferFailedException( e.getMessage(), e );
593 }
594
595 fireTransferDebug( url + " - Status code: " + statusCode );
596
597
598
599 switch ( statusCode )
600 {
601 case HttpStatus.SC_OK:
602 break;
603
604 case HttpStatus.SC_NOT_MODIFIED:
605
606 return;
607
608 case SC_NULL:
609 {
610 TransferFailedException e = new TransferFailedException( "Failed to transfer file: " + url );
611 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
612 throw e;
613 }
614
615 case HttpStatus.SC_FORBIDDEN:
616 fireSessionConnectionRefused();
617 throw new AuthorizationException( "Access denied to: " + url );
618
619 case HttpStatus.SC_UNAUTHORIZED:
620 fireSessionConnectionRefused();
621 throw new AuthorizationException( "Not authorized." );
622
623 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
624 fireSessionConnectionRefused();
625 throw new AuthorizationException( "Not authorized by proxy." );
626
627 case HttpStatus.SC_NOT_FOUND:
628 throw new ResourceDoesNotExistException( "File: " + url + " does not exist" );
629
630
631 default:
632 {
633 cleanupGetTransfer( resource );
634 TransferFailedException e = new TransferFailedException(
635 "Failed to transfer file: " + url + ". Return code is: " + statusCode );
636 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
637 throw e;
638 }
639 }
640
641 InputStream is = null;
642
643 Header contentLengthHeader = getMethod.getResponseHeader( "Content-Length" );
644
645 if ( contentLengthHeader != null )
646 {
647 try
648 {
649 long contentLength = Integer.valueOf( contentLengthHeader.getValue() ).intValue();
650
651 resource.setContentLength( contentLength );
652 }
653 catch ( NumberFormatException e )
654 {
655 fireTransferDebug(
656 "error parsing content length header '" + contentLengthHeader.getValue() + "' " + e );
657 }
658 }
659
660 Header lastModifiedHeader = getMethod.getResponseHeader( "Last-Modified" );
661
662 long lastModified = 0;
663
664 if ( lastModifiedHeader != null )
665 {
666 try
667 {
668 lastModified = DateUtil.parseDate( lastModifiedHeader.getValue() ).getTime();
669
670 resource.setLastModified( lastModified );
671 }
672 catch ( DateParseException e )
673 {
674 fireTransferDebug( "Unable to parse last modified header" );
675 }
676
677 fireTransferDebug( "last-modified = " + lastModifiedHeader.getValue() + " (" + lastModified + ")" );
678 }
679
680 Header contentEncoding = getMethod.getResponseHeader( "Content-Encoding" );
681 boolean isGZipped = contentEncoding != null && "gzip".equalsIgnoreCase( contentEncoding.getValue() );
682
683 try
684 {
685 is = getMethod.getResponseBodyAsStream();
686 if ( isGZipped )
687 {
688 is = new GZIPInputStream( is );
689 }
690 }
691 catch ( IOException e )
692 {
693 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
694
695 String msg =
696 "Error occurred while retrieving from remote repository:" + getRepository() + ": " + e.getMessage();
697
698 throw new TransferFailedException( msg, e );
699 }
700
701 inputData.setInputStream( is );
702 }
703
704 protected void cleanupGetTransfer( Resource resource )
705 {
706 if ( getMethod != null )
707 {
708 getMethod.releaseConnection();
709 }
710 }
711
712 public void fillOutputData( OutputData outputData )
713 throws TransferFailedException
714 {
715 throw new IllegalStateException( "Should not be using the streaming wagon for HTTP PUT" );
716 }
717 }