View Javadoc

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