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