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 org.apache.http.Header;
23  import org.apache.http.HttpEntity;
24  import org.apache.http.HttpException;
25  import org.apache.http.HttpHost;
26  import org.apache.http.HttpResponse;
27  import org.apache.http.HttpStatus;
28  import org.apache.http.auth.AuthScope;
29  import org.apache.http.auth.Credentials;
30  import org.apache.http.auth.NTCredentials;
31  import org.apache.http.auth.UsernamePasswordCredentials;
32  import org.apache.http.client.AuthCache;
33  import org.apache.http.client.methods.HttpGet;
34  import org.apache.http.client.methods.HttpHead;
35  import org.apache.http.client.methods.HttpPut;
36  import org.apache.http.client.methods.HttpUriRequest;
37  import org.apache.http.client.params.ClientPNames;
38  import org.apache.http.client.params.CookiePolicy;
39  import org.apache.http.client.protocol.ClientContext;
40  import org.apache.http.conn.ClientConnectionManager;
41  import org.apache.http.conn.params.ConnRoutePNames;
42  import org.apache.http.conn.scheme.Scheme;
43  import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
44  import org.apache.http.conn.ssl.SSLSocketFactory;
45  import org.apache.http.conn.ssl.X509HostnameVerifier;
46  import org.apache.http.entity.AbstractHttpEntity;
47  import org.apache.http.impl.auth.BasicScheme;
48  import org.apache.http.impl.client.BasicAuthCache;
49  import org.apache.http.impl.client.DefaultHttpClient;
50  import org.apache.http.impl.conn.SingleClientConnManager;
51  import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
52  import org.apache.http.impl.cookie.DateParseException;
53  import org.apache.http.impl.cookie.DateUtils;
54  import org.apache.http.message.BasicHeader;
55  import org.apache.http.params.CoreConnectionPNames;
56  import org.apache.http.params.CoreProtocolPNames;
57  import org.apache.http.params.HttpParams;
58  import org.apache.http.protocol.BasicHttpContext;
59  import org.apache.maven.wagon.InputData;
60  import org.apache.maven.wagon.OutputData;
61  import org.apache.maven.wagon.PathUtils;
62  import org.apache.maven.wagon.ResourceDoesNotExistException;
63  import org.apache.maven.wagon.StreamWagon;
64  import org.apache.maven.wagon.TransferFailedException;
65  import org.apache.maven.wagon.Wagon;
66  import org.apache.maven.wagon.authorization.AuthorizationException;
67  import org.apache.maven.wagon.events.TransferEvent;
68  import org.apache.maven.wagon.proxy.ProxyInfo;
69  import org.apache.maven.wagon.repository.Repository;
70  import org.apache.maven.wagon.resource.Resource;
71  import org.codehaus.plexus.util.StringUtils;
72  
73  import javax.net.ssl.SSLException;
74  import javax.net.ssl.SSLSession;
75  import javax.net.ssl.SSLSocket;
76  import java.io.File;
77  import java.io.FileInputStream;
78  import java.io.IOException;
79  import java.io.InputStream;
80  import java.io.OutputStream;
81  import java.net.URLEncoder;
82  import java.security.cert.X509Certificate;
83  import java.text.SimpleDateFormat;
84  import java.util.Date;
85  import java.util.Locale;
86  import java.util.Map;
87  import java.util.Properties;
88  import java.util.TimeZone;
89  import java.util.zip.GZIPInputStream;
90  
91  /**
92   * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a>
93   * @author <a href="mailto:james@atlassian.com">James William Dumay</a>
94   */
95  public abstract class AbstractHttpClientWagon
96      extends StreamWagon
97  {
98  
99      private BasicHttpContext localContext;
100 
101     private final class RequestEntityImplementation
102         extends AbstractHttpEntity
103     {
104 
105         private final static int BUFFER_SIZE = 2048;
106 
107         private final Resource resource;
108 
109         private final Wagon wagon;
110 
111         private InputStream stream;
112 
113         private File source;
114 
115         private long length;
116 
117         private RequestEntityImplementation( final InputStream stream, final Resource resource, final Wagon wagon,
118                                              final File source )
119             throws TransferFailedException
120         {
121             if ( source != null )
122             {
123                 this.source = source;
124             }
125             else
126             {
127                 this.stream = stream;
128             }
129             this.resource = resource;
130             this.length = resource == null ? -1 : resource.getContentLength();
131             this.wagon = wagon;
132         }
133 
134         public long getContentLength()
135         {
136             return length;
137         }
138 
139 
140         public InputStream getContent()
141             throws IOException, IllegalStateException
142         {
143             return this.source != null ? new FileInputStream( this.source ) : this.stream;
144         }
145 
146         public boolean isRepeatable()
147         {
148             return false;
149         }
150 
151 
152         public void writeTo( final OutputStream outstream )
153             throws IOException
154         {
155             if ( outstream == null )
156             {
157                 throw new IllegalArgumentException( "Output stream may not be null" );
158             }
159             TransferEvent transferEvent =
160                 new TransferEvent( wagon, resource, TransferEvent.TRANSFER_PROGRESS, TransferEvent.REQUEST_PUT );
161             transferEvent.setTimestamp( System.currentTimeMillis() );
162             InputStream instream = this.source != null ? new FileInputStream( this.source ) : this.stream;
163             try
164             {
165                 byte[] buffer = new byte[BUFFER_SIZE];
166                 int l;
167                 if ( this.length < 0 )
168                 {
169                     // until EOF
170                     while ( ( l = instream.read( buffer ) ) != -1 )
171                     {
172                         fireTransferProgress( transferEvent, buffer, -1 );
173                         outstream.write( buffer, 0, l );
174                     }
175                 }
176                 else
177                 {
178                     // no need to consume more than length
179                     long remaining = this.length;
180                     while ( remaining > 0 )
181                     {
182                         l = instream.read( buffer, 0, (int) Math.min( BUFFER_SIZE, remaining ) );
183                         if ( l == -1 )
184                         {
185                             break;
186                         }
187                         fireTransferProgress( transferEvent, buffer, (int) Math.min( BUFFER_SIZE, remaining ) );
188                         outstream.write( buffer, 0, l );
189                         remaining -= l;
190                     }
191                 }
192             }
193             finally
194             {
195                 instream.close();
196             }
197         }
198 
199         public boolean isStreaming()
200         {
201             return true;
202         }
203 
204 
205     }
206 
207     protected static final int SC_NULL = -1;
208 
209     protected static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone( "GMT" );
210 
211     private DefaultHttpClient client;
212 
213     /**
214      * @since 2.0
215      */
216     protected static ClientConnectionManager connectionManagerPooled;
217 
218     /**
219      * @since 2.0
220      */
221     protected ClientConnectionManager clientConnectionManager = new SingleClientConnManager();
222 
223     /**
224      * use http(s) connection pool mechanism.
225      * <b>enabled by default</b>
226      *
227      * @since 2.0
228      */
229     protected static boolean useClientManagerPooled =
230         Boolean.valueOf( System.getProperty( "maven.wagon.http.pool", "true" ) );
231 
232     /**
233      * skip failure on certificate validity checks.
234      * <b>enabled by default</b>
235      *
236      * @since 2.0
237      */
238     protected static boolean sslEasy = Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.easy", "true" ) );
239 
240     /**
241      * ssl hostname verifier is allow all by default. Disable this will use a browser compat hostname verifier
242      * <b>enabled by default</b>
243      *
244      * @since 2.0
245      */
246     protected static boolean sslAllowAll =
247         Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.allowall", "true" ) );
248 
249     /**
250      * if using sslEasy certificate date issues will be ignored
251      * <b>enabled by default</b>
252      *
253      * @since 2.0
254      */
255     protected static boolean IGNORE_SSL_VALIDITY_DATES =
256         Boolean.valueOf( System.getProperty( "maven.wagon.http.ssl.ignore.validity.dates", "true" ) );
257 
258     static
259     {
260         if ( !useClientManagerPooled )
261         {
262             System.out.println( "http connection pool disabled in wagon http" );
263         }
264         else
265         {
266 
267             ThreadSafeClientConnManager threadSafeClientConnManager = new ThreadSafeClientConnManager();
268             int maxPerRoute =
269                 Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxPerRoute", "20" ) );
270             threadSafeClientConnManager.setDefaultMaxPerRoute( maxPerRoute );
271             int maxTotal = Integer.parseInt( System.getProperty( "maven.wagon.httpconnectionManager.maxTotal", "40" ) );
272             threadSafeClientConnManager.setDefaultMaxPerRoute( maxPerRoute );
273             threadSafeClientConnManager.setMaxTotal( maxTotal );
274 
275             if ( sslEasy )
276             {
277                 try
278                 {
279                     SSLSocketFactory sslSocketFactory =
280                         new SSLSocketFactory( EasyX509TrustManager.createEasySSLContext(), sslAllowAll
281                             ? new EasyHostNameVerifier()
282                             : new BrowserCompatHostnameVerifier() );
283                     Scheme httpsScheme = new Scheme( "https", 443, sslSocketFactory );
284 
285                     threadSafeClientConnManager.getSchemeRegistry().register( httpsScheme );
286                 }
287                 catch ( IOException e )
288                 {
289                     throw new RuntimeException( "failed to init SSLSocket Factory " + e.getMessage(), e );
290                 }
291             }
292             connectionManagerPooled = threadSafeClientConnManager;
293         }
294     }
295 
296     /**
297      * disable all host name verification
298      *
299      * @since 2.0
300      */
301     private static class EasyHostNameVerifier
302         implements X509HostnameVerifier
303     {
304         public void verify( String s, SSLSocket sslSocket )
305             throws IOException
306         {
307             //no op
308         }
309 
310         public void verify( String s, X509Certificate x509Certificate )
311             throws SSLException
312         {
313             //no op
314         }
315 
316         public void verify( String s, String[] strings, String[] strings1 )
317             throws SSLException
318         {
319             //no op
320         }
321 
322         public boolean verify( String s, SSLSession sslSession )
323         {
324             return true;
325         }
326     }
327 
328     public ClientConnectionManager getConnectionManager()
329     {
330         if ( !useClientManagerPooled )
331         {
332             return clientConnectionManager;
333         }
334         return connectionManagerPooled;
335     }
336 
337     public static void setConnectionManagerPooled( ClientConnectionManager clientConnectionManager )
338     {
339         connectionManagerPooled = clientConnectionManager;
340     }
341 
342     public static void setUseClientManagerPooled( boolean pooledClientManager )
343     {
344         useClientManagerPooled = pooledClientManager;
345     }
346 
347     /**
348      * @plexus.configuration
349      * @deprecated Use httpConfiguration instead.
350      */
351     private Properties httpHeaders;
352 
353     /**
354      * @since 1.0-beta-6
355      */
356     private HttpConfiguration httpConfiguration;
357 
358     private HttpGet getMethod;
359 
360     public void openConnectionInternal()
361     {
362         repository.setUrl( getURL( repository ) );
363         client = new DefaultHttpClient( getConnectionManager() );
364 
365         // WAGON-273: default the cookie-policy to browser compatible
366         client.getParams().setParameter( ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY );
367 
368         String username = null;
369         String password = null;
370 
371         if ( authenticationInfo != null )
372         {
373             username = authenticationInfo.getUserName();
374 
375             password = authenticationInfo.getPassword();
376         }
377 
378         if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) )
379         {
380             Credentials creds = new UsernamePasswordCredentials( username, password );
381 
382             String host = getRepository().getHost();
383             int port = getRepository().getPort() > -1 ? getRepository().getPort() : AuthScope.ANY_PORT;
384 
385             client.getCredentialsProvider().setCredentials( new AuthScope( host, port ), creds );
386 
387             AuthCache authCache = new BasicAuthCache();
388             BasicScheme basicAuth = new BasicScheme();
389             HttpHost targetHost = new HttpHost( repository.getHost(), repository.getPort(), repository.getProtocol() );
390             authCache.put( targetHost, basicAuth );
391 
392             localContext = new BasicHttpContext();
393             localContext.setAttribute( ClientContext.AUTH_CACHE, authCache );
394         }
395 
396         ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
397         if ( proxyInfo != null )
398         {
399             String proxyUsername = proxyInfo.getUserName();
400             String proxyPassword = proxyInfo.getPassword();
401             String proxyHost = proxyInfo.getHost();
402             int proxyPort = proxyInfo.getPort();
403             String proxyNtlmHost = proxyInfo.getNtlmHost();
404             String proxyNtlmDomain = proxyInfo.getNtlmDomain();
405             if ( proxyHost != null )
406             {
407                 HttpHost proxy = new HttpHost( proxyHost, proxyPort );
408 
409                 if ( proxyUsername != null && proxyPassword != null )
410                 {
411                     Credentials creds;
412                     if ( proxyNtlmHost != null || proxyNtlmDomain != null )
413                     {
414                         creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain );
415                     }
416                     else
417                     {
418                         creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword );
419                     }
420 
421                     int port = proxyInfo.getPort() > -1 ? proxyInfo.getPort() : AuthScope.ANY_PORT;
422 
423                     AuthScope authScope = new AuthScope( proxyHost, port );
424                     client.getCredentialsProvider().setCredentials( authScope, creds );
425                 }
426 
427                 client.getParams().setParameter( ConnRoutePNames.DEFAULT_PROXY, proxy );
428             }
429         }
430     }
431 
432     public void closeConnection()
433     {
434         if ( !useClientManagerPooled )
435         {
436             getConnectionManager().shutdown();
437         }
438     }
439 
440     public void put( File source, String resourceName )
441         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
442     {
443         Resource resource = new Resource( resourceName );
444 
445         firePutInitiated( resource, source );
446 
447         resource.setContentLength( source.length() );
448 
449         resource.setLastModified( source.lastModified() );
450 
451         put( null, resource, source );
452     }
453 
454     public void putFromStream( final InputStream stream, String destination, long contentLength, long lastModified )
455         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
456     {
457         Resource resource = new Resource( destination );
458 
459         firePutInitiated( resource, null );
460 
461         resource.setContentLength( contentLength );
462 
463         resource.setLastModified( lastModified );
464 
465         put( stream, resource, null );
466     }
467 
468     private void put( final InputStream stream, Resource resource, File source )
469         throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
470     {
471         put( resource, source, new RequestEntityImplementation( stream, resource, this, source ) );
472     }
473 
474     private void put( Resource resource, File source, HttpEntity httpEntity )
475         throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
476     {
477         StringBuilder url = new StringBuilder( getRepository().getUrl() );
478         String[] parts = StringUtils.split( resource.getName(), "/" );
479         for ( String part : parts )
480         {
481             // TODO: Fix encoding...
482             // url += "/" + URLEncoder.encode( parts[i], System.getProperty("file.encoding") );
483             if ( !url.toString().endsWith( "/" ) )
484             {
485                 url.append( '/' );
486             }
487             url.append( URLEncoder.encode( part ) );
488         }
489 
490         //Parent directories need to be created before posting
491         try
492         {
493             mkdirs( PathUtils.dirname( resource.getName() ) );
494         }
495         catch ( HttpException he )
496         {
497             fireTransferError( resource, he, TransferEvent.REQUEST_GET );
498         }
499         catch ( IOException e )
500         {
501             fireTransferError( resource, e, TransferEvent.REQUEST_GET );
502         }
503 
504         HttpPut putMethod = new HttpPut( url.toString() );
505 
506         firePutStarted( resource, source );
507 
508         try
509         {
510             putMethod.setEntity( httpEntity );
511 
512             HttpResponse response;
513             try
514             {
515                 response = execute( putMethod );
516             }
517             catch ( IOException e )
518             {
519                 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
520 
521                 throw new TransferFailedException( e.getMessage(), e );
522             }
523             catch ( HttpException e )
524             {
525                 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
526 
527                 throw new TransferFailedException( e.getMessage(), e );
528             }
529 
530             int statusCode = response.getStatusLine().getStatusCode();
531             String reasonPhrase = ", ReasonPhrase:" + response.getStatusLine().getReasonPhrase() + ".";
532             fireTransferDebug( url + " - Status code: " + statusCode + reasonPhrase );
533 
534             // Check that we didn't run out of retries.
535             switch ( statusCode )
536             {
537                 // Success Codes
538                 case HttpStatus.SC_OK: // 200
539                 case HttpStatus.SC_CREATED: // 201
540                 case HttpStatus.SC_ACCEPTED: // 202
541                 case HttpStatus.SC_NO_CONTENT:  // 204
542                     break;
543 
544                 case SC_NULL:
545                 {
546                     TransferFailedException e =
547                         new TransferFailedException( "Failed to transfer file: " + url + reasonPhrase );
548                     fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
549                     throw e;
550                 }
551 
552                 case HttpStatus.SC_FORBIDDEN:
553                     fireSessionConnectionRefused();
554                     throw new AuthorizationException( "Access denied to: " + url + reasonPhrase );
555 
556                 case HttpStatus.SC_NOT_FOUND:
557                     throw new ResourceDoesNotExistException( "File: " + url + " does not exist" + reasonPhrase );
558 
559                     //add more entries here
560                 default:
561                 {
562                     TransferFailedException e = new TransferFailedException(
563                         "Failed to transfer file: " + url + ". Return code is: " + statusCode + reasonPhrase );
564                     fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
565                     throw e;
566                 }
567             }
568 
569             firePutCompleted( resource, source );
570         }
571         finally
572         {
573             putMethod.abort();
574         }
575     }
576 
577     protected void mkdirs( String dirname )
578         throws HttpException, IOException
579     {
580         // nothing to do
581     }
582 
583     public boolean resourceExists( String resourceName )
584         throws TransferFailedException, AuthorizationException
585     {
586         String repositoryUrl = getRepository().getUrl();
587         String url = repositoryUrl + ( repositoryUrl.endsWith( "/" ) ? "" : "/" ) + resourceName;
588         HttpHead headMethod = new HttpHead( url );
589         HttpResponse response = null;
590         int statusCode;
591         try
592         {
593             response = execute( headMethod );
594         }
595         catch ( IOException e )
596         {
597             throw new TransferFailedException( e.getMessage(), e );
598         }
599         catch ( HttpException e )
600         {
601             throw new TransferFailedException( e.getMessage(), e );
602         }
603 
604         try
605         {
606             statusCode = response.getStatusLine().getStatusCode();
607             String reasonPhrase = ", ReasonPhrase:" + response.getStatusLine().getReasonPhrase() + ".";
608             switch ( statusCode )
609             {
610                 case HttpStatus.SC_OK:
611                     return true;
612 
613                 case HttpStatus.SC_NOT_MODIFIED:
614                     return true;
615 
616                 case SC_NULL:
617                     throw new TransferFailedException( "Failed to transfer file: " + url + reasonPhrase );
618 
619                 case HttpStatus.SC_FORBIDDEN:
620                     throw new AuthorizationException( "Access denied to: " + url + reasonPhrase );
621 
622                 case HttpStatus.SC_UNAUTHORIZED:
623                     throw new AuthorizationException( "Not authorized" + reasonPhrase );
624 
625                 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
626                     throw new AuthorizationException( "Not authorized by proxy" + reasonPhrase );
627 
628                 case HttpStatus.SC_NOT_FOUND:
629                     return false;
630 
631                 //add more entries here
632                 default:
633                     throw new TransferFailedException(
634                         "Failed to transfer file: " + url + ". Return code is: " + statusCode + reasonPhrase );
635             }
636         }
637         finally
638         {
639             headMethod.abort();
640         }
641     }
642 
643     protected HttpResponse execute( HttpUriRequest httpMethod )
644         throws HttpException, IOException
645     {
646         setParameters( httpMethod );
647         setHeaders( httpMethod );
648         client.getParams().setParameter( CoreProtocolPNames.USER_AGENT, getUserAgent( httpMethod ) );
649 
650         ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
651 
652         if ( proxyInfo != null )
653         {
654             if ( proxyInfo.getUserName() != null && proxyInfo.getPassword() != null )
655             {
656                 Credentials creds;
657                 if ( proxyInfo.getNtlmHost() != null || proxyInfo.getNtlmDomain() != null )
658                 {
659                     creds =
660                         new NTCredentials( proxyInfo.getUserName(), proxyInfo.getPassword(), proxyInfo.getNtlmHost(),
661                                            proxyInfo.getNtlmDomain() );
662                 }
663                 else
664                 {
665                     creds = new UsernamePasswordCredentials( proxyInfo.getUserName(), proxyInfo.getPassword() );
666                 }
667 
668                 Header bs = new BasicScheme().authenticate( creds, httpMethod );
669                 httpMethod.addHeader( "Proxy-Authorization", bs.getValue() );
670             }
671 
672         }
673 
674         return client.execute( httpMethod, localContext );
675     }
676 
677     protected void setParameters( HttpUriRequest method )
678     {
679         HttpMethodConfiguration config =
680             httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
681         if ( config != null )
682         {
683             HttpParams params = config.asMethodParams( method.getParams() );
684             if ( params != null )
685             {
686                 method.setParams( params );
687             }
688         }
689 
690         if ( config == null )
691         {
692             int readTimeout = getReadTimeout();
693             method.getParams().setParameter( CoreConnectionPNames.SO_TIMEOUT, readTimeout );
694         }
695     }
696 
697     protected void setHeaders( HttpUriRequest method )
698     {
699         HttpMethodConfiguration config =
700             httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
701         if ( config == null || config.isUseDefaultHeaders() )
702         {
703             // TODO: merge with the other headers and have some better defaults, unify with lightweight headers
704             method.addHeader( "Cache-control", "no-cache" );
705             method.addHeader( "Cache-store", "no-store" );
706             method.addHeader( "Pragma", "no-cache" );
707             method.addHeader( "Expires", "0" );
708             method.addHeader( "Accept-Encoding", "gzip" );
709         }
710 
711         if ( httpHeaders != null )
712         {
713             for ( Map.Entry<Object, Object> entry : httpHeaders.entrySet() )
714             {
715                 method.addHeader( (String) entry.getKey(), (String) entry.getValue() );
716             }
717         }
718 
719         Header[] headers = config == null ? null : config.asRequestHeaders();
720         if ( headers != null )
721         {
722             for ( int i = 0; i < headers.length; i++ )
723             {
724                 method.addHeader( headers[i] );
725             }
726         }
727     }
728 
729     protected String getUserAgent( HttpUriRequest method )
730     {
731         if ( httpHeaders != null )
732         {
733             String value = (String) httpHeaders.get( "User-Agent" );
734             if ( value != null )
735             {
736                 return value;
737             }
738         }
739         HttpMethodConfiguration config =
740             httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method );
741 
742         if ( config != null )
743         {
744             return (String) config.getHeaders().get( "User-Agent" );
745         }
746         return null;
747     }
748 
749     /**
750      * getUrl
751      * Implementors can override this to remove unwanted parts of the url such as role-hints
752      *
753      * @param repository
754      * @return
755      */
756     protected String getURL( Repository repository )
757     {
758         return repository.getUrl();
759     }
760 
761     public HttpConfiguration getHttpConfiguration()
762     {
763         return httpConfiguration;
764     }
765 
766     public void setHttpConfiguration( HttpConfiguration httpConfiguration )
767     {
768         this.httpConfiguration = httpConfiguration;
769     }
770 
771     public void fillInputData( InputData inputData )
772         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
773     {
774         Resource resource = inputData.getResource();
775 
776         String repositoryUrl = getRepository().getUrl();
777         String url = repositoryUrl + ( repositoryUrl.endsWith( "/" ) ? "" : "/" ) + resource.getName();
778         getMethod = new HttpGet( url );
779         long timestamp = resource.getLastModified();
780         if ( timestamp > 0 )
781         {
782             SimpleDateFormat fmt = new SimpleDateFormat( "EEE, dd-MMM-yy HH:mm:ss zzz", Locale.US );
783             fmt.setTimeZone( GMT_TIME_ZONE );
784             Header hdr = new BasicHeader( "If-Modified-Since", fmt.format( new Date( timestamp ) ) );
785             fireTransferDebug( "sending ==> " + hdr + "(" + timestamp + ")" );
786             getMethod.addHeader( hdr );
787         }
788 
789         HttpResponse response;
790         int statusCode;
791         try
792         {
793             response = execute( getMethod );
794         }
795         catch ( IOException e )
796         {
797             fireTransferError( resource, e, TransferEvent.REQUEST_GET );
798 
799             throw new TransferFailedException( e.getMessage(), e );
800         }
801         catch ( HttpException e )
802         {
803             fireTransferError( resource, e, TransferEvent.REQUEST_GET );
804 
805             throw new TransferFailedException( e.getMessage(), e );
806         }
807 
808         statusCode = response.getStatusLine().getStatusCode();
809 
810         String reasonPhrase = ", ReasonPhrase:" + response.getStatusLine().getReasonPhrase() + ".";
811 
812         fireTransferDebug( url + " - Status code: " + statusCode + reasonPhrase );
813 
814         // TODO [BP]: according to httpclient docs, really should swallow the output on error. verify if that is
815         // required
816         switch ( statusCode )
817         {
818             case HttpStatus.SC_OK:
819                 break;
820 
821             case HttpStatus.SC_NOT_MODIFIED:
822                 // return, leaving last modified set to original value so getIfNewer should return unmodified
823                 return;
824 
825             case SC_NULL:
826             {
827                 TransferFailedException e =
828                     new TransferFailedException( "Failed to transfer file: " + url + reasonPhrase );
829                 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
830                 throw e;
831             }
832 
833             case HttpStatus.SC_FORBIDDEN:
834                 fireSessionConnectionRefused();
835                 throw new AuthorizationException( "Access denied to: " + url + reasonPhrase );
836 
837             case HttpStatus.SC_UNAUTHORIZED:
838                 fireSessionConnectionRefused();
839                 throw new AuthorizationException( "Not authorized" + reasonPhrase );
840 
841             case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
842                 fireSessionConnectionRefused();
843                 throw new AuthorizationException( "Not authorized by proxy" + reasonPhrase );
844 
845             case HttpStatus.SC_NOT_FOUND:
846                 throw new ResourceDoesNotExistException( "File: " + url + reasonPhrase );
847 
848                 // add more entries here
849             default:
850             {
851                 cleanupGetTransfer( resource );
852                 TransferFailedException e = new TransferFailedException(
853                     "Failed to transfer file: " + url + ". Return code is: " + statusCode + reasonPhrase );
854                 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
855                 throw e;
856             }
857         }
858 
859         InputStream is;
860 
861         Header contentLengthHeader = response.getFirstHeader( "Content-Length" );
862 
863         if ( contentLengthHeader != null )
864         {
865             try
866             {
867                 long contentLength = Integer.valueOf( contentLengthHeader.getValue() ).intValue();
868 
869                 resource.setContentLength( contentLength );
870             }
871             catch ( NumberFormatException e )
872             {
873                 fireTransferDebug(
874                     "error parsing content length header '" + contentLengthHeader.getValue() + "' " + e );
875             }
876         }
877 
878         Header lastModifiedHeader = response.getFirstHeader( "Last-Modified" );
879 
880         long lastModified = 0;
881 
882         if ( lastModifiedHeader != null )
883         {
884             try
885             {
886                 lastModified = DateUtils.parseDate( lastModifiedHeader.getValue() ).getTime();
887 
888                 resource.setLastModified( lastModified );
889             }
890             catch ( DateParseException e )
891             {
892                 fireTransferDebug( "Unable to parse last modified header" );
893             }
894 
895             fireTransferDebug( "last-modified = " + lastModifiedHeader.getValue() + " (" + lastModified + ")" );
896         }
897 
898         Header contentEncoding = response.getFirstHeader( "Content-Encoding" );
899         boolean isGZipped = contentEncoding == null ? false : "gzip".equalsIgnoreCase( contentEncoding.getValue() );
900 
901         try
902         {
903             is = response.getEntity().getContent();
904 
905             if ( isGZipped )
906             {
907                 is = new GZIPInputStream( is );
908             }
909         }
910         catch ( IOException e )
911         {
912             fireTransferError( resource, e, TransferEvent.REQUEST_GET );
913 
914             String msg =
915                 "Error occurred while retrieving from remote repository:" + getRepository() + ": " + e.getMessage();
916 
917             throw new TransferFailedException( msg, e );
918         }
919 
920         inputData.setInputStream( is );
921     }
922 
923     protected void cleanupGetTransfer( Resource resource )
924     {
925         if ( getMethod != null )
926         {
927             getMethod.abort();
928         }
929     }
930 
931 
932     @Override
933     public void putFromStream( InputStream stream, String destination )
934         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
935     {
936         putFromStream( stream, destination, -1, -1 );
937     }
938 
939     @Override
940     protected void putFromStream( InputStream stream, Resource resource )
941         throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException
942     {
943         putFromStream( stream, resource.getName(), -1, -1 );
944     }
945 
946     public Properties getHttpHeaders()
947     {
948         return httpHeaders;
949     }
950 
951     public void setHttpHeaders( Properties httpHeaders )
952     {
953         this.httpHeaders = httpHeaders;
954     }
955 
956     @Override
957     public void fillOutputData( OutputData outputData )
958         throws TransferFailedException
959     {
960         // no needed in this implementation but throw an Exception if used
961         throw new IllegalStateException( "this wagon http client must not use fillOutputData" );
962     }
963 }