View Javadoc

1   package org.apache.maven.wagon.shared.http4;
2   
3   /*
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
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
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
19   * under the License.
20   */
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.OutputStream;
28  import java.net.URLEncoder;
29  import java.nio.ByteBuffer;
30  import java.security.cert.X509Certificate;
31  import java.text.SimpleDateFormat;
32  import java.util.Date;
33  import java.util.Locale;
34  import java.util.Map;
35  import java.util.Properties;
36  import java.util.TimeZone;
37  import java.util.zip.GZIPInputStream;
38  
39  import javax.net.ssl.HttpsURLConnection;
40  import javax.net.ssl.SSLException;
41  import javax.net.ssl.SSLSession;
42  import javax.net.ssl.SSLSocket;
43  
44  import org.apache.http.Header;
45  import org.apache.http.HttpEntity;
46  import org.apache.http.HttpException;
47  import org.apache.http.HttpHost;
48  import org.apache.http.HttpResponse;
49  import org.apache.http.HttpStatus;
50  import org.apache.http.auth.AuthScope;
51  import org.apache.http.auth.Credentials;
52  import org.apache.http.auth.NTCredentials;
53  import org.apache.http.auth.UsernamePasswordCredentials;
54  import org.apache.http.client.AuthCache;
55  import org.apache.http.client.methods.HttpGet;
56  import org.apache.http.client.methods.HttpHead;
57  import org.apache.http.client.methods.HttpPut;
58  import org.apache.http.client.methods.HttpUriRequest;
59  import org.apache.http.client.params.ClientPNames;
60  import org.apache.http.client.params.CookiePolicy;
61  import org.apache.http.client.protocol.ClientContext;
62  import org.apache.http.conn.ClientConnectionManager;
63  import org.apache.http.conn.params.ConnRoutePNames;
64  import org.apache.http.conn.scheme.PlainSocketFactory;
65  import org.apache.http.conn.scheme.Scheme;
66  import org.apache.http.conn.scheme.SchemeRegistry;
67  import org.apache.http.conn.ssl.SSLSocketFactory;
68  import org.apache.http.conn.ssl.X509HostnameVerifier;
69  import org.apache.http.entity.AbstractHttpEntity;
70  import org.apache.http.impl.auth.BasicScheme;
71  import org.apache.http.impl.client.BasicAuthCache;
72  import org.apache.http.impl.client.DefaultHttpClient;
73  import org.apache.http.impl.conn.BasicClientConnectionManager;
74  import org.apache.http.impl.conn.PoolingClientConnectionManager;
75  import org.apache.http.impl.cookie.DateParseException;
76  import org.apache.http.impl.cookie.DateUtils;
77  import org.apache.http.message.BasicHeader;
78  import org.apache.http.params.CoreConnectionPNames;
79  import org.apache.http.params.CoreProtocolPNames;
80  import org.apache.http.params.HttpParams;
81  import org.apache.http.protocol.BasicHttpContext;
82  import org.apache.maven.wagon.InputData;
83  import org.apache.maven.wagon.OutputData;
84  import org.apache.maven.wagon.PathUtils;
85  import org.apache.maven.wagon.ResourceDoesNotExistException;
86  import org.apache.maven.wagon.StreamWagon;
87  import org.apache.maven.wagon.TransferFailedException;
88  import org.apache.maven.wagon.Wagon;
89  import org.apache.maven.wagon.authorization.AuthorizationException;
90  import org.apache.maven.wagon.events.TransferEvent;
91  import org.apache.maven.wagon.proxy.ProxyInfo;
92  import org.apache.maven.wagon.repository.Repository;
93  import org.apache.maven.wagon.resource.Resource;
94  import org.codehaus.plexus.util.IOUtil;
95  import org.codehaus.plexus.util.StringUtils;
96  
97  /**
98   * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a>
99   * @author <a href="mailto:james@atlassian.com">James William Dumay</a>
100  */
101 public 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 }