001package org.apache.maven.wagon.shared.http4;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.ByteArrayInputStream;
023import java.io.File;
024import java.io.FileInputStream;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.OutputStream;
028import java.net.URLEncoder;
029import java.nio.ByteBuffer;
030import java.security.cert.X509Certificate;
031import java.text.SimpleDateFormat;
032import java.util.Date;
033import java.util.Locale;
034import java.util.Map;
035import java.util.Properties;
036import java.util.TimeZone;
037import java.util.zip.GZIPInputStream;
038
039import javax.net.ssl.HttpsURLConnection;
040import javax.net.ssl.SSLException;
041import javax.net.ssl.SSLSession;
042import javax.net.ssl.SSLSocket;
043
044import org.apache.http.Header;
045import org.apache.http.HttpEntity;
046import org.apache.http.HttpException;
047import org.apache.http.HttpHost;
048import org.apache.http.HttpResponse;
049import org.apache.http.HttpStatus;
050import org.apache.http.auth.AuthScope;
051import org.apache.http.auth.Credentials;
052import org.apache.http.auth.NTCredentials;
053import org.apache.http.auth.UsernamePasswordCredentials;
054import org.apache.http.client.AuthCache;
055import org.apache.http.client.methods.HttpGet;
056import org.apache.http.client.methods.HttpHead;
057import org.apache.http.client.methods.HttpPut;
058import org.apache.http.client.methods.HttpUriRequest;
059import org.apache.http.client.params.ClientPNames;
060import org.apache.http.client.params.CookiePolicy;
061import org.apache.http.client.protocol.ClientContext;
062import org.apache.http.conn.ClientConnectionManager;
063import org.apache.http.conn.params.ConnRoutePNames;
064import org.apache.http.conn.scheme.PlainSocketFactory;
065import org.apache.http.conn.scheme.Scheme;
066import org.apache.http.conn.scheme.SchemeRegistry;
067import org.apache.http.conn.ssl.SSLSocketFactory;
068import org.apache.http.conn.ssl.X509HostnameVerifier;
069import org.apache.http.entity.AbstractHttpEntity;
070import org.apache.http.impl.auth.BasicScheme;
071import org.apache.http.impl.client.BasicAuthCache;
072import org.apache.http.impl.client.DefaultHttpClient;
073import org.apache.http.impl.conn.BasicClientConnectionManager;
074import org.apache.http.impl.conn.PoolingClientConnectionManager;
075import org.apache.http.impl.cookie.DateParseException;
076import org.apache.http.impl.cookie.DateUtils;
077import org.apache.http.message.BasicHeader;
078import org.apache.http.params.CoreConnectionPNames;
079import org.apache.http.params.CoreProtocolPNames;
080import org.apache.http.params.HttpParams;
081import org.apache.http.protocol.BasicHttpContext;
082import org.apache.maven.wagon.InputData;
083import org.apache.maven.wagon.OutputData;
084import org.apache.maven.wagon.PathUtils;
085import org.apache.maven.wagon.ResourceDoesNotExistException;
086import org.apache.maven.wagon.StreamWagon;
087import org.apache.maven.wagon.TransferFailedException;
088import org.apache.maven.wagon.Wagon;
089import org.apache.maven.wagon.authorization.AuthorizationException;
090import org.apache.maven.wagon.events.TransferEvent;
091import org.apache.maven.wagon.proxy.ProxyInfo;
092import org.apache.maven.wagon.repository.Repository;
093import org.apache.maven.wagon.resource.Resource;
094import org.codehaus.plexus.util.IOUtil;
095import org.codehaus.plexus.util.StringUtils;
096
097/**
098 * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a>
099 * @author <a href="mailto:james@atlassian.com">James William Dumay</a>
100 */
101public abstract class AbstractHttpClientWagon
102    extends StreamWagon
103{
104
105    private BasicHttpContext localContext;
106
107    private final class RequestEntityImplementation
108        extends AbstractHttpEntity
109    {
110
111        private final static int BUFFER_SIZE = 2048;
112
113        private final Resource resource;
114
115        private final Wagon wagon;
116
117        private ByteBuffer byteBuffer;
118
119        private File source;
120
121        private long length = -1;
122
123        private RequestEntityImplementation( final InputStream stream, final Resource resource, final Wagon wagon,
124                                             final File source )
125            throws TransferFailedException
126        {
127            if ( source != null )
128            {
129                this.source = source;
130            }
131            else
132            {
133                try
134                {
135                    byte[] bytes = IOUtil.toByteArray( stream );
136                    byteBuffer = ByteBuffer.allocate( bytes.length );
137                    byteBuffer.put( bytes );
138                }
139                catch ( IOException e )
140                {
141                    throw new TransferFailedException( e.getMessage(), e );
142                }
143            }
144            this.resource = resource;
145            this.length = resource == null ? -1 : resource.getContentLength();
146
147            this.wagon = wagon;
148        }
149
150        public long getContentLength()
151        {
152            return length;
153        }
154
155        public InputStream getContent()
156            throws IOException, IllegalStateException
157        {
158            if ( this.source != null )
159            {
160                return new FileInputStream( this.source );
161            }
162            return new ByteArrayInputStream( this.byteBuffer.array() );
163        }
164
165        public boolean isRepeatable()
166        {
167            return true;
168        }
169
170        public void writeTo( final OutputStream outstream )
171            throws IOException
172        {
173            if ( outstream == null )
174            {
175                throw new IllegalArgumentException( "Output stream may not be null" );
176            }
177            TransferEvent transferEvent =
178                new TransferEvent( wagon, resource, TransferEvent.TRANSFER_PROGRESS, TransferEvent.REQUEST_PUT );
179            transferEvent.setTimestamp( System.currentTimeMillis() );
180            InputStream instream = ( this.source != null )
181                ? new FileInputStream( this.source )
182                : new ByteArrayInputStream( this.byteBuffer.array() );
183            try
184            {
185                byte[] buffer = new byte[BUFFER_SIZE];
186                int l;
187                if ( this.length < 0 )
188                {
189                    // until EOF
190                    while ( ( l = instream.read( buffer ) ) != -1 )
191                    {
192                        fireTransferProgress( transferEvent, buffer, -1 );
193                        outstream.write( buffer, 0, l );
194                    }
195                }
196                else
197                {
198                    // no need to consume more than length
199                    long remaining = this.length;
200                    while ( remaining > 0 )
201                    {
202                        l = instream.read( buffer, 0, (int) Math.min( BUFFER_SIZE, remaining ) );
203                        if ( l == -1 )
204                        {
205                            break;
206                        }
207                        fireTransferProgress( transferEvent, buffer, (int) Math.min( BUFFER_SIZE, remaining ) );
208                        outstream.write( buffer, 0, l );
209                        remaining -= l;
210                    }
211                }
212            }
213            finally
214            {
215                instream.close();
216            }
217        }
218
219        public boolean isStreaming()
220        {
221            return true;
222        }
223    }
224
225    protected static final int SC_NULL = -1;
226
227    protected static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone( "GMT" );
228
229    private DefaultHttpClient client;
230
231    /**
232     * @since 2.0
233     */
234    protected static ClientConnectionManager connectionManagerPooled;
235
236    /**
237     * @since 2.0
238     */
239    protected ClientConnectionManager clientConnectionManager =
240        new BasicClientConnectionManager( createSchemeRegistry() );
241
242    /**
243     * use http(s) connection pool mechanism.
244     * <b>enabled by default</b>
245     *
246     * @since 2.0
247     */
248    protected static boolean useClientManagerPooled =
249        Boolean.valueOf( System.getProperty( "maven.wagon.http.pool", "true" ) );
250
251    /**
252     * skip failure on certificate validity checks.
253     * <b>disabled by default</b>
254     *
255     * @since 2.0
256     */
257    protected static boolean sslInsecure = Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.insecure", "false" ) );
258
259    /**
260     * if using sslInsecure, certificate date issues will be ignored
261     * <b>disabled by default</b>
262     *
263     * @since 2.0
264     */
265    protected static boolean IGNORE_SSL_VALIDITY_DATES =
266        Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.ignore.validity.dates", "false" ) );
267
268    /**
269     * If enabled, ssl hostname verifier does not check hostname. Disable this will use a browser compat hostname verifier
270     * <b>disabled by default</b>
271     *
272     * @since 2.0
273     * @see BrowserCompatHostnameVerifier
274     */
275    protected static boolean sslAllowAll =
276        Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.allowall", "false" ) );
277
278    private static SchemeRegistry createSchemeRegistry()
279    {
280        SchemeRegistry schemeRegistry = new SchemeRegistry();
281        schemeRegistry.register( new Scheme( "http", 80, PlainSocketFactory.getSocketFactory() ) );
282        SSLSocketFactory sslSocketFactory;
283        if ( sslInsecure )
284        {
285            try
286            {
287                sslSocketFactory = new SSLSocketFactory(
288                    RelaxedX509TrustManager.createRelaxedSSLContext(),
289                    sslAllowAll ? new RelaxedHostNameVerifier() : SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER );
290            }
291            catch ( IOException e )
292            {
293                throw new RuntimeException( "failed to init SSLSocket Factory " + e.getMessage(), e );
294            }
295        }
296        else
297        {
298            sslSocketFactory = new SSLSocketFactory(
299                HttpsURLConnection.getDefaultSSLSocketFactory(),
300                SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER );
301        }
302
303        Scheme httpsScheme = new Scheme( "https", 443, new ConfigurableSSLSocketFactoryDecorator( sslSocketFactory ) );
304        schemeRegistry.register( httpsScheme );
305
306        return schemeRegistry;
307    }
308
309    static
310    {
311        if ( !useClientManagerPooled )
312        {
313            System.out.println( "http connection pool disabled in wagon http" );
314        }
315        else
316        {
317            PoolingClientConnectionManager poolingClientConnectionManager =
318                new PoolingClientConnectionManager( createSchemeRegistry() );
319            int maxPerRoute =
320                Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxPerRoute", "20" ) );
321            poolingClientConnectionManager.setDefaultMaxPerRoute( maxPerRoute );
322            int maxTotal = Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxTotal", "40" ) );
323            poolingClientConnectionManager.setDefaultMaxPerRoute( maxPerRoute );
324            poolingClientConnectionManager.setMaxTotal( maxTotal );
325
326            connectionManagerPooled = poolingClientConnectionManager;
327        }
328    }
329
330    /**
331     * disable all host name verification
332     *
333     * @since 2.0
334     */
335    private static class RelaxedHostNameVerifier
336        implements X509HostnameVerifier
337    {
338        public void verify( String s, SSLSocket sslSocket )
339            throws IOException
340        {
341            //no op
342        }
343
344        public void verify( String s, X509Certificate x509Certificate )
345            throws SSLException
346        {
347            //no op
348        }
349
350        public void verify( String s, String[] strings, String[] strings1 )
351            throws SSLException
352        {
353            //no op
354        }
355
356        public boolean verify( String s, SSLSession sslSession )
357        {
358            return true;
359        }
360    }
361
362    public ClientConnectionManager getConnectionManager()
363    {
364        if ( !useClientManagerPooled )
365        {
366            return clientConnectionManager;
367        }
368        return connectionManagerPooled;
369    }
370
371    public static void setConnectionManagerPooled( ClientConnectionManager clientConnectionManager )
372    {
373        connectionManagerPooled = clientConnectionManager;
374    }
375
376    public static void setUseClientManagerPooled( boolean pooledClientManager )
377    {
378        useClientManagerPooled = pooledClientManager;
379    }
380
381    /**
382     * @plexus.configuration
383     * @deprecated Use httpConfiguration instead.
384     */
385    private Properties httpHeaders;
386
387    /**
388     * @since 1.0-beta-6
389     */
390    private HttpConfiguration httpConfiguration;
391
392    private HttpGet getMethod;
393
394    public void openConnectionInternal()
395    {
396        repository.setUrl( getURL( repository ) );
397        client = new DefaultHttpClient( getConnectionManager() );
398
399        // WAGON-273: default the cookie-policy to browser compatible
400        client.getParams().setParameter( ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY );
401
402        if ( authenticationInfo != null )
403        {
404
405            String username = authenticationInfo.getUserName();
406            String password = authenticationInfo.getPassword();
407
408            if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) )
409            {
410                Credentials creds = new UsernamePasswordCredentials( username, password );
411
412                String host = getRepository().getHost();
413                int port = getRepository().getPort() > -1 ? getRepository().getPort() : AuthScope.ANY_PORT;
414
415                client.getCredentialsProvider().setCredentials( new AuthScope( host, port ), creds );
416                // preemptive off by default
417                /*
418                AuthCache authCache = new BasicAuthCache();
419                BasicScheme basicAuth = new BasicScheme();
420                HttpHost targetHost =
421                    new HttpHost( repository.getHost(), repository.getPort(), repository.getProtocol() );
422                authCache.put( targetHost, basicAuth );
423
424                localContext = new BasicHttpContext();
425                localContext.setAttribute( ClientContext.AUTH_CACHE, authCache );
426                */
427            }
428        }
429
430        ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
431        if ( proxyInfo != null )
432        {
433            String proxyUsername = proxyInfo.getUserName();
434            String proxyPassword = proxyInfo.getPassword();
435            String proxyHost = proxyInfo.getHost();
436            int proxyPort = proxyInfo.getPort();
437            String proxyNtlmHost = proxyInfo.getNtlmHost();
438            String proxyNtlmDomain = proxyInfo.getNtlmDomain();
439            if ( proxyHost != null )
440            {
441                HttpHost proxy = new HttpHost( proxyHost, proxyPort );
442
443                if ( proxyUsername != null && proxyPassword != null )
444                {
445                    Credentials creds;
446                    if ( proxyNtlmHost != null || proxyNtlmDomain != null )
447                    {
448                        creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain );
449                    }
450                    else
451                    {
452                        creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword );
453                    }
454
455                    int port = proxyInfo.getPort() > -1 ? proxyInfo.getPort() : AuthScope.ANY_PORT;
456
457                    AuthScope authScope = new AuthScope( proxyHost, port );
458                    client.getCredentialsProvider().setCredentials( authScope, creds );
459                }
460
461                client.getParams().setParameter( ConnRoutePNames.DEFAULT_PROXY, proxy );
462            }
463        }
464    }
465
466    public void closeConnection()
467    {
468        if ( !useClientManagerPooled )
469        {
470            getConnectionManager().shutdown();
471        }
472    }
473
474    public void put( File source, String resourceName )
475        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
476    {
477        Resource resource = new Resource( resourceName );
478
479        firePutInitiated( resource, source );
480
481        resource.setContentLength( source.length() );
482
483        resource.setLastModified( source.lastModified() );
484
485        put( null, resource, source );
486    }
487
488    public void putFromStream( final InputStream stream, String destination, long contentLength, long lastModified )
489        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
490    {
491        Resource resource = new Resource( destination );
492
493        firePutInitiated( resource, null );
494
495        resource.setContentLength( contentLength );
496
497        resource.setLastModified( lastModified );
498
499        put( stream, resource, null );
500    }
501
502    private void put( final InputStream stream, Resource resource, File source )
503        throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
504    {
505        put( resource, source, new RequestEntityImplementation( stream, resource, this, source ) );
506    }
507
508    private void put( Resource resource, File source, HttpEntity httpEntity )
509        throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
510    {
511
512        StringBuilder url = new StringBuilder( getURL( getRepository() ) );
513        String[] parts = StringUtils.split( resource.getName(), "/" );
514        for ( String part : parts )
515        {
516            // TODO: Fix encoding...
517            // url += "/" + URLEncoder.encode( parts[i], System.getProperty("file.encoding") );
518            if ( !url.toString().endsWith( "/" ) )
519            {
520                url.append( '/' );
521            }
522            url.append( URLEncoder.encode( part ) );
523        }
524        put( resource, source, httpEntity, url.toString() );
525    }
526
527    private void put( Resource resource, File source, HttpEntity httpEntity, String url )
528        throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
529    {
530
531        //Parent directories need to be created before posting
532        try
533        {
534            mkdirs( PathUtils.dirname( resource.getName() ) );
535        }
536        catch ( HttpException he )
537        {
538            fireTransferError( resource, he, TransferEvent.REQUEST_GET );
539        }
540        catch ( IOException e )
541        {
542            fireTransferError( resource, e, TransferEvent.REQUEST_GET );
543        }
544
545        if ( authenticationInfo != null )
546        {
547            String username = authenticationInfo.getUserName();
548            String password = authenticationInfo.getPassword();
549            // preemptive for put
550            if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) )
551            {
552                AuthCache authCache = new BasicAuthCache();
553                BasicScheme basicAuth = new BasicScheme();
554                HttpHost targetHost =
555                    new HttpHost( repository.getHost(), repository.getPort(), repository.getProtocol() );
556                authCache.put( targetHost, basicAuth );
557
558                localContext = new BasicHttpContext();
559                localContext.setAttribute( ClientContext.AUTH_CACHE, authCache );
560            }
561        }
562
563        HttpPut putMethod = new HttpPut( url );
564
565        firePutStarted( resource, source );
566
567        try
568        {
569            putMethod.setEntity( httpEntity );
570
571            HttpResponse response;
572            try
573            {
574                response = execute( putMethod );
575            }
576            catch ( IOException e )
577            {
578                fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
579
580                throw new TransferFailedException( e.getMessage(), e );
581            }
582            catch ( HttpException e )
583            {
584                fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
585
586                throw new TransferFailedException( e.getMessage(), e );
587            }
588
589            int statusCode = response.getStatusLine().getStatusCode();
590            String reasonPhrase = ", ReasonPhrase: " + response.getStatusLine().getReasonPhrase() + ".";
591            fireTransferDebug( url + " - Status code: " + statusCode + reasonPhrase );
592
593            // Check that we didn't run out of retries.
594            switch ( statusCode )
595            {
596                // Success Codes
597                case HttpStatus.SC_OK: // 200
598                case HttpStatus.SC_CREATED: // 201
599                case HttpStatus.SC_ACCEPTED: // 202
600                case HttpStatus.SC_NO_CONTENT:  // 204
601                    break;
602                // handle all redirect even if http specs says " the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user"
603                case HttpStatus.SC_MOVED_PERMANENTLY: // 301
604                case HttpStatus.SC_MOVED_TEMPORARILY: // 302
605                case HttpStatus.SC_SEE_OTHER: // 303
606                    put( resource, source, httpEntity, calculateRelocatedUrl( response ) );
607                    return;
608                case SC_NULL:
609                {
610                    TransferFailedException e =
611                        new TransferFailedException( "Failed to transfer file: " + url + reasonPhrase );
612                    fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
613                    throw e;
614                }
615
616                case HttpStatus.SC_FORBIDDEN:
617                    fireSessionConnectionRefused();
618                    throw new AuthorizationException( "Access denied to: " + url + reasonPhrase );
619
620                case HttpStatus.SC_NOT_FOUND:
621                    throw new ResourceDoesNotExistException( "File: " + url + " does not exist" + reasonPhrase );
622
623                    //add more entries here
624                default:
625                {
626                    TransferFailedException e = new TransferFailedException(
627                        "Failed to transfer file: " + url + ". Return code is: " + statusCode + reasonPhrase );
628                    fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
629                    throw e;
630                }
631            }
632
633            firePutCompleted( resource, source );
634        }
635        finally
636        {
637            putMethod.abort();
638        }
639    }
640
641    protected String calculateRelocatedUrl( HttpResponse response )
642    {
643        Header locationHeader = response.getFirstHeader( "Location" );
644        String locationField = locationHeader.getValue();
645        // is it a relative Location or a full ?
646        return locationField.startsWith( "http" ) ? locationField : getURL( getRepository() ) + '/' + locationField;
647    }
648
649    protected void mkdirs( String dirname )
650        throws HttpException, IOException
651    {
652        // nothing to do
653    }
654
655    public boolean resourceExists( String resourceName )
656        throws TransferFailedException, AuthorizationException
657    {
658        String repositoryUrl = getRepository().getUrl();
659        String url = repositoryUrl + ( repositoryUrl.endsWith( "/" ) ? "" : "/" ) + resourceName;
660        HttpHead headMethod = new HttpHead( url );
661        HttpResponse response = null;
662        int statusCode;
663        try
664        {
665            response = execute( headMethod );
666        }
667        catch ( IOException e )
668        {
669            throw new TransferFailedException( e.getMessage(), e );
670        }
671        catch ( HttpException e )
672        {
673            throw new TransferFailedException( e.getMessage(), e );
674        }
675
676        try
677        {
678            statusCode = response.getStatusLine().getStatusCode();
679            String reasonPhrase = ", ReasonPhrase: " + response.getStatusLine().getReasonPhrase() + ".";
680            switch ( statusCode )
681            {
682                case HttpStatus.SC_OK:
683                    return true;
684
685                case HttpStatus.SC_NOT_MODIFIED:
686                    return true;
687
688                case SC_NULL:
689                    throw new TransferFailedException( "Failed to transfer file: " + url + reasonPhrase );
690
691                case HttpStatus.SC_FORBIDDEN:
692                    throw new AuthorizationException( "Access denied to: " + url + reasonPhrase );
693
694                case HttpStatus.SC_UNAUTHORIZED:
695                    throw new AuthorizationException( "Not authorized " + reasonPhrase );
696
697                case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
698                    throw new AuthorizationException( "Not authorized by proxy " + reasonPhrase );
699
700                case HttpStatus.SC_NOT_FOUND:
701                    return false;
702
703                //add more entries here
704                default:
705                    throw new TransferFailedException(
706                        "Failed to transfer file: " + url + ". Return code is: " + statusCode + reasonPhrase );
707            }
708        }
709        finally
710        {
711            headMethod.abort();
712        }
713    }
714
715    protected HttpResponse execute( HttpUriRequest httpMethod )
716        throws HttpException, IOException
717    {
718        setParameters( httpMethod );
719        setHeaders( httpMethod );
720        client.getParams().setParameter( CoreProtocolPNames.USER_AGENT, getUserAgent( httpMethod ) );
721
722        ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
723
724        if ( proxyInfo != null )
725        {
726            if ( proxyInfo.getUserName() != null && proxyInfo.getPassword() != null )
727            {
728                Credentials creds;
729                if ( proxyInfo.getNtlmHost() != null || proxyInfo.getNtlmDomain() != null )
730                {
731                    creds =
732                        new NTCredentials( proxyInfo.getUserName(), proxyInfo.getPassword(), proxyInfo.getNtlmHost(),
733                                           proxyInfo.getNtlmDomain() );
734                }
735                else
736                {
737                    creds = new UsernamePasswordCredentials( proxyInfo.getUserName(), proxyInfo.getPassword() );
738                }
739
740                Header bs = new BasicScheme().authenticate( creds, httpMethod );
741                httpMethod.addHeader( "Proxy-Authorization", bs.getValue() );
742            }
743
744        }
745
746        return client.execute( httpMethod, localContext );
747    }
748
749    protected void setParameters( HttpUriRequest method )
750    {
751        HttpMethodConfiguration config =
752            httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
753        if ( config != null )
754        {
755            HttpParams params = config.asMethodParams( method.getParams() );
756
757            if ( config.isUsePreemptive() && authenticationInfo != null )
758            {
759                String username = authenticationInfo.getUserName();
760                String password = authenticationInfo.getPassword();
761
762                if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) )
763                {
764
765                    AuthCache authCache = new BasicAuthCache();
766                    BasicScheme basicAuth = new BasicScheme();
767                    HttpHost targetHost =
768                        new HttpHost( repository.getHost(), repository.getPort(), repository.getProtocol() );
769                    authCache.put( targetHost, basicAuth );
770
771                    localContext = new BasicHttpContext();
772                    localContext.setAttribute( ClientContext.AUTH_CACHE, authCache );
773                }
774
775            }
776
777            if ( params != null )
778            {
779                method.setParams( params );
780            }
781        }
782
783        if ( config == null )
784        {
785            int readTimeout = getReadTimeout();
786            method.getParams().setParameter( CoreConnectionPNames.SO_TIMEOUT, readTimeout );
787        }
788    }
789
790    protected void setHeaders( HttpUriRequest method )
791    {
792        HttpMethodConfiguration config =
793            httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
794        if ( config == null || config.isUseDefaultHeaders() )
795        {
796            // TODO: merge with the other headers and have some better defaults, unify with lightweight headers
797            method.addHeader( "Cache-control", "no-cache" );
798            method.addHeader( "Cache-store", "no-store" );
799            method.addHeader( "Pragma", "no-cache" );
800            method.addHeader( "Expires", "0" );
801            method.addHeader( "Accept-Encoding", "gzip" );
802        }
803
804        if ( httpHeaders != null )
805        {
806            for ( Map.Entry<Object, Object> entry : httpHeaders.entrySet() )
807            {
808                method.addHeader( (String) entry.getKey(), (String) entry.getValue() );
809            }
810        }
811
812        Header[] headers = config == null ? null : config.asRequestHeaders();
813        if ( headers != null )
814        {
815            for ( int i = 0; i < headers.length; i++ )
816            {
817                method.addHeader( headers[i] );
818            }
819        }
820    }
821
822    protected String getUserAgent( HttpUriRequest method )
823    {
824        if ( httpHeaders != null )
825        {
826            String value = (String) httpHeaders.get( "User-Agent" );
827            if ( value != null )
828            {
829                return value;
830            }
831        }
832        HttpMethodConfiguration config =
833            httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
834
835        if ( config != null )
836        {
837            return (String) config.getHeaders().get( "User-Agent" );
838        }
839        return null;
840    }
841
842    /**
843     * getUrl
844     * Implementors can override this to remove unwanted parts of the url such as role-hints
845     *
846     * @param repository
847     * @return
848     */
849    protected String getURL( Repository repository )
850    {
851        return repository.getUrl();
852    }
853
854    public HttpConfiguration getHttpConfiguration()
855    {
856        return httpConfiguration;
857    }
858
859    public void setHttpConfiguration( HttpConfiguration httpConfiguration )
860    {
861        this.httpConfiguration = httpConfiguration;
862    }
863
864    public void fillInputData( InputData inputData )
865        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
866    {
867        Resource resource = inputData.getResource();
868
869        String repositoryUrl = getRepository().getUrl();
870        String url = repositoryUrl + ( repositoryUrl.endsWith( "/" ) ? "" : "/" ) + resource.getName();
871        getMethod = new HttpGet( url );
872        long timestamp = resource.getLastModified();
873        if ( timestamp > 0 )
874        {
875            SimpleDateFormat fmt = new SimpleDateFormat( "EEE, dd-MMM-yy HH:mm:ss zzz", Locale.US );
876            fmt.setTimeZone( GMT_TIME_ZONE );
877            Header hdr = new BasicHeader( "If-Modified-Since", fmt.format( new Date( timestamp ) ) );
878            fireTransferDebug( "sending ==> " + hdr + "(" + timestamp + ")" );
879            getMethod.addHeader( hdr );
880        }
881
882        HttpResponse response;
883        int statusCode;
884        try
885        {
886            response = execute( getMethod );
887        }
888        catch ( IOException e )
889        {
890            fireTransferError( resource, e, TransferEvent.REQUEST_GET );
891
892            throw new TransferFailedException( e.getMessage(), e );
893        }
894        catch ( HttpException e )
895        {
896            fireTransferError( resource, e, TransferEvent.REQUEST_GET );
897
898            throw new TransferFailedException( e.getMessage(), e );
899        }
900
901        statusCode = response.getStatusLine().getStatusCode();
902
903        String reasonPhrase = ", ReasonPhrase:" + response.getStatusLine().getReasonPhrase() + ".";
904
905        fireTransferDebug( url + " - Status code: " + statusCode + reasonPhrase );
906
907        // TODO [BP]: according to httpclient docs, really should swallow the output on error. verify if that is
908        // required
909        switch ( statusCode )
910        {
911            case HttpStatus.SC_OK:
912                break;
913
914            case HttpStatus.SC_NOT_MODIFIED:
915                // return, leaving last modified set to original value so getIfNewer should return unmodified
916                return;
917
918            case SC_NULL:
919            {
920                TransferFailedException e =
921                    new TransferFailedException( "Failed to transfer file: " + url + " " + reasonPhrase );
922                fireTransferError( resource, e, TransferEvent.REQUEST_GET );
923                throw e;
924            }
925
926            case HttpStatus.SC_FORBIDDEN:
927                fireSessionConnectionRefused();
928                throw new AuthorizationException( "Access denied to: " + url + " " + reasonPhrase );
929
930            case HttpStatus.SC_UNAUTHORIZED:
931                fireSessionConnectionRefused();
932                throw new AuthorizationException( "Not authorized " + reasonPhrase );
933
934            case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
935                fireSessionConnectionRefused();
936                throw new AuthorizationException( "Not authorized by proxy " + reasonPhrase );
937
938            case HttpStatus.SC_NOT_FOUND:
939                throw new ResourceDoesNotExistException( "File: " + url + " " + reasonPhrase );
940
941                // add more entries here
942            default:
943            {
944                cleanupGetTransfer( resource );
945                TransferFailedException e = new TransferFailedException(
946                    "Failed to transfer file: " + url + ". Return code is: " + statusCode + " " + reasonPhrase );
947                fireTransferError( resource, e, TransferEvent.REQUEST_GET );
948                throw e;
949            }
950        }
951
952        InputStream is;
953
954        Header contentLengthHeader = response.getFirstHeader( "Content-Length" );
955
956        if ( contentLengthHeader != null )
957        {
958            try
959            {
960                long contentLength = Long.parseLong( contentLengthHeader.getValue() );
961
962                resource.setContentLength( contentLength );
963            }
964            catch ( NumberFormatException e )
965            {
966                fireTransferDebug(
967                    "error parsing content length header '" + contentLengthHeader.getValue() + "' " + e );
968            }
969        }
970
971        Header lastModifiedHeader = response.getFirstHeader( "Last-Modified" );
972
973        long lastModified = 0;
974
975        if ( lastModifiedHeader != null )
976        {
977            try
978            {
979                lastModified = DateUtils.parseDate( lastModifiedHeader.getValue() ).getTime();
980
981                resource.setLastModified( lastModified );
982            }
983            catch ( DateParseException e )
984            {
985                fireTransferDebug( "Unable to parse last modified header" );
986            }
987
988            fireTransferDebug( "last-modified = " + lastModifiedHeader.getValue() + " (" + lastModified + ")" );
989        }
990
991        Header contentEncoding = response.getFirstHeader( "Content-Encoding" );
992        boolean isGZipped = contentEncoding == null ? false : "gzip".equalsIgnoreCase( contentEncoding.getValue() );
993
994        try
995        {
996            is = response.getEntity().getContent();
997
998            if ( isGZipped )
999            {
1000                is = new GZIPInputStream( is );
1001            }
1002        }
1003        catch ( IOException e )
1004        {
1005            fireTransferError( resource, e, TransferEvent.REQUEST_GET );
1006
1007            String msg =
1008                "Error occurred while retrieving from remote repository " + getRepository() + ": " + e.getMessage();
1009
1010            throw new TransferFailedException( msg, e );
1011        }
1012
1013        inputData.setInputStream( is );
1014    }
1015
1016    protected void cleanupGetTransfer( Resource resource )
1017    {
1018        if ( getMethod != null )
1019        {
1020            getMethod.abort();
1021        }
1022    }
1023
1024
1025    @Override
1026    public void putFromStream( InputStream stream, String destination )
1027        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
1028    {
1029        putFromStream( stream, destination, -1, -1 );
1030    }
1031
1032    @Override
1033    protected void putFromStream( InputStream stream, Resource resource )
1034        throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
1035    {
1036        putFromStream( stream, resource.getName(), -1, -1 );
1037    }
1038
1039    public Properties getHttpHeaders()
1040    {
1041        return httpHeaders;
1042    }
1043
1044    public void setHttpHeaders( Properties httpHeaders )
1045    {
1046        this.httpHeaders = httpHeaders;
1047    }
1048
1049    @Override
1050    public void fillOutputData( OutputData outputData )
1051        throws TransferFailedException
1052    {
1053        // no needed in this implementation but throw an Exception if used
1054        throw new IllegalStateException( "this wagon http client must not use fillOutputData" );
1055    }
1056}