1
package org.apache.maven.wagon.shared.http;
4
* Licensed to the Apache Software Foundation (ASF) under one
5
* or more contributor license agreements. See the NOTICE file
6
* distributed with this work for additional information
7
* regarding copyright ownership. The ASF licenses this file
8
* to you under the Apache License, Version 2.0 (the
9
* "License"); you may not use this file except in compliance
10
* with the License. You may obtain a copy of the License at
12
* http://www.apache.org/licenses/LICENSE-2.0
14
* Unless required by applicable law or agreed to in writing,
15
* software distributed under the License is distributed on an
16
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
* KIND, either express or implied. See the License for the
18
* specific language governing permissions and limitations
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;
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;
72
* @author <a href="michal.maczka@dimatics.com">Michal Maczka</a>
73
* @author <a href="mailto:james@atlassian.com">James William Dumay</a>
75
public abstract class AbstractHttpClientWagon
78
private final class RequestEntityImplementation
79
implements RequestEntity
81
private final Resource resource;
83
private final Wagon wagon;
85
private final File source;
87
private RequestEntityImplementation( final InputStream stream, final Resource resource, final Wagon wagon,
89
throws TransferFailedException
97
FileOutputStream fos = null;
100
this.source = File.createTempFile( "http-wagon.", ".tmp" );
101
this.source.deleteOnExit();
103
fos = new FileOutputStream( this.source );
104
IOUtil.copy( stream, fos );
106
catch ( IOException e )
108
fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
109
throw new TransferFailedException( "Failed to buffer stream contents to temp file for upload.", e );
117
this.resource = resource;
121
public long getContentLength()
123
return resource.getContentLength();
126
public String getContentType()
131
public boolean isRepeatable()
136
public void writeRequest( OutputStream output )
139
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
141
TransferEvent transferEvent =
142
new TransferEvent( wagon, resource, TransferEvent.TRANSFER_PROGRESS, TransferEvent.REQUEST_PUT );
143
transferEvent.setTimestamp( System.currentTimeMillis() );
145
FileInputStream fin = null;
148
fin = new FileInputStream( source );
149
int remaining = Integer.MAX_VALUE;
150
while ( remaining > 0 )
152
int n = fin.read( buffer, 0, Math.min( buffer.length, remaining ) );
159
fireTransferProgress( transferEvent, buffer, n );
161
output.write( buffer, 0, n );
175
protected static final int SC_NULL = -1;
177
protected static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone( "GMT" );
179
private HttpClient client;
181
protected HttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
184
* @deprecated Use httpConfiguration instead.
186
private Properties httpHeaders;
191
private HttpConfiguration httpConfiguration;
193
private HttpMethod getMethod;
195
public void openConnectionInternal()
197
repository.setUrl( getURL( repository ) );
198
client = new HttpClient( connectionManager );
200
// WAGON-273: default the cookie-policy to browser compatible
201
client.getParams().setCookiePolicy( CookiePolicy.BROWSER_COMPATIBILITY );
203
String username = null;
204
String password = null;
206
if ( authenticationInfo != null )
208
username = authenticationInfo.getUserName();
210
password = authenticationInfo.getPassword();
212
client.getParams().setAuthenticationPreemptive( true );
215
String host = getRepository().getHost();
217
if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) )
219
Credentials creds = new UsernamePasswordCredentials( username, password );
221
int port = getRepository().getPort() > -1 ? getRepository().getPort() : AuthScope.ANY_PORT;
223
AuthScope scope = new AuthScope( host, port );
224
client.getState().setCredentials( scope, creds );
227
HostConfiguration hc = new HostConfiguration();
229
ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
230
if ( proxyInfo != null )
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 )
240
hc.setProxy( proxyHost, proxyPort );
242
if ( proxyUsername != null && proxyPassword != null )
245
if ( proxyNtlmHost != null || proxyNtlmDomain != null )
247
creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain );
251
creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword );
254
int port = proxyInfo.getPort() > -1 ? proxyInfo.getPort() : AuthScope.ANY_PORT;
256
AuthScope scope = new AuthScope( proxyHost, port );
257
client.getState().setProxyCredentials( scope, creds );
264
//start a session with the webserver
265
client.setHostConfiguration( hc );
268
public void closeConnection()
270
if ( connectionManager instanceof MultiThreadedHttpConnectionManager )
272
( (MultiThreadedHttpConnectionManager) connectionManager ).shutdown();
276
public void put( File source, String resourceName )
277
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
279
Resource resource = new Resource( resourceName );
281
firePutInitiated( resource, source );
283
resource.setContentLength( source.length() );
285
resource.setLastModified( source.lastModified() );
287
put( null, resource, source );
290
public void putFromStream( final InputStream stream, String destination, long contentLength, long lastModified )
291
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
293
Resource resource = new Resource( destination );
295
firePutInitiated( resource, null );
297
resource.setContentLength( contentLength );
299
resource.setLastModified( lastModified );
301
put( stream, resource, null );
304
private void put( final InputStream stream, Resource resource, File source )
305
throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
307
StringBuilder url = new StringBuilder( getRepository().getUrl() );
308
String[] parts = StringUtils.split( resource.getName(), "/" );
309
for ( String part : parts )// int i = 0; i < parts.length; i++ )
311
// TODO: Fix encoding...
312
// url += "/" + URLEncoder.encode( parts[i], System.getProperty("file.encoding") );
313
if ( !url.toString().endsWith( "/" ) )
317
url.append( URLEncoder.encode( part ) );
320
//Parent directories need to be created before posting
323
mkdirs( PathUtils.dirname( resource.getName() ) );
325
catch ( IOException e )
327
fireTransferError( resource, e, TransferEvent.REQUEST_GET );
330
PutMethod putMethod = new PutMethod( url.toString() );
332
firePutStarted( resource, source );
336
putMethod.setRequestEntity( new RequestEntityImplementation( stream, resource, this, source ) );
341
statusCode = execute( putMethod );
343
catch ( IOException e )
345
fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
347
throw new TransferFailedException( e.getMessage(), e );
350
fireTransferDebug( url + " - Status code: " + statusCode );
352
// Check that we didn't run out of retries.
353
switch ( statusCode )
356
case HttpStatus.SC_OK: // 200
357
case HttpStatus.SC_CREATED: // 201
358
case HttpStatus.SC_ACCEPTED: // 202
359
case HttpStatus.SC_NO_CONTENT: // 204
364
TransferFailedException e = new TransferFailedException( "Failed to transfer file: " + url );
365
fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
369
case HttpStatus.SC_FORBIDDEN:
370
fireSessionConnectionRefused();
371
throw new AuthorizationException( "Access denied to: " + url );
373
case HttpStatus.SC_NOT_FOUND:
374
throw new ResourceDoesNotExistException( "File: " + url + " does not exist" );
376
//add more entries here
379
TransferFailedException e = new TransferFailedException(
380
"Failed to transfer file: " + url + ". Return code is: " + statusCode );
381
fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
386
firePutCompleted( resource, source );
390
putMethod.releaseConnection();
394
protected void mkdirs( String dirname )
397
// do nothing as default.
400
public boolean resourceExists( String resourceName )
401
throws TransferFailedException, AuthorizationException
403
StringBuilder url = new StringBuilder( getRepository().getUrl() );
404
if ( !url.toString().endsWith( "/" ) )
408
url.append( resourceName );
409
HeadMethod headMethod = new HeadMethod( url.toString() );
413
statusCode = execute( headMethod );
415
catch ( IOException e )
417
throw new TransferFailedException( e.getMessage(), e );
421
switch ( statusCode )
423
case HttpStatus.SC_OK:
426
case HttpStatus.SC_NOT_MODIFIED:
430
throw new TransferFailedException( "Failed to transfer file: " + url );
432
case HttpStatus.SC_FORBIDDEN:
433
throw new AuthorizationException( "Access denied to: " + url );
435
case HttpStatus.SC_UNAUTHORIZED:
436
throw new AuthorizationException( "Not authorized." );
438
case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
439
throw new AuthorizationException( "Not authorized by proxy." );
441
case HttpStatus.SC_NOT_FOUND:
444
//add more entries here
446
throw new TransferFailedException(
447
"Failed to transfer file: " + url + ". Return code is: " + statusCode );
452
headMethod.releaseConnection();
456
protected int execute( HttpMethod httpMethod )
461
setParameters( httpMethod );
462
setHeaders( httpMethod );
464
statusCode = client.executeMethod( httpMethod );
468
protected void setParameters( HttpMethod method )
470
HttpMethodConfiguration config =
471
httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
472
if ( config != null )
474
HttpMethodParams params = config.asMethodParams( method.getParams() );
475
if ( params != null )
477
method.setParams( params );
481
if ( config == null || config.getConnectionTimeout() == HttpMethodConfiguration.DEFAULT_CONNECTION_TIMEOUT )
483
method.getParams().setSoTimeout( getTimeout() );
487
protected void setHeaders( HttpMethod method )
489
HttpMethodConfiguration config =
490
httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
491
if ( config == null || config.isUseDefaultHeaders() )
493
// TODO: merge with the other headers and have some better defaults, unify with lightweight headers
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" );
501
if ( httpHeaders != null )
503
for ( Iterator i = httpHeaders.keySet().iterator(); i.hasNext(); )
505
String header = (String) i.next();
506
method.addRequestHeader( header, httpHeaders.getProperty( header ) );
510
Header[] headers = config == null ? null : config.asRequestHeaders();
511
if ( headers != null )
513
for ( int i = 0; i < headers.length; i++ )
515
method.addRequestHeader( headers[i] );
522
* Implementors can override this to remove unwanted parts of the url such as role-hints
527
protected String getURL( Repository repository )
529
return repository.getUrl();
532
protected HttpClient getClient()
537
public void setConnectionManager( HttpConnectionManager connectionManager )
539
this.connectionManager = connectionManager;
542
public Properties getHttpHeaders()
547
public void setHttpHeaders( Properties httpHeaders )
549
this.httpHeaders = httpHeaders;
552
public HttpConfiguration getHttpConfiguration()
554
return httpConfiguration;
557
public void setHttpConfiguration( HttpConfiguration httpConfiguration )
559
this.httpConfiguration = httpConfiguration;
562
public void fillInputData( InputData inputData )
563
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
565
Resource resource = inputData.getResource();
567
StringBuilder url = new StringBuilder( getRepository().getUrl() );
568
if ( !url.toString().endsWith( "/" ) )
572
url.append( resource.getName() );
574
getMethod = new GetMethod( url.toString() );
575
long timestamp = resource.getLastModified();
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 );
588
statusCode = execute( getMethod );
590
catch ( IOException e )
592
fireTransferError( resource, e, TransferEvent.REQUEST_GET );
594
throw new TransferFailedException( e.getMessage(), e );
597
fireTransferDebug( url + " - Status code: " + statusCode );
599
// TODO [BP]: according to httpclient docs, really should swallow the output on error. verify if that is
601
switch ( statusCode )
603
case HttpStatus.SC_OK:
606
case HttpStatus.SC_NOT_MODIFIED:
607
// return, leaving last modified set to original value so getIfNewer should return unmodified
612
TransferFailedException e = new TransferFailedException( "Failed to transfer file: " + url );
613
fireTransferError( resource, e, TransferEvent.REQUEST_GET );
617
case HttpStatus.SC_FORBIDDEN:
618
fireSessionConnectionRefused();
619
throw new AuthorizationException( "Access denied to: " + url );
621
case HttpStatus.SC_UNAUTHORIZED:
622
fireSessionConnectionRefused();
623
throw new AuthorizationException( "Not authorized." );
625
case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
626
fireSessionConnectionRefused();
627
throw new AuthorizationException( "Not authorized by proxy." );
629
case HttpStatus.SC_NOT_FOUND:
630
throw new ResourceDoesNotExistException( "File: " + url + " does not exist" );
632
// add more entries here
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 );
643
InputStream is = null;
645
Header contentLengthHeader = getMethod.getResponseHeader( "Content-Length" );
647
if ( contentLengthHeader != null )
651
long contentLength = Integer.valueOf( contentLengthHeader.getValue() ).intValue();
653
resource.setContentLength( contentLength );
655
catch ( NumberFormatException e )
658
"error parsing content length header '" + contentLengthHeader.getValue() + "' " + e );
662
Header lastModifiedHeader = getMethod.getResponseHeader( "Last-Modified" );
664
long lastModified = 0;
666
if ( lastModifiedHeader != null )
670
lastModified = DateUtil.parseDate( lastModifiedHeader.getValue() ).getTime();
672
resource.setLastModified( lastModified );
674
catch ( DateParseException e )
676
fireTransferDebug( "Unable to parse last modified header" );
679
fireTransferDebug( "last-modified = " + lastModifiedHeader.getValue() + " (" + lastModified + ")" );
682
Header contentEncoding = getMethod.getResponseHeader( "Content-Encoding" );
683
boolean isGZipped = contentEncoding != null && "gzip".equalsIgnoreCase( contentEncoding.getValue() );
687
is = getMethod.getResponseBodyAsStream();
690
is = new GZIPInputStream( is );
693
catch ( IOException e )
695
fireTransferError( resource, e, TransferEvent.REQUEST_GET );
698
"Error occurred while retrieving from remote repository:" + getRepository() + ": " + e.getMessage();
700
throw new TransferFailedException( msg, e );
703
inputData.setInputStream( is );
706
protected void cleanupGetTransfer( Resource resource )
708
if ( getMethod != null )
710
getMethod.releaseConnection();
715
public void putFromStream( InputStream stream, String destination )
716
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
718
putFromStream( stream, destination, -1, -1 );
722
protected void putFromStream( InputStream stream, Resource resource )
723
throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
725
putFromStream( stream, resource.getName( ), -1, -1 );
729
public void fillOutputData( OutputData outputData )
730
throws TransferFailedException
732
// no needed in this implementation but throw an Exception if used
733
throw new IllegalStateException( "this wagon http client must not use fillOutputData" );