View Javadoc

1   /*
2    * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v 1.25 2004/05/13 04:03:25 mbecke Exp $
3    * $Revision: 1.25 $
4    * $Date: 2004/05/13 04:03:25 $
5    *
6    * ====================================================================
7    *
8    *  Copyright 1999-2004 The Apache Software Foundation
9    *
10   *  Licensed under the Apache License, Version 2.0 (the "License");
11   *  you may not use this file except in compliance with the License.
12   *  You may obtain a copy of the License at
13   *
14   *      http://www.apache.org/licenses/LICENSE-2.0
15   *
16   *  Unless required by applicable law or agreed to in writing, software
17   *  distributed under the License is distributed on an "AS IS" BASIS,
18   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   *  See the License for the specific language governing permissions and
20   *  limitations under the License.
21   * ====================================================================
22   *
23   * This software consists of voluntary contributions made by many
24   * individuals on behalf of the Apache Software Foundation.  For more
25   * information on the Apache Software Foundation, please see
26   * <http://www.apache.org/>.
27   *
28   */
29  
30  package org.apache.commons.httpclient;
31  
32  import java.io.IOException;
33  import java.util.HashSet;
34  import java.util.Map;
35  import java.util.Set;
36  
37  import org.apache.commons.httpclient.auth.AuthChallengeException;
38  import org.apache.commons.httpclient.auth.AuthChallengeParser;
39  import org.apache.commons.httpclient.auth.AuthChallengeProcessor;
40  import org.apache.commons.httpclient.auth.AuthScheme;
41  import org.apache.commons.httpclient.auth.AuthState;
42  import org.apache.commons.httpclient.auth.AuthenticationException;
43  import org.apache.commons.httpclient.auth.CredentialsProvider;
44  import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
45  import org.apache.commons.httpclient.auth.HttpAuthRealm;
46  import org.apache.commons.httpclient.auth.MalformedChallengeException;
47  import org.apache.commons.httpclient.params.HttpClientParams;
48  import org.apache.commons.httpclient.params.HttpConnectionParams;
49  import org.apache.commons.httpclient.params.HttpMethodParams;
50  import org.apache.commons.httpclient.params.HttpParams;
51  import org.apache.commons.logging.Log;
52  import org.apache.commons.logging.LogFactory;
53  
54  /***
55   * Handles the process of executing a method including authentication, redirection and retries.
56   * 
57   * @since 3.0
58   */
59  class HttpMethodDirector {
60  
61      /*** The www authenticate challange header. */
62      public static final String WWW_AUTH_CHALLENGE = "WWW-Authenticate";
63  
64      /*** The www authenticate response header. */
65      public static final String WWW_AUTH_RESP = "Authorization";
66  
67      /*** The proxy authenticate challange header. */
68      public static final String PROXY_AUTH_CHALLENGE = "Proxy-Authenticate";
69  
70      /*** The proxy authenticate response header. */
71      public static final String PROXY_AUTH_RESP = "Proxy-Authorization";
72  
73      private static final Log LOG = LogFactory.getLog(HttpMethodDirector.class);
74  
75      private ConnectMethod connectMethod;
76      
77      private HttpState state;
78  	
79      private HostConfiguration hostConfiguration;
80      
81      private HttpConnectionManager connectionManager;
82      
83      private HttpClientParams params;
84      
85      private HttpConnection conn;
86      
87      /*** A flag to indicate if the connection should be released after the method is executed. */
88      private boolean releaseConnection = false;
89  
90      private static final int AUTH_UNINITIATED = 0;
91      private static final int AUTH_PREEMPTIVE = 1;
92      private static final int AUTH_WWW_REQUIRED = 2;
93      private static final int AUTH_PROXY_REQUIRED = 3;
94      private static final int AUTH_NOT_REQUIRED = Integer.MAX_VALUE;
95  
96      /*** Actual state of authentication process */
97      private int authProcess = AUTH_UNINITIATED;
98      
99      /*** Authentication processor */
100     private AuthChallengeProcessor authProcessor = null;
101 
102     private Set redirectLocations = null; 
103     
104     public HttpMethodDirector(
105         final HttpConnectionManager connectionManager,
106         final HostConfiguration hostConfiguration,
107         final HttpClientParams params,
108         final HttpState state
109     ) {
110         super();
111         this.connectionManager = connectionManager;
112         this.hostConfiguration = hostConfiguration;
113         this.params = params;
114         this.state = state;
115         this.authProcessor = new AuthChallengeProcessor(this.params);
116     }
117     
118 	
119     /***
120      * Executes the method associated with this method director.
121      * 
122      * @throws IOException
123      * @throws HttpException
124      */
125     public void executeMethod(final HttpMethod method) throws IOException, HttpException {
126         if (method == null) {
127             throw new IllegalArgumentException("Method may not be null");
128         }
129         // Link all parameter collections to form the hierarchy:
130         // Global -> HttpClient -> HostConfiguration -> HttpMethod
131         this.hostConfiguration.getParams().setDefaults(this.params);
132         method.getParams().setDefaults(this.hostConfiguration.getParams());
133         try {
134             int maxRedirects = this.params.getIntParameter(HttpClientParams.MAX_REDIRECTS, 100);
135 
136             for (int redirectCount = 0;;) {
137 
138                 // make sure the connection we have is appropriate
139                 if (this.conn != null && !hostConfiguration.hostEquals(this.conn)) {
140                     this.conn.setLocked(false);
141                     this.conn.releaseConnection();
142                     this.conn = null;
143                 }
144         
145                 // get a connection, if we need one
146                 if (this.conn == null) {
147                     this.conn = connectionManager.getConnectionWithTimeout(
148                         hostConfiguration,
149                         this.params.getConnectionManagerTimeout() 
150                     );
151                     this.conn.setLocked(true);
152                     if (this.params.isAuthenticationPreemptive()
153                      || this.state.isAuthenticationPreemptive()) 
154                     {
155                         LOG.debug("Preemptively sending default basic credentials");
156                         this.authProcess = AUTH_PREEMPTIVE;
157                         method.getHostAuthState().setPreemptive();
158                         if (this.conn.isProxied()) {
159                             method.getProxyAuthState().setPreemptive();
160                         }
161                     }
162                 }
163                 authenticate(method);
164                 executeWithRetry(method);
165                 if (this.connectMethod != null) {
166                     fakeResponse(method);
167                     break;
168                 }
169                 
170                 boolean retry = false;
171                 if (isRedirectNeeded(method)) {
172                     if (processRedirectResponse(method)) {
173                         retry = true;
174                         ++redirectCount;
175                         if (redirectCount >= maxRedirects) {
176                             LOG.error("Narrowly avoided an infinite loop in execute");
177                             throw new RedirectException("Maximum redirects ("
178                                 + maxRedirects + ") exceeded");
179                         }
180                         if (LOG.isDebugEnabled()) {
181                             LOG.debug("Execute redirect " + redirectCount + " of " + maxRedirects);
182                         }
183                     }
184                 }
185                 if (isAuthenticationNeeded(method)) {
186                     if (processAuthenticationResponse(method)) {
187                         retry = true;
188                     }
189                 } else {
190                     this.authProcess = AUTH_NOT_REQUIRED;
191                 }
192                 if (!retry) {
193                     break;
194                 }
195                 // retry - close previous stream.  Caution - this causes
196                 // responseBodyConsumed to be called, which may also close the
197                 // connection.
198                 if (method.getResponseBodyAsStream() != null) {
199                     method.getResponseBodyAsStream().close();
200                 }
201 
202             } //end of retry loop
203         } finally {
204             if (this.conn != null) {
205                 this.conn.setLocked(false);
206             }
207             // If the response has been fully processed, return the connection
208             // to the pool.  Use this flag, rather than other tests (like
209             // responseStream == null), as subclasses, might reset the stream,
210             // for example, reading the entire response into a file and then
211             // setting the file as the stream.
212             if (
213                 (releaseConnection || method.getResponseBodyAsStream() == null) 
214                 && this.conn != null
215             ) {
216                 this.conn.releaseConnection();
217             }
218         }
219 
220     }
221 
222     
223     private void authenticate(final HttpMethod method) {
224         try {
225             authenticateProxy(method);
226             authenticateHost(method);
227         } catch (AuthenticationException e) {
228             LOG.error(e.getMessage(), e);
229         }
230     }
231 
232 
233     private boolean cleanAuthHeaders(final HttpMethod method, final String name) {
234         Header[] authheaders = method.getRequestHeaders(name);
235         boolean clean = true;
236         for (int i = 0; i < authheaders.length; i++) {
237             Header authheader = authheaders[i];
238             if (authheader.isAutogenerated()) {
239                 method.removeRequestHeader(authheader);
240             } else {
241                 clean = false;
242             }
243         }
244         return clean;
245     }
246     
247 
248     private void authenticateHost(final HttpMethod method) throws AuthenticationException {
249         // Clean up existing authentication headers
250         if (!cleanAuthHeaders(method, WWW_AUTH_RESP)) {
251             // User defined authentication header(s) present
252             return;
253         }
254         AuthScheme authscheme = method.getHostAuthState().getAuthScheme();
255         if (authscheme == null) {
256             return;
257         }
258         if ((this.authProcess == AUTH_WWW_REQUIRED) || (!authscheme.isConnectionBased())) {
259             String host = conn.getVirtualHost();
260             if (host == null) {
261                 host = conn.getHost();
262             }
263             int port = conn.getPort();
264             HttpAuthRealm realm = new HttpAuthRealm(
265                 host, port, 
266                 authscheme.getRealm(), 
267                 authscheme.getSchemeName());  
268             if (LOG.isDebugEnabled()) {
269                 LOG.debug("Authenticating with " + realm);
270             }
271             Credentials credentials = this.state.getCredentials(realm);
272             if (credentials != null) {
273                 String authstring = authscheme.authenticate(credentials, method);
274                 if (authstring != null) {
275                     method.addRequestHeader(new Header(WWW_AUTH_RESP, authstring, true));
276                 }
277             } else {
278                 LOG.warn("Required credentials not available");
279             }
280         }
281     }
282 
283 
284     private void authenticateProxy(final HttpMethod method) throws AuthenticationException {
285         // Clean up existing authentication headers
286         if (!cleanAuthHeaders(method, PROXY_AUTH_RESP)) {
287             // User defined authentication header(s) present
288             return;
289         }
290         AuthScheme authscheme = method.getProxyAuthState().getAuthScheme();
291         if (authscheme == null) {
292             return;
293         }
294         if ((this.authProcess == AUTH_PROXY_REQUIRED) || (!authscheme.isConnectionBased())) {
295             HttpAuthRealm realm = new HttpAuthRealm(
296                 conn.getProxyHost(), conn.getProxyPort(), 
297                 authscheme.getRealm(), 
298                 authscheme.getSchemeName());  
299             if (LOG.isDebugEnabled()) {
300                 LOG.debug("Authenticating with " + realm);
301             }
302             Credentials credentials = this.state.getProxyCredentials(realm);
303             if (credentials != null) {
304                 String authstring = authscheme.authenticate(credentials, method);
305                 if (authstring != null) {
306                     method.addRequestHeader(new Header(PROXY_AUTH_RESP, authstring, true));
307                 }
308             } else {
309                 LOG.warn("Required credentials not available");
310             }
311         }
312     }
313     
314     
315     /***
316      * Executes a method with the current hostConfiguration.
317      *
318      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions 
319      * can be recovered from.
320      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
321      * cannot be recovered from.
322      */
323     private void executeWithRetry(final HttpMethod method) 
324         throws IOException, HttpException {
325         
326         /*** How many times did this transparently handle a recoverable exception? */
327         int recoverableExceptionCount = 0;
328         int execCount = 0;
329         // TODO: how do we get requestSent?
330         boolean requestSent = false;
331         
332         // loop until the method is successfully processed, the retryHandler 
333         // returns false or a non-recoverable exception is thrown
334         try {
335             while (true) {
336                 execCount++;
337                 requestSent = false;
338 
339                 if (LOG.isTraceEnabled()) {
340                     LOG.trace("Attempt number " + execCount + " to process request");
341                 }
342                 if (!this.conn.isOpen()) {
343                     // this connection must be opened before it can be used
344                     // This has nothing to do with opening a secure tunnel
345                     this.conn.open();
346                     if (this.conn.isProxied() && this.conn.isSecure() 
347                     && !(method instanceof ConnectMethod)) {
348                         // we need to create a secure tunnel before we can execute the real method
349                         if (!executeConnect()) {
350                             // abort, the connect method failed
351                             return;
352                         }
353                     }
354                 }
355                 int timeout = 0;
356                 // see if a timeout is given for this method
357                 Object param = this.params.getParameter(HttpMethodParams.SO_TIMEOUT);
358                 if (param == null) {
359                     // if not, use the default value
360                     param = this.conn.getParams().getParameter(HttpConnectionParams.SO_TIMEOUT);
361                 }
362                 if (param != null) {
363                     timeout = ((Integer)param).intValue();
364                 }
365                 this.conn.setSocketTimeout(timeout);
366                 
367                 try {
368                     method.execute(state, this.conn);
369                     break;
370                 } catch (HttpRecoverableException httpre) {
371                     LOG.debug("Closing the connection.");
372                     this.conn.close();
373                     LOG.info("Recoverable exception caught when processing request");
374                     // update the recoverable exception count.
375                     recoverableExceptionCount++;
376                 
377                     // test if this method should be retried                
378                     MethodRetryHandler handler = method.getMethodRetryHandler();
379                     if (handler == null) {
380                         handler = new DefaultMethodRetryHandler();
381                     }
382                     if (!handler.retryMethod(
383                             method, 
384                             this.conn, 
385                             httpre, 
386                             execCount, 
387                             requestSent)
388                     ) {
389                         LOG.warn(
390                             "Recoverable exception caught but MethodRetryHandler.retryMethod() "
391                             + "returned false, rethrowing exception"
392                         );
393                         throw httpre;
394                     }
395                 }
396             }
397         } catch (IOException e) {
398             if (this.conn.isOpen()) {
399                 LOG.debug("Closing the connection.");
400                 this.conn.close();
401             }
402             releaseConnection = true;
403             throw e;
404         } catch (RuntimeException e) {
405             if (this.conn.isOpen()) {
406                 LOG.debug("Closing the connection.");
407                 this.conn.close();
408             }
409             releaseConnection = true;
410             throw e;
411         }
412     }
413     
414     /***
415      * Executes a ConnectMethod to establish a tunneled connection.
416      * 
417      * @return <code>true</code> if the connect was successful
418      * 
419      * @throws IOException
420      * @throws HttpException
421      */
422     private boolean executeConnect() 
423         throws IOException, HttpException {
424 
425         this.connectMethod = new ConnectMethod();
426         this.connectMethod.getParams().setDefaults(this.params);
427         
428         int code;
429         for (;;) {
430             try {
431                 authenticateProxy(this.connectMethod);
432             } catch (AuthenticationException e) {
433                 LOG.error(e.getMessage(), e);
434             }
435             executeWithRetry(this.connectMethod);
436             code = this.connectMethod.getStatusCode();
437             boolean retry = false;
438             if (code == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
439                 if (processAuthenticationResponse(this.connectMethod)) {
440                     retry = true;
441                 }
442             } else {
443                 this.authProcess = AUTH_NOT_REQUIRED;
444             }
445             if (!retry) {
446                 break;
447             }
448             if (this.connectMethod.getResponseBodyAsStream() != null) {
449                 this.connectMethod.getResponseBodyAsStream().close();
450             }
451         }
452         if ((code >= 200) && (code < 300)) {
453             this.conn.tunnelCreated();
454             // Drop the connect method, as it is no longer needed
455             this.connectMethod = null;
456             return true;
457         } else {
458             return false;
459         }
460     }
461 
462     /***
463      * Fake response
464      * @param method
465      * @return
466      */
467     
468     private void fakeResponse(final HttpMethod method)
469         throws IOException, HttpException {
470         // What is to follow is an ugly hack.
471         // I REALLY hate having to resort to such
472         // an appalling trick
473         // The only feasible solution is to split monolithic
474         // HttpMethod into HttpRequest/HttpResponse pair.
475         // That would allow to execute CONNECT method 
476         // behind the scene and return CONNECT HttpResponse 
477         // object in response to the original request that 
478         // contains the correct status line, headers & 
479         // response body.
480         LOG.debug("CONNECT failed, fake the response for the original method");
481         // Pass the status, headers and response stream to the wrapped
482         // method.
483         // To ensure that the connection is not released more than once
484         // this method is still responsible for releasing the connection. 
485         // This will happen when the response body is consumed, or when
486         // the wrapped method closes the response connection in 
487         // releaseConnection().
488         if (method instanceof HttpMethodBase) {
489             ((HttpMethodBase) method).fakeResponse(
490                 this.connectMethod.getStatusLine(),
491                 this.connectMethod.getResponseHeaderGroup(),
492                 this.connectMethod.getResponseBodyAsStream()
493             );
494             method.getProxyAuthState().setAuthScheme(
495                 this.connectMethod.getProxyAuthState().getAuthScheme());
496             this.connectMethod = null;
497         } else {
498             releaseConnection = true;
499             LOG.warn(
500                 "Unable to fake response on method as it is not derived from HttpMethodBase.");
501         }
502     }
503     
504 	/***
505 	 * Process the redirect response.
506      * 
507 	 * @return <code>true</code> if the redirect was successful
508 	 */
509 	private boolean processRedirectResponse(final HttpMethod method)
510      throws RedirectException  
511     {
512 		//get the location header to find out where to redirect to
513 		Header locationHeader = method.getResponseHeader("location");
514 		if (locationHeader == null) {
515 			// got a redirect response, but no location header
516 			LOG.error("Received redirect response " + method.getStatusCode()
517 					+ " but no location header");
518 			return false;
519 		}
520 		String location = locationHeader.getValue();
521 		if (LOG.isDebugEnabled()) {
522 			LOG.debug("Redirect requested to location '" + location + "'");
523 		}
524         
525 		//rfc2616 demands the location value be a complete URI
526 		//Location       = "Location" ":" absoluteURI
527 		URI redirectUri = null;
528 		URI currentUri = null;
529 
530 		try {
531 			currentUri = new URI(
532 				this.conn.getProtocol().getScheme(),
533 				null,
534                 this.conn.getHost(), 
535                 this.conn.getPort(), 
536 				method.getPath()
537 			);
538 			redirectUri = new URI(location, true);
539 			if (redirectUri.isRelativeURI()) {
540 				if (this.params.isParameterTrue(HttpClientParams.REJECT_RELATIVE_REDIRECT)) {
541 					LOG.warn("Relative redirect location '" + location + "' not allowed");
542 					return false;
543 				} else { 
544 					//location is incomplete, use current values for defaults
545 					LOG.debug("Redirect URI is not absolute - parsing as relative");
546 					redirectUri = new URI(currentUri, redirectUri);
547 				}
548 			}
549             method.setURI(redirectUri);
550             hostConfiguration.setHost(redirectUri);
551 		} catch (URIException e) {
552 			LOG.warn("Redirected location '" + location + "' is malformed");
553 			return false;
554 		}
555 
556         if (this.params.isParameterFalse(HttpClientParams.ALLOW_CIRCULAR_REDIRECTS)) {
557             if (this.redirectLocations == null) {
558                 this.redirectLocations = new HashSet();
559             }
560             this.redirectLocations.add(currentUri);
561             if (this.redirectLocations.contains(redirectUri)) {
562                 throw new RedirectException("Circular redirect to '" +
563                     redirectUri + "'");   
564             }
565         }
566 
567 		if (LOG.isDebugEnabled()) {
568 			LOG.debug("Redirecting from '" + currentUri.getEscapedURI()
569 				+ "' to '" + redirectUri.getEscapedURI());
570 		}
571 
572         //And finally invalidate the actual authentication scheme
573         method.getHostAuthState().invalidate(); 
574 		return true;
575 	}
576 
577 	/***
578 	 * Processes a response that requires authentication
579 	 *
580 	 * @param method the current {@link HttpMethod HTTP method}
581 	 *
582 	 * @return <tt>true</tt> if the authentication challenge can be responsed to,
583      *   (that is, at least one of the requested authentication scheme is supported, 
584      *   and matching credentials have been found), <tt>false</tt> otherwise.
585 	 */
586 	private boolean processAuthenticationResponse(final HttpMethod method) {
587 		LOG.trace("enter HttpMethodBase.processAuthenticationResponse("
588 			+ "HttpState, HttpConnection)");
589 
590 		try {
591             switch (method.getStatusCode()) {
592                 case HttpStatus.SC_UNAUTHORIZED:
593                     return processWWWAuthChallenge(method);
594                 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
595                     return processProxyAuthChallenge(method);
596                 default:
597                     return false;
598             }
599         } catch (Exception e) {
600             if (LOG.isErrorEnabled()) {
601                 LOG.error(e.getMessage(), e);
602             }
603             return false;
604         }
605 	}
606 
607     private boolean processWWWAuthChallenge(final HttpMethod method)
608         throws MalformedChallengeException, AuthenticationException  
609     {
610         AuthState authstate = method.getHostAuthState();
611         if (authstate.isPreemptive()) {
612             authstate.invalidate();
613         }
614         Map challenges = AuthChallengeParser.parseChallenges(
615             method.getResponseHeaders(WWW_AUTH_CHALLENGE));
616         if (challenges.isEmpty()) {
617             return false; 
618         }
619         AuthScheme authscheme = null;
620         try {
621             authscheme = this.authProcessor.processChallenge(authstate, challenges);
622         } catch (AuthChallengeException e) {
623             if (LOG.isWarnEnabled()) {
624                 LOG.warn(e.getMessage());
625             }
626         }
627         if (authscheme == null) {
628             return false;
629         }
630         String host = conn.getVirtualHost();
631         if (host == null) {
632             host = conn.getHost();
633         }
634         int port = conn.getPort();
635         HttpAuthRealm realm = new HttpAuthRealm(
636             host, port, 
637             authscheme.getRealm(), 
638             authscheme.getSchemeName());  
639 
640         if ((this.authProcess == AUTH_WWW_REQUIRED) && (authscheme.isComplete())) {
641             // Already tried and failed
642             Credentials credentials = promptForCredentials(
643                 authscheme, method.getParams(), realm);
644             if (credentials == null) {
645                 if (LOG.isInfoEnabled()) {
646                     LOG.info("Failure authenticating with " + realm);
647                 }
648                 return false;
649             } else {
650                 return true;
651             }
652         } else {
653             this.authProcess = AUTH_WWW_REQUIRED;
654                         
655             Credentials credentials = this.state.getCredentials(realm);
656             if (credentials == null) {
657                 credentials = promptForCredentials(
658                     authscheme, method.getParams(), realm);
659             }
660             if (credentials == null) {
661                 if (LOG.isInfoEnabled()) {
662                     LOG.info("No credentials available for the " + realm); 
663                 }
664                 return false;
665             } else {
666                 return true;
667             }
668         }
669     }
670 
671     private boolean processProxyAuthChallenge(final HttpMethod method)
672         throws MalformedChallengeException, AuthenticationException
673     {  
674         AuthState authstate = method.getProxyAuthState();
675         if (authstate.isPreemptive()) {
676             authstate.invalidate();
677         }
678         Map proxyChallenges = AuthChallengeParser.parseChallenges(
679             method.getResponseHeaders(PROXY_AUTH_CHALLENGE));
680         if (proxyChallenges.isEmpty()) {
681             return false; 
682         }
683         AuthScheme authscheme = null;
684         try {
685             authscheme = this.authProcessor.processChallenge(authstate, proxyChallenges);
686         } catch (AuthChallengeException e) {
687             if (LOG.isWarnEnabled()) {
688                 LOG.warn(e.getMessage());
689             }
690         }
691         if (authscheme == null) {
692             return false;
693         }
694         HttpAuthRealm realm = new HttpAuthRealm(
695             conn.getProxyHost(), conn.getProxyPort(), 
696             authscheme.getRealm(), 
697             authscheme.getSchemeName());  
698 
699         if ((this.authProcess == AUTH_PROXY_REQUIRED) && (authscheme.isComplete())) {
700             // Already tried and failed
701             Credentials credentials = promptForProxyCredentials(
702                 authscheme, method.getParams(), realm);
703             if (credentials == null) {
704                 if (LOG.isInfoEnabled()) {
705                     LOG.info("Failure authenticating with " + realm);
706                 }
707                 return false;
708             } else {
709                 return true;
710             }
711         } else {
712             this.authProcess = AUTH_PROXY_REQUIRED;
713 
714             Credentials credentials = this.state.getProxyCredentials(realm);
715             if (credentials == null) {
716                 credentials = promptForProxyCredentials(
717                     authscheme, method.getParams(), realm);
718             }
719             if (credentials == null) {
720                 if (LOG.isInfoEnabled()) {
721                     LOG.info("No proxy credentials available for the " + realm); 
722                 }
723                 return false;
724             } else {
725                 return true;
726             }
727         }
728     }
729 
730     /***
731      * Tests if the {@link HttpMethod method} requires a redirect to another location.
732      * 
733      * @param method HTTP method
734      * 
735      * @return boolean <tt>true</tt> if a retry is needed, <tt>false</tt> otherwise.
736      */
737 	private boolean isRedirectNeeded(final HttpMethod method) {
738 		switch (method.getStatusCode()) {
739 			case HttpStatus.SC_MOVED_TEMPORARILY:
740 			case HttpStatus.SC_MOVED_PERMANENTLY:
741 			case HttpStatus.SC_SEE_OTHER:
742 			case HttpStatus.SC_TEMPORARY_REDIRECT:
743 				LOG.debug("Redirect required");
744                 if (method.getFollowRedirects()) {
745                     return true;
746                 } else {
747                     LOG.info("Redirect requested but followRedirects is "
748                             + "disabled");
749                     return false;
750                 }
751 			default:
752 				return false;
753 		} //end of switch
754 	}
755 
756     /***
757      * Tests if the {@link HttpMethod method} requires authentication.
758      * 
759      * @param method HTTP method
760      * 
761      * @return boolean <tt>true</tt> if a retry is needed, <tt>false</tt> otherwise.
762      */
763     private boolean isAuthenticationNeeded(final HttpMethod method) {
764         switch (method.getStatusCode()) {
765             case HttpStatus.SC_UNAUTHORIZED:
766             case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
767                 LOG.debug("Authorization required");
768                 if (method.getDoAuthentication()) { //process authentication response
769                     return true;
770                 } else { //let the client handle the authenticaiton
771                     LOG.info("Authentication requested but doAuthentication is "
772                             + "disabled");
773                     return false;
774                 }
775             default:
776                 return false;
777         } //end of switch
778     }
779 
780     private Credentials promptForCredentials(
781         final AuthScheme authScheme,
782         final HttpParams params, 
783         final HttpAuthRealm realm)
784     {
785         Credentials creds = null;
786         CredentialsProvider credProvider = 
787             (CredentialsProvider)params.getParameter(CredentialsProvider.PROVIDER);
788         if (credProvider != null) {
789             try {
790                 creds = credProvider.getCredentials(
791                     authScheme, realm.getHost(), realm.getPort(), false);
792             } catch (CredentialsNotAvailableException e) {
793                 LOG.warn(e.getMessage());
794             }
795             if (creds != null) {
796                 this.state.setCredentials(realm, creds);
797                 if (LOG.isDebugEnabled()) {
798                     LOG.debug("New credentials for " + realm);
799                 }
800             }
801         }
802         return creds;
803     }
804 
805     private Credentials promptForProxyCredentials(
806         final AuthScheme authScheme,
807         final HttpParams params,
808         final HttpAuthRealm realm) 
809     {
810         Credentials creds = null;
811         CredentialsProvider credProvider = 
812             (CredentialsProvider)params.getParameter(CredentialsProvider.PROVIDER);
813         if (credProvider != null) {
814             try {
815                 creds = credProvider.getCredentials(
816                     authScheme, realm.getHost(), realm.getPort(), true);
817             } catch (CredentialsNotAvailableException e) {
818                 LOG.warn(e.getMessage());
819             }
820             if (creds != null) {
821                 this.state.setProxyCredentials(realm, creds);
822                 if (LOG.isDebugEnabled()) {
823                     LOG.debug("New proxy credentials for " + realm);
824                 }
825             }
826         }
827         return creds;
828     }
829 
830     /***
831      * @return
832      */
833     public HostConfiguration getHostConfiguration() {
834         return hostConfiguration;
835     }
836 
837     /***
838      * @return
839      */
840     public HttpState getState() {
841         return state;
842     }
843 
844     /***
845      * @return
846      */
847     public HttpConnectionManager getConnectionManager() {
848         return connectionManager;
849     }
850 
851     /***
852      * @return
853      */
854     public HttpParams getParams() {
855         return this.params;
856     }
857 }