001: /*
002: * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v 1.34 2005/01/14 19:40:39 olegk Exp $
003: * $Revision: 486658 $
004: * $Date: 2006-12-13 15:05:50 +0100 (Wed, 13 Dec 2006) $
005: *
006: * ====================================================================
007: *
008: * Licensed to the Apache Software Foundation (ASF) under one or more
009: * contributor license agreements. See the NOTICE file distributed with
010: * this work for additional information regarding copyright ownership.
011: * The ASF licenses this file to You under the Apache License, Version 2.0
012: * (the "License"); you may not use this file except in compliance with
013: * the License. You may obtain a copy of the License at
014: *
015: * http://www.apache.org/licenses/LICENSE-2.0
016: *
017: * Unless required by applicable law or agreed to in writing, software
018: * distributed under the License is distributed on an "AS IS" BASIS,
019: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020: * See the License for the specific language governing permissions and
021: * limitations under the License.
022: * ====================================================================
023: *
024: * This software consists of voluntary contributions made by many
025: * individuals on behalf of the Apache Software Foundation. For more
026: * information on the Apache Software Foundation, please see
027: * <http://www.apache.org/>.
028: *
029: */
030:
031: package org.apache.commons.httpclient;
032:
033: import java.io.IOException;
034: import java.util.Collection;
035: import java.util.HashSet;
036: import java.util.Iterator;
037: import java.util.Map;
038: import java.util.Set;
039:
040: import org.apache.commons.httpclient.auth.AuthChallengeException;
041: import org.apache.commons.httpclient.auth.AuthChallengeParser;
042: import org.apache.commons.httpclient.auth.AuthChallengeProcessor;
043: import org.apache.commons.httpclient.auth.AuthScheme;
044: import org.apache.commons.httpclient.auth.AuthState;
045: import org.apache.commons.httpclient.auth.AuthenticationException;
046: import org.apache.commons.httpclient.auth.CredentialsProvider;
047: import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
048: import org.apache.commons.httpclient.auth.AuthScope;
049: import org.apache.commons.httpclient.auth.MalformedChallengeException;
050: import org.apache.commons.httpclient.params.HostParams;
051: import org.apache.commons.httpclient.params.HttpClientParams;
052: import org.apache.commons.httpclient.params.HttpConnectionParams;
053: import org.apache.commons.httpclient.params.HttpMethodParams;
054: import org.apache.commons.httpclient.params.HttpParams;
055: import org.apache.commons.logging.Log;
056: import org.apache.commons.logging.LogFactory;
057:
058: /**
059: * Handles the process of executing a method including authentication, redirection and retries.
060: *
061: * @since 3.0
062: */
063: class HttpMethodDirector {
064:
065: /** The www authenticate challange header. */
066: public static final String WWW_AUTH_CHALLENGE = "WWW-Authenticate";
067:
068: /** The www authenticate response header. */
069: public static final String WWW_AUTH_RESP = "Authorization";
070:
071: /** The proxy authenticate challange header. */
072: public static final String PROXY_AUTH_CHALLENGE = "Proxy-Authenticate";
073:
074: /** The proxy authenticate response header. */
075: public static final String PROXY_AUTH_RESP = "Proxy-Authorization";
076:
077: private static final Log LOG = LogFactory
078: .getLog(HttpMethodDirector.class);
079:
080: private ConnectMethod connectMethod;
081:
082: private HttpState state;
083:
084: private HostConfiguration hostConfiguration;
085:
086: private HttpConnectionManager connectionManager;
087:
088: private HttpClientParams params;
089:
090: private HttpConnection conn;
091:
092: /** A flag to indicate if the connection should be released after the method is executed. */
093: private boolean releaseConnection = false;
094:
095: /** Authentication processor */
096: private AuthChallengeProcessor authProcessor = null;
097:
098: private Set redirectLocations = null;
099:
100: public HttpMethodDirector(
101: final HttpConnectionManager connectionManager,
102: final HostConfiguration hostConfiguration,
103: final HttpClientParams params, final HttpState state) {
104: super ();
105: this .connectionManager = connectionManager;
106: this .hostConfiguration = hostConfiguration;
107: this .params = params;
108: this .state = state;
109: this .authProcessor = new AuthChallengeProcessor(this .params);
110: }
111:
112: /**
113: * Executes the method associated with this method director.
114: *
115: * @throws IOException
116: * @throws HttpException
117: */
118: public void executeMethod(final HttpMethod method)
119: throws IOException, HttpException {
120: if (method == null) {
121: throw new IllegalArgumentException("Method may not be null");
122: }
123: // Link all parameter collections to form the hierarchy:
124: // Global -> HttpClient -> HostConfiguration -> HttpMethod
125: this .hostConfiguration.getParams().setDefaults(this .params);
126: method.getParams().setDefaults(
127: this .hostConfiguration.getParams());
128:
129: // Generate default request headers
130: Collection defaults = (Collection) this .hostConfiguration
131: .getParams().getParameter(HostParams.DEFAULT_HEADERS);
132: if (defaults != null) {
133: Iterator i = defaults.iterator();
134: while (i.hasNext()) {
135: method.addRequestHeader((Header) i.next());
136: }
137: }
138:
139: try {
140: int maxRedirects = this .params.getIntParameter(
141: HttpClientParams.MAX_REDIRECTS, 100);
142:
143: for (int redirectCount = 0;;) {
144:
145: // make sure the connection we have is appropriate
146: if (this .conn != null
147: && !hostConfiguration.hostEquals(this .conn)) {
148: this .conn.setLocked(false);
149: this .conn.releaseConnection();
150: this .conn = null;
151: }
152:
153: // get a connection, if we need one
154: if (this .conn == null) {
155: this .conn = connectionManager
156: .getConnectionWithTimeout(
157: hostConfiguration,
158: this .params
159: .getConnectionManagerTimeout());
160: this .conn.setLocked(true);
161: if (this .params.isAuthenticationPreemptive()
162: || this .state.isAuthenticationPreemptive()) {
163: LOG
164: .debug("Preemptively sending default basic credentials");
165: method.getHostAuthState().setPreemptive();
166: method.getHostAuthState()
167: .setAuthAttempted(true);
168: if (this .conn.isProxied()
169: && !this .conn.isSecure()) {
170: method.getProxyAuthState().setPreemptive();
171: method.getProxyAuthState()
172: .setAuthAttempted(true);
173: }
174: }
175: }
176: authenticate(method);
177: executeWithRetry(method);
178: if (this .connectMethod != null) {
179: fakeResponse(method);
180: break;
181: }
182:
183: boolean retry = false;
184: if (isRedirectNeeded(method)) {
185: if (processRedirectResponse(method)) {
186: retry = true;
187: ++redirectCount;
188: if (redirectCount >= maxRedirects) {
189: LOG
190: .error("Narrowly avoided an infinite loop in execute");
191: throw new RedirectException(
192: "Maximum redirects ("
193: + maxRedirects
194: + ") exceeded");
195: }
196: if (LOG.isDebugEnabled()) {
197: LOG.debug("Execute redirect "
198: + redirectCount + " of "
199: + maxRedirects);
200: }
201: }
202: }
203: if (isAuthenticationNeeded(method)) {
204: if (processAuthenticationResponse(method)) {
205: LOG.debug("Retry authentication");
206: retry = true;
207: }
208: }
209: if (!retry) {
210: break;
211: }
212: // retry - close previous stream. Caution - this causes
213: // responseBodyConsumed to be called, which may also close the
214: // connection.
215: if (method.getResponseBodyAsStream() != null) {
216: method.getResponseBodyAsStream().close();
217: }
218:
219: } //end of retry loop
220: } finally {
221: if (this .conn != null) {
222: this .conn.setLocked(false);
223: }
224: // If the response has been fully processed, return the connection
225: // to the pool. Use this flag, rather than other tests (like
226: // responseStream == null), as subclasses, might reset the stream,
227: // for example, reading the entire response into a file and then
228: // setting the file as the stream.
229: if ((releaseConnection || method.getResponseBodyAsStream() == null)
230: && this .conn != null) {
231: this .conn.releaseConnection();
232: }
233: }
234:
235: }
236:
237: private void authenticate(final HttpMethod method) {
238: try {
239: if (this .conn.isProxied() && !this .conn.isSecure()) {
240: authenticateProxy(method);
241: }
242: authenticateHost(method);
243: } catch (AuthenticationException e) {
244: LOG.error(e.getMessage(), e);
245: }
246: }
247:
248: private boolean cleanAuthHeaders(final HttpMethod method,
249: final String name) {
250: Header[] authheaders = method.getRequestHeaders(name);
251: boolean clean = true;
252: for (int i = 0; i < authheaders.length; i++) {
253: Header authheader = authheaders[i];
254: if (authheader.isAutogenerated()) {
255: method.removeRequestHeader(authheader);
256: } else {
257: clean = false;
258: }
259: }
260: return clean;
261: }
262:
263: private void authenticateHost(final HttpMethod method)
264: throws AuthenticationException {
265: // Clean up existing authentication headers
266: if (!cleanAuthHeaders(method, WWW_AUTH_RESP)) {
267: // User defined authentication header(s) present
268: return;
269: }
270: AuthState authstate = method.getHostAuthState();
271: AuthScheme authscheme = authstate.getAuthScheme();
272: if (authscheme == null) {
273: return;
274: }
275: if (authstate.isAuthRequested()
276: || !authscheme.isConnectionBased()) {
277: String host = method.getParams().getVirtualHost();
278: if (host == null) {
279: host = conn.getHost();
280: }
281: int port = conn.getPort();
282: AuthScope authscope = new AuthScope(host, port, authscheme
283: .getRealm(), authscheme.getSchemeName());
284: if (LOG.isDebugEnabled()) {
285: LOG.debug("Authenticating with " + authscope);
286: }
287: Credentials credentials = this .state
288: .getCredentials(authscope);
289: if (credentials != null) {
290: String authstring = authscheme.authenticate(
291: credentials, method);
292: if (authstring != null) {
293: method.addRequestHeader(new Header(WWW_AUTH_RESP,
294: authstring, true));
295: }
296: } else {
297: if (LOG.isWarnEnabled()) {
298: LOG.warn("Required credentials not available for "
299: + authscope);
300: if (method.getHostAuthState().isPreemptive()) {
301: LOG
302: .warn("Preemptive authentication requested but no default "
303: + "credentials available");
304: }
305: }
306: }
307: }
308: }
309:
310: private void authenticateProxy(final HttpMethod method)
311: throws AuthenticationException {
312: // Clean up existing authentication headers
313: if (!cleanAuthHeaders(method, PROXY_AUTH_RESP)) {
314: // User defined authentication header(s) present
315: return;
316: }
317: AuthState authstate = method.getProxyAuthState();
318: AuthScheme authscheme = authstate.getAuthScheme();
319: if (authscheme == null) {
320: return;
321: }
322: if (authstate.isAuthRequested()
323: || !authscheme.isConnectionBased()) {
324: AuthScope authscope = new AuthScope(conn.getProxyHost(),
325: conn.getProxyPort(), authscheme.getRealm(),
326: authscheme.getSchemeName());
327: if (LOG.isDebugEnabled()) {
328: LOG.debug("Authenticating with " + authscope);
329: }
330: Credentials credentials = this .state
331: .getProxyCredentials(authscope);
332: if (credentials != null) {
333: String authstring = authscheme.authenticate(
334: credentials, method);
335: if (authstring != null) {
336: method.addRequestHeader(new Header(PROXY_AUTH_RESP,
337: authstring, true));
338: }
339: } else {
340: if (LOG.isWarnEnabled()) {
341: LOG
342: .warn("Required proxy credentials not available for "
343: + authscope);
344: if (method.getProxyAuthState().isPreemptive()) {
345: LOG
346: .warn("Preemptive authentication requested but no default "
347: + "proxy credentials available");
348: }
349: }
350: }
351: }
352: }
353:
354: /**
355: * Applies connection parameters specified for a given method
356: *
357: * @param method HTTP method
358: *
359: * @throws IOException if an I/O occurs setting connection parameters
360: */
361: private void applyConnectionParams(final HttpMethod method)
362: throws IOException {
363: int timeout = 0;
364: // see if a timeout is given for this method
365: Object param = method.getParams().getParameter(
366: HttpMethodParams.SO_TIMEOUT);
367: if (param == null) {
368: // if not, use the default value
369: param = this .conn.getParams().getParameter(
370: HttpConnectionParams.SO_TIMEOUT);
371: }
372: if (param != null) {
373: timeout = ((Integer) param).intValue();
374: }
375: this .conn.setSocketTimeout(timeout);
376: }
377:
378: /**
379: * Executes a method with the current hostConfiguration.
380: *
381: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
382: * can be recovered from.
383: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
384: * cannot be recovered from.
385: */
386: private void executeWithRetry(final HttpMethod method)
387: throws IOException, HttpException {
388:
389: /** How many times did this transparently handle a recoverable exception? */
390: int execCount = 0;
391: // loop until the method is successfully processed, the retryHandler
392: // returns false or a non-recoverable exception is thrown
393: try {
394: while (true) {
395: execCount++;
396: try {
397:
398: if (LOG.isTraceEnabled()) {
399: LOG.trace("Attempt number " + execCount
400: + " to process request");
401: }
402: if (this .conn.getParams().isStaleCheckingEnabled()) {
403: this .conn.closeIfStale();
404: }
405: if (!this .conn.isOpen()) {
406: // this connection must be opened before it can be used
407: // This has nothing to do with opening a secure tunnel
408: this .conn.open();
409: if (this .conn.isProxied()
410: && this .conn.isSecure()
411: && !(method instanceof ConnectMethod)) {
412: // we need to create a secure tunnel before we can execute the real method
413: if (!executeConnect()) {
414: // abort, the connect method failed
415: return;
416: }
417: }
418: }
419: applyConnectionParams(method);
420: method.execute(state, this .conn);
421: break;
422: } catch (HttpException e) {
423: // filter out protocol exceptions which cannot be recovered from
424: throw e;
425: } catch (IOException e) {
426: LOG.debug("Closing the connection.");
427: this .conn.close();
428: // test if this method should be retried
429: // ========================================
430: // this code is provided for backward compatibility with 2.0
431: // will be removed in the next major release
432: if (method instanceof HttpMethodBase) {
433: MethodRetryHandler handler = ((HttpMethodBase) method)
434: .getMethodRetryHandler();
435: if (handler != null) {
436: if (!handler.retryMethod(method, this .conn,
437: new HttpRecoverableException(e
438: .getMessage()), execCount,
439: method.isRequestSent())) {
440: LOG
441: .debug("Method retry handler returned false. "
442: + "Automatic recovery will not be attempted");
443: throw e;
444: }
445: }
446: }
447: // ========================================
448: HttpMethodRetryHandler handler = (HttpMethodRetryHandler) method
449: .getParams().getParameter(
450: HttpMethodParams.RETRY_HANDLER);
451: if (handler == null) {
452: handler = new DefaultHttpMethodRetryHandler();
453: }
454: if (!handler.retryMethod(method, e, execCount)) {
455: LOG
456: .debug("Method retry handler returned false. "
457: + "Automatic recovery will not be attempted");
458: throw e;
459: }
460: if (LOG.isInfoEnabled()) {
461: LOG.info("I/O exception ("
462: + e.getClass().getName()
463: + ") caught when processing request: "
464: + e.getMessage());
465: }
466: if (LOG.isDebugEnabled()) {
467: LOG.debug(e.getMessage(), e);
468: }
469: LOG.info("Retrying request");
470: }
471: }
472: } catch (IOException e) {
473: if (this .conn.isOpen()) {
474: LOG.debug("Closing the connection.");
475: this .conn.close();
476: }
477: releaseConnection = true;
478: throw e;
479: } catch (RuntimeException e) {
480: if (this .conn.isOpen()) {
481: LOG.debug("Closing the connection.");
482: this .conn.close();
483: }
484: releaseConnection = true;
485: throw e;
486: }
487: }
488:
489: /**
490: * Executes a ConnectMethod to establish a tunneled connection.
491: *
492: * @return <code>true</code> if the connect was successful
493: *
494: * @throws IOException
495: * @throws HttpException
496: */
497: private boolean executeConnect() throws IOException, HttpException {
498:
499: this .connectMethod = new ConnectMethod(this .hostConfiguration);
500: this .connectMethod.getParams().setDefaults(
501: this .hostConfiguration.getParams());
502:
503: int code;
504: for (;;) {
505: if (!this .conn.isOpen()) {
506: this .conn.open();
507: }
508: if (this .params.isAuthenticationPreemptive()
509: || this .state.isAuthenticationPreemptive()) {
510: LOG
511: .debug("Preemptively sending default basic credentials");
512: this .connectMethod.getProxyAuthState().setPreemptive();
513: this .connectMethod.getProxyAuthState()
514: .setAuthAttempted(true);
515: }
516: try {
517: authenticateProxy(this .connectMethod);
518: } catch (AuthenticationException e) {
519: LOG.error(e.getMessage(), e);
520: }
521: applyConnectionParams(this .connectMethod);
522: this .connectMethod.execute(state, this .conn);
523: code = this .connectMethod.getStatusCode();
524: boolean retry = false;
525: AuthState authstate = this .connectMethod
526: .getProxyAuthState();
527: authstate
528: .setAuthRequested(code == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED);
529: if (authstate.isAuthRequested()) {
530: if (processAuthenticationResponse(this .connectMethod)) {
531: retry = true;
532: }
533: }
534: if (!retry) {
535: break;
536: }
537: if (this .connectMethod.getResponseBodyAsStream() != null) {
538: this .connectMethod.getResponseBodyAsStream().close();
539: }
540: }
541: if ((code >= 200) && (code < 300)) {
542: this .conn.tunnelCreated();
543: // Drop the connect method, as it is no longer needed
544: this .connectMethod = null;
545: return true;
546: } else {
547: this .conn.close();
548: return false;
549: }
550: }
551:
552: /**
553: * Fake response
554: * @param method
555: * @return
556: */
557:
558: private void fakeResponse(final HttpMethod method)
559: throws IOException, HttpException {
560: // What is to follow is an ugly hack.
561: // I REALLY hate having to resort to such
562: // an appalling trick
563: // The only feasible solution is to split monolithic
564: // HttpMethod into HttpRequest/HttpResponse pair.
565: // That would allow to execute CONNECT method
566: // behind the scene and return CONNECT HttpResponse
567: // object in response to the original request that
568: // contains the correct status line, headers &
569: // response body.
570: LOG
571: .debug("CONNECT failed, fake the response for the original method");
572: // Pass the status, headers and response stream to the wrapped
573: // method.
574: // To ensure that the connection is not released more than once
575: // this method is still responsible for releasing the connection.
576: // This will happen when the response body is consumed, or when
577: // the wrapped method closes the response connection in
578: // releaseConnection().
579: if (method instanceof HttpMethodBase) {
580: ((HttpMethodBase) method).fakeResponse(this .connectMethod
581: .getStatusLine(), this .connectMethod
582: .getResponseHeaderGroup(), this .connectMethod
583: .getResponseBodyAsStream());
584: method.getProxyAuthState().setAuthScheme(
585: this .connectMethod.getProxyAuthState()
586: .getAuthScheme());
587: this .connectMethod = null;
588: } else {
589: releaseConnection = true;
590: LOG
591: .warn("Unable to fake response on method as it is not derived from HttpMethodBase.");
592: }
593: }
594:
595: /**
596: * Process the redirect response.
597: *
598: * @return <code>true</code> if the redirect was successful
599: */
600: private boolean processRedirectResponse(final HttpMethod method)
601: throws RedirectException {
602: //get the location header to find out where to redirect to
603: Header locationHeader = method.getResponseHeader("location");
604: if (locationHeader == null) {
605: // got a redirect response, but no location header
606: LOG.error("Received redirect response "
607: + method.getStatusCode()
608: + " but no location header");
609: return false;
610: }
611: String location = locationHeader.getValue();
612: if (LOG.isDebugEnabled()) {
613: LOG.debug("Redirect requested to location '" + location
614: + "'");
615: }
616:
617: //rfc2616 demands the location value be a complete URI
618: //Location = "Location" ":" absoluteURI
619: URI redirectUri = null;
620: URI currentUri = null;
621:
622: try {
623: currentUri = new URI(this .conn.getProtocol().getScheme(),
624: null, this .conn.getHost(), this .conn.getPort(),
625: method.getPath());
626:
627: String charset = method.getParams().getUriCharset();
628: redirectUri = new URI(location, true, charset);
629:
630: if (redirectUri.isRelativeURI()) {
631: if (this .params
632: .isParameterTrue(HttpClientParams.REJECT_RELATIVE_REDIRECT)) {
633: LOG.warn("Relative redirect location '" + location
634: + "' not allowed");
635: return false;
636: } else {
637: //location is incomplete, use current values for defaults
638: LOG
639: .debug("Redirect URI is not absolute - parsing as relative");
640: redirectUri = new URI(currentUri, redirectUri);
641: }
642: } else {
643: // Reset the default params
644: method.getParams().setDefaults(this .params);
645: }
646: method.setURI(redirectUri);
647: hostConfiguration.setHost(redirectUri);
648: } catch (URIException ex) {
649: throw new InvalidRedirectLocationException(
650: "Invalid redirect location: " + location, location,
651: ex);
652: }
653:
654: if (this .params
655: .isParameterFalse(HttpClientParams.ALLOW_CIRCULAR_REDIRECTS)) {
656: if (this .redirectLocations == null) {
657: this .redirectLocations = new HashSet();
658: }
659: this .redirectLocations.add(currentUri);
660: try {
661: if (redirectUri.hasQuery()) {
662: redirectUri.setQuery(null);
663: }
664: } catch (URIException e) {
665: // Should never happen
666: return false;
667: }
668:
669: if (this .redirectLocations.contains(redirectUri)) {
670: throw new CircularRedirectException(
671: "Circular redirect to '" + redirectUri + "'");
672: }
673: }
674:
675: if (LOG.isDebugEnabled()) {
676: LOG.debug("Redirecting from '" + currentUri.getEscapedURI()
677: + "' to '" + redirectUri.getEscapedURI());
678: }
679: //And finally invalidate the actual authentication scheme
680: method.getHostAuthState().invalidate();
681: return true;
682: }
683:
684: /**
685: * Processes a response that requires authentication
686: *
687: * @param method the current {@link HttpMethod HTTP method}
688: *
689: * @return <tt>true</tt> if the authentication challenge can be responsed to,
690: * (that is, at least one of the requested authentication scheme is supported,
691: * and matching credentials have been found), <tt>false</tt> otherwise.
692: */
693: private boolean processAuthenticationResponse(
694: final HttpMethod method) {
695: LOG.trace("enter HttpMethodBase.processAuthenticationResponse("
696: + "HttpState, HttpConnection)");
697:
698: try {
699: switch (method.getStatusCode()) {
700: case HttpStatus.SC_UNAUTHORIZED:
701: return processWWWAuthChallenge(method);
702: case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
703: return processProxyAuthChallenge(method);
704: default:
705: return false;
706: }
707: } catch (Exception e) {
708: if (LOG.isErrorEnabled()) {
709: LOG.error(e.getMessage(), e);
710: }
711: return false;
712: }
713: }
714:
715: private boolean processWWWAuthChallenge(final HttpMethod method)
716: throws MalformedChallengeException, AuthenticationException {
717: AuthState authstate = method.getHostAuthState();
718: Map challenges = AuthChallengeParser.parseChallenges(method
719: .getResponseHeaders(WWW_AUTH_CHALLENGE));
720: if (challenges.isEmpty()) {
721: LOG.debug("Authentication challenge(s) not found");
722: return false;
723: }
724: AuthScheme authscheme = null;
725: try {
726: authscheme = this .authProcessor.processChallenge(authstate,
727: challenges);
728: } catch (AuthChallengeException e) {
729: if (LOG.isWarnEnabled()) {
730: LOG.warn(e.getMessage());
731: }
732: }
733: if (authscheme == null) {
734: return false;
735: }
736: String host = method.getParams().getVirtualHost();
737: if (host == null) {
738: host = conn.getHost();
739: }
740: int port = conn.getPort();
741: AuthScope authscope = new AuthScope(host, port, authscheme
742: .getRealm(), authscheme.getSchemeName());
743:
744: if (LOG.isDebugEnabled()) {
745: LOG.debug("Authentication scope: " + authscope);
746: }
747: if (authstate.isAuthAttempted() && authscheme.isComplete()) {
748: // Already tried and failed
749: Credentials credentials = promptForCredentials(authscheme,
750: method.getParams(), authscope);
751: if (credentials == null) {
752: if (LOG.isInfoEnabled()) {
753: LOG
754: .info("Failure authenticating with "
755: + authscope);
756: }
757: return false;
758: } else {
759: return true;
760: }
761: } else {
762: authstate.setAuthAttempted(true);
763: Credentials credentials = this .state
764: .getCredentials(authscope);
765: if (credentials == null) {
766: credentials = promptForCredentials(authscheme, method
767: .getParams(), authscope);
768: }
769: if (credentials == null) {
770: if (LOG.isInfoEnabled()) {
771: LOG.info("No credentials available for "
772: + authscope);
773: }
774: return false;
775: } else {
776: return true;
777: }
778: }
779: }
780:
781: private boolean processProxyAuthChallenge(final HttpMethod method)
782: throws MalformedChallengeException, AuthenticationException {
783: AuthState authstate = method.getProxyAuthState();
784: Map proxyChallenges = AuthChallengeParser
785: .parseChallenges(method
786: .getResponseHeaders(PROXY_AUTH_CHALLENGE));
787: if (proxyChallenges.isEmpty()) {
788: LOG.debug("Proxy authentication challenge(s) not found");
789: return false;
790: }
791: AuthScheme authscheme = null;
792: try {
793: authscheme = this .authProcessor.processChallenge(authstate,
794: proxyChallenges);
795: } catch (AuthChallengeException e) {
796: if (LOG.isWarnEnabled()) {
797: LOG.warn(e.getMessage());
798: }
799: }
800: if (authscheme == null) {
801: return false;
802: }
803: AuthScope authscope = new AuthScope(conn.getProxyHost(), conn
804: .getProxyPort(), authscheme.getRealm(), authscheme
805: .getSchemeName());
806:
807: if (LOG.isDebugEnabled()) {
808: LOG.debug("Proxy authentication scope: " + authscope);
809: }
810: if (authstate.isAuthAttempted() && authscheme.isComplete()) {
811: // Already tried and failed
812: Credentials credentials = promptForProxyCredentials(
813: authscheme, method.getParams(), authscope);
814: if (credentials == null) {
815: if (LOG.isInfoEnabled()) {
816: LOG
817: .info("Failure authenticating with "
818: + authscope);
819: }
820: return false;
821: } else {
822: return true;
823: }
824: } else {
825: authstate.setAuthAttempted(true);
826: Credentials credentials = this .state
827: .getProxyCredentials(authscope);
828: if (credentials == null) {
829: credentials = promptForProxyCredentials(authscheme,
830: method.getParams(), authscope);
831: }
832: if (credentials == null) {
833: if (LOG.isInfoEnabled()) {
834: LOG.info("No credentials available for "
835: + authscope);
836: }
837: return false;
838: } else {
839: return true;
840: }
841: }
842: }
843:
844: /**
845: * Tests if the {@link HttpMethod method} requires a redirect to another location.
846: *
847: * @param method HTTP method
848: *
849: * @return boolean <tt>true</tt> if a retry is needed, <tt>false</tt> otherwise.
850: */
851: private boolean isRedirectNeeded(final HttpMethod method) {
852: switch (method.getStatusCode()) {
853: case HttpStatus.SC_MOVED_TEMPORARILY:
854: case HttpStatus.SC_MOVED_PERMANENTLY:
855: case HttpStatus.SC_SEE_OTHER:
856: case HttpStatus.SC_TEMPORARY_REDIRECT:
857: LOG.debug("Redirect required");
858: if (method.getFollowRedirects()) {
859: return true;
860: } else {
861: return false;
862: }
863: default:
864: return false;
865: } //end of switch
866: }
867:
868: /**
869: * Tests if the {@link HttpMethod method} requires authentication.
870: *
871: * @param method HTTP method
872: *
873: * @return boolean <tt>true</tt> if a retry is needed, <tt>false</tt> otherwise.
874: */
875: private boolean isAuthenticationNeeded(final HttpMethod method) {
876: method.getHostAuthState().setAuthRequested(
877: method.getStatusCode() == HttpStatus.SC_UNAUTHORIZED);
878: method
879: .getProxyAuthState()
880: .setAuthRequested(
881: method.getStatusCode() == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED);
882: if (method.getHostAuthState().isAuthRequested()
883: || method.getProxyAuthState().isAuthRequested()) {
884: LOG.debug("Authorization required");
885: if (method.getDoAuthentication()) { //process authentication response
886: return true;
887: } else { //let the client handle the authenticaiton
888: LOG
889: .info("Authentication requested but doAuthentication is "
890: + "disabled");
891: return false;
892: }
893: } else {
894: return false;
895: }
896: }
897:
898: private Credentials promptForCredentials(
899: final AuthScheme authScheme, final HttpParams params,
900: final AuthScope authscope) {
901: LOG.debug("Credentials required");
902: Credentials creds = null;
903: CredentialsProvider credProvider = (CredentialsProvider) params
904: .getParameter(CredentialsProvider.PROVIDER);
905: if (credProvider != null) {
906: try {
907: creds = credProvider
908: .getCredentials(authScheme,
909: authscope.getHost(), authscope
910: .getPort(), false);
911: } catch (CredentialsNotAvailableException e) {
912: LOG.warn(e.getMessage());
913: }
914: if (creds != null) {
915: this .state.setCredentials(authscope, creds);
916: if (LOG.isDebugEnabled()) {
917: LOG.debug(authscope + " new credentials given");
918: }
919: }
920: } else {
921: LOG.debug("Credentials provider not available");
922: }
923: return creds;
924: }
925:
926: private Credentials promptForProxyCredentials(
927: final AuthScheme authScheme, final HttpParams params,
928: final AuthScope authscope) {
929: LOG.debug("Proxy credentials required");
930: Credentials creds = null;
931: CredentialsProvider credProvider = (CredentialsProvider) params
932: .getParameter(CredentialsProvider.PROVIDER);
933: if (credProvider != null) {
934: try {
935: creds = credProvider.getCredentials(authScheme,
936: authscope.getHost(), authscope.getPort(), true);
937: } catch (CredentialsNotAvailableException e) {
938: LOG.warn(e.getMessage());
939: }
940: if (creds != null) {
941: this .state.setProxyCredentials(authscope, creds);
942: if (LOG.isDebugEnabled()) {
943: LOG.debug(authscope + " new credentials given");
944: }
945: }
946: } else {
947: LOG.debug("Proxy credentials provider not available");
948: }
949: return creds;
950: }
951:
952: /**
953: * @return
954: */
955: public HostConfiguration getHostConfiguration() {
956: return hostConfiguration;
957: }
958:
959: /**
960: * @return
961: */
962: public HttpState getState() {
963: return state;
964: }
965:
966: /**
967: * @return
968: */
969: public HttpConnectionManager getConnectionManager() {
970: return connectionManager;
971: }
972:
973: /**
974: * @return
975: */
976: public HttpParams getParams() {
977: return this.params;
978: }
979: }
|