All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cxf.transport.http.HTTPConduit Maven / Gradle / Ivy

There is a newer version: 3.0.0-milestone2
Show newest version
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.cxf.transport.http;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpRetryException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.namespace.QName;

import org.apache.cxf.Bus;
import org.apache.cxf.common.injection.NoJSR250Annotations;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.configuration.Configurable;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.configuration.security.CertificateConstraintsType;
import org.apache.cxf.configuration.security.ProxyAuthorizationPolicy;
import org.apache.cxf.endpoint.ClientCallback;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.helpers.HttpHeaderHelper;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.helpers.LoadingByteArrayOutputStream;
import org.apache.cxf.io.AbstractThresholdOutputStream;
import org.apache.cxf.io.CacheAndWriteOutputStream;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.ExchangeImpl;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageContentsList;
import org.apache.cxf.message.MessageImpl;
import org.apache.cxf.message.MessageUtils;
import org.apache.cxf.phase.PhaseInterceptorChain;
import org.apache.cxf.policy.PolicyDataEngine;
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.cxf.transport.AbstractConduit;
import org.apache.cxf.transport.Assertor;
import org.apache.cxf.transport.MessageObserver;
import org.apache.cxf.transport.http.auth.DefaultBasicAuthSupplier;
import org.apache.cxf.transport.http.auth.DigestAuthSupplier;
import org.apache.cxf.transport.http.auth.HttpAuthHeader;
import org.apache.cxf.transport.http.auth.HttpAuthSupplier;
import org.apache.cxf.transport.http.auth.SpnegoAuthSupplier;
import org.apache.cxf.transport.http.policy.impl.ClientPolicyCalculator;
import org.apache.cxf.transport.https.CertConstraints;
import org.apache.cxf.transport.https.CertConstraintsInterceptor;
import org.apache.cxf.transport.https.CertConstraintsJaxBUtils;
import org.apache.cxf.transport.https.HttpsURLConnectionInfo;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.apache.cxf.workqueue.AutomaticWorkQueue;
import org.apache.cxf.workqueue.WorkQueueManager;
import org.apache.cxf.ws.addressing.EndpointReferenceType;

/*
 * HTTP Conduit implementation.
 * 

* This implementation is a based on the java.net.URLConnection interface and * dependent upon installed implementations of that URLConnection, * HttpURLConnection, and HttpsURLConnection. Currently, this implementation * has been known to work with the Sun JDK 1.5 default implementations. The * HttpsURLConnection is part of Sun's implementation of the JSSE. * Presently, the source code for the Sun JSSE implementation is unavailable * and therefore we may only lay a guess of whether its HttpsURLConnection * implementation correctly works as far as security is concerned. *

* The Trust Decision. If a MessageTrustDecider is configured/set for the * Conduit, it is called upon the first flush of the headers in the * WrappedOutputStream. This reason for this approach is two-fold. * Theoretically, in order to get connection information out of the * URLConnection, it must be "connected". We assume that its implementation will * only follow through up to the point at which it will be ready to send * one byte of data down to the endpoint, but through proxies, and the * commpletion of a TLS handshake in the case of HttpsURLConnection. * However, if we force the connect() call right away, the default * implementations will not allow any calls to add/setRequestProperty, * throwing an exception that the URLConnection is already connected. *

* We need to keep the semantic that later CXF interceptors may add to the * PROTOCOL_HEADERS in the Message. This architectual decision forces us to * delay the connection until after that point, then pulling the trust decision. *

* The security caveat is that we don't really know when the connection is * really established. The call to "connect" is stated to force the * "connection," but it is a no-op if the connection was already established. * It is entirely possible that an implementation of an URLConnection may * indeed connect at will and start sending the headers down the connection * during calls to add/setRequestProperty! *

* We know that the JDK 1.5 sun.com.net.www.HttpURLConnection does not send * this information before the "connect" call, because we can look at the * source code. However, we can only assume, not verify, that the JSSE 1.5 * HttpsURLConnection does the same, in that it is probable that the * HttpsURLConnection shares the HttpURLConnection implementation. *

* Due to these implementations following redirects without trust checks, we * force the URLConnection implementations not to follow redirects. If * client side policy dictates that we follow redirects, trust decisions are * placed before each retransmit. On a redirect, any authorization information * dynamically acquired by a BasicAuth UserPass supplier is removed before * being retransmitted, as it may no longer be applicable to the new url to * which the connection is redirected. */ /** * This Conduit handles the "http" and "https" transport protocols. An * instance is governed by policies either explicitly set or by * configuration. */ @NoJSR250Annotations public abstract class HTTPConduit extends AbstractConduit implements Configurable, Assertor, PropertyChangeListener { /** * This constant is the Message(Map) key for the HttpURLConnection that * is used to get the response. */ public static final String KEY_HTTP_CONNECTION = "http.connection"; /** * The Logger for this class. */ protected static final Logger LOG = LogUtils.getL7dLogger(HTTPConduit.class); private static boolean hasLoggedAsyncWarning; /** * This constant holds the suffix ".http-conduit" that is appended to the * Endpoint Qname to give the configuration name of this conduit. */ private static final String SC_HTTP_CONDUIT_SUFFIX = ".http-conduit"; private static final String AUTO_REDIRECT_SAME_HOST_ONLY = "http.redirect.same.host.only"; private static final String AUTO_REDIRECT_ALLOW_REL_URI = "http.redirect.relative.uri"; private static final String HTTP_POST_METHOD = "POST"; private static final String HTTP_PUT_METHOD = "PUT"; private static final Set KNOWN_HTTP_VERBS_WITH_NO_CONTENT = new HashSet(Arrays.asList(new String[]{"GET", "DELETE", "HEAD", "OPTIONS", "TRACE"})); /** * This constant is the Message(Map) key for a list of visited URLs that * is used in redirect loop protection. */ private static final String KEY_VISITED_URLS = "VisitedURLs"; /** * This constant is the Message(Map) key for a list of URLs that * is used in authorization loop protection. */ private static final String KEY_AUTH_URLS = "AuthURLs"; /** * This field holds a reference to the CXF bus associated this conduit. */ protected final Bus bus; /** * This field is used for two reasons. First it provides the base name for * the conduit for Spring configuration. The other is to hold default * address information, should it not be supplied in the Message Map, by the * Message.ENDPOINT_ADDRESS property. */ protected final EndpointInfo endpointInfo; /** * This field holds the "default" URI for this particular conduit, which * is created on demand. */ protected URI defaultEndpointURI; protected String defaultEndpointURIString; protected boolean fromEndpointReferenceType; protected ProxyFactory proxyFactory; // Configurable values /** * This field holds the QoS configuration settings for this conduit. * This field is injected via spring configuration based on the conduit * name. */ protected HTTPClientPolicy clientSidePolicy; /** * This field holds the password authorization configuration. * This field is injected via spring configuration based on the conduit * name. */ protected AuthorizationPolicy authorizationPolicy; /** * This field holds the password authorization configuration for the * configured proxy. This field is injected via spring configuration based * on the conduit name. */ protected ProxyAuthorizationPolicy proxyAuthorizationPolicy; /** * This field holds the configuration TLS configuration which * is programmatically configured. */ protected TLSClientParameters tlsClientParameters; /** * This field contains the MessageTrustDecider. */ protected MessageTrustDecider trustDecider; /** * Implements the authentication handling when talking to a server. If it is not set * it will be created from the authorizationPolicy.authType */ protected HttpAuthSupplier authSupplier; /** * Implements the proxy authentication handling. If it is not set * it will be created from the proxyAuthorizationPolicy.authType */ protected HttpAuthSupplier proxyAuthSupplier; protected Cookies cookies; protected CertConstraints certConstraints; /** * Constructor * * @param b the associated Bus * @param ei the endpoint info of the initiator * @throws IOException */ public HTTPConduit(Bus b, EndpointInfo ei) throws IOException { this(b, ei, null); } /** * Constructor * * @param b the associated Bus. * @param endpoint the endpoint info of the initiator. * @param t the endpoint reference of the target. * @throws IOException */ public HTTPConduit(Bus b, EndpointInfo ei, EndpointReferenceType t) throws IOException { super(getTargetReference(ei, t, b)); bus = b; endpointInfo = ei; if (t != null) { fromEndpointReferenceType = true; } proxyFactory = new ProxyFactory(); cookies = new Cookies(); updateClientPolicy(); } /** * updates the HTTPClientPolicy that is compatible with the assertions * included in the service, endpoint, operation and message policy subjects * if a PolicyDataEngine is installed * * wsdl extensors are superseded by policies which in * turn are superseded by injection */ private void updateClientPolicy() { PolicyDataEngine policyEngine = bus.getExtension(PolicyDataEngine.class); if (policyEngine != null && endpointInfo.getService() != null) { clientSidePolicy = policyEngine.getClientEndpointPolicy(endpointInfo, this, new ClientPolicyCalculator()); } } /** * This method returns the registered Logger for this conduit. */ protected Logger getLogger() { return LOG; } /** * This method returns the name of the conduit, which is based on the * endpoint name plus the SC_HTTP_CONDUIT_SUFFIX. * @return */ public final String getConduitName() { return endpointInfo.getName() + SC_HTTP_CONDUIT_SUFFIX; } private static void configureConduitFromEndpointInfo(HTTPConduit conduit, EndpointInfo endpointInfo) { if (conduit.getClient() == null) { conduit.setClient(endpointInfo.getTraversedExtensor( new HTTPClientPolicy(), HTTPClientPolicy.class)); } if (conduit.getAuthorization() == null) { conduit.setAuthorization(endpointInfo.getTraversedExtensor( new AuthorizationPolicy(), AuthorizationPolicy.class)); } if (conduit.getProxyAuthorization() == null) { conduit.setProxyAuthorization(endpointInfo.getTraversedExtensor( new ProxyAuthorizationPolicy(), ProxyAuthorizationPolicy.class)); } if (conduit.getTlsClientParameters() == null) { conduit.setTlsClientParameters(endpointInfo.getTraversedExtensor( null, TLSClientParameters.class)); } if (conduit.getTrustDecider() == null) { conduit.setTrustDecider(endpointInfo.getTraversedExtensor(null, MessageTrustDecider.class)); } if (conduit.getAuthSupplier() == null) { conduit.setAuthSupplier(endpointInfo.getTraversedExtensor(null, HttpAuthSupplier.class)); } } private void logConfig() { if (!LOG.isLoggable(Level.FINE)) { return; } if (trustDecider == null) { LOG.log(Level.FINE, "No Trust Decider configured for Conduit '" + getConduitName() + "'"); } else { LOG.log(Level.FINE, "Message Trust Decider of class '" + trustDecider.getClass().getName() + "' with logical name of '" + trustDecider.getLogicalName() + "' has been configured for Conduit '" + getConduitName() + "'"); } if (authSupplier == null) { LOG.log(Level.FINE, "No Auth Supplier configured for Conduit '" + getConduitName() + "'"); } else { LOG.log(Level.FINE, "HttpAuthSupplier of class '" + authSupplier.getClass().getName() + "' has been configured for Conduit '" + getConduitName() + "'"); } if (this.tlsClientParameters != null) { LOG.log(Level.FINE, "Conduit '" + getConduitName() + "' has been configured for TLS " + "keyManagers " + Arrays.toString(tlsClientParameters.getKeyManagers()) + "trustManagers " + Arrays.toString(tlsClientParameters.getTrustManagers()) + "secureRandom " + tlsClientParameters.getSecureRandom() + "Disable Common Name (CN) Check: " + tlsClientParameters.isDisableCNCheck()); } else { LOG.log(Level.FINE, "Conduit '" + getConduitName() + "' has been configured for plain http."); } } /** * This call gets called by the HTTPTransportFactory after it * causes an injection of the Spring configuration properties * of this Conduit. */ public void finalizeConfig() { // See if not set by configuration, if there are defaults // in order from the Endpoint, Service, or Bus. configureConduitFromEndpointInfo(this, endpointInfo); logConfig(); if (getClient().getDecoupledEndpoint() != null) { this.endpointInfo.setProperty("org.apache.cxf.ws.addressing.replyto", getClient().getDecoupledEndpoint()); } if (clientSidePolicy != null) { clientSidePolicy.removePropertyChangeListener(this); //make sure we aren't added twice clientSidePolicy.addPropertyChangeListener(this); } } /** * Allow access to the cookies that the conduit is maintaining * @return the sessionCookies map */ public Map getCookies() { return cookies.getSessionCookies(); } protected abstract void setupConnection(Message message, URI url, HTTPClientPolicy csPolicy) throws IOException; /** * Prepare to send an outbound HTTP message over this http conduit to a * particular endpoint. *

* If the Message.PATH_INFO property is set it gets appended * to the Conduit's endpoint URL. If the Message.QUERY_STRING * property is set, it gets appended to the resultant URL following * a "?". *

* If the Message.HTTP_REQUEST_METHOD property is NOT set, the * Http request method defaults to "POST". *

* If the Message.PROTOCOL_HEADERS is not set on the message, it is * initialized to an empty map. *

* This call creates the OutputStream for the content of the message. * It also assigns the created Http(s)URLConnection to the Message * Map. * * @param message The message to be sent. */ public void prepare(Message message) throws IOException { // This call can possibly change the conduit endpoint address and // protocol from the default set in EndpointInfo that is associated // with the Conduit. URI currentURI; try { currentURI = setupURI(message); } catch (URISyntaxException e) { throw new IOException(e); } // The need to cache the request is off by default boolean needToCacheRequest = false; HTTPClientPolicy csPolicy = getClient(message); setupConnection(message, currentURI, csPolicy); // If the HTTP_REQUEST_METHOD is not set, the default is "POST". String httpRequestMethod = (String)message.get(Message.HTTP_REQUEST_METHOD); if (httpRequestMethod == null) { httpRequestMethod = "POST"; message.put(Message.HTTP_REQUEST_METHOD, "POST"); } boolean isChunking = false; int chunkThreshold = 0; final AuthorizationPolicy effectiveAuthPolicy = getEffectiveAuthPolicy(message); if (this.authSupplier == null) { this.authSupplier = createAuthSupplier(effectiveAuthPolicy.getAuthorizationType()); } if (this.proxyAuthSupplier == null) { this.proxyAuthSupplier = createAuthSupplier(proxyAuthorizationPolicy.getAuthorizationType()); } if (this.authSupplier.requiresRequestCaching()) { needToCacheRequest = true; isChunking = false; LOG.log(Level.FINE, "Auth Supplier, but no Premeptive User Pass or Digest auth (nonce may be stale)" + " We must cache request."); } if (csPolicy.isAutoRedirect()) { needToCacheRequest = true; LOG.log(Level.FINE, "AutoRedirect is turned on."); } if (csPolicy.getMaxRetransmits() > 0) { needToCacheRequest = true; LOG.log(Level.FINE, "MaxRetransmits is set > 0."); } // DELETE does not work and empty PUTs cause misleading exceptions // if chunking is enabled // TODO : ensure chunking can be enabled for non-empty PUTs - if requested if (csPolicy.isAllowChunking() && isChunkingSupported(message, httpRequestMethod)) { //TODO: The chunking mode be configured or at least some // documented client constant. //use -1 and allow the URL connection to pick a default value isChunking = true; chunkThreshold = csPolicy.getChunkingThreshold(); } cookies.writeToMessageHeaders(message); // The trust decision is relegated to after the "flushing" of the // request headers. if (certConstraints != null) { message.put(CertConstraints.class.getName(), certConstraints); message.getInterceptorChain().add(CertConstraintsInterceptor.INSTANCE); } setHeadersByAuthorizationPolicy(message, currentURI); new Headers(message).setFromClientPolicy(getClient(message)); message.setContent(OutputStream.class, createOutputStream(message, needToCacheRequest, isChunking, chunkThreshold)); // We are now "ready" to "send" the message. } protected boolean isChunkingSupported(Message message, String httpMethod) { if (HTTP_POST_METHOD.equals(httpMethod)) { return true; } if (HTTP_PUT_METHOD.equals(httpMethod)) { MessageContentsList objs = MessageContentsList.getContentsList(message); if (objs != null && objs.size() > 0) { Object obj = objs.get(0); if (obj.getClass() != String.class || (obj.getClass() == String.class && ((String)obj).length() > 0)) { return true; } } } return false; } protected abstract OutputStream createOutputStream(Message message, boolean needToCacheRequest, boolean isChunking, int chunkThreshold) throws IOException; private HttpAuthSupplier createAuthSupplier(String authType) { if (HttpAuthHeader.AUTH_TYPE_NEGOTIATE.equals(authType)) { return new SpnegoAuthSupplier(); } else if (HttpAuthHeader.AUTH_TYPE_DIGEST.equals(authType)) { return new DigestAuthSupplier(); } else { return new DefaultBasicAuthSupplier(); } } protected static int determineReceiveTimeout(Message message, HTTPClientPolicy csPolicy) { long rtimeout = csPolicy.getReceiveTimeout(); if (message.get(Message.RECEIVE_TIMEOUT) != null) { Object obj = message.get(Message.RECEIVE_TIMEOUT); try { rtimeout = Long.parseLong(obj.toString()); } catch (NumberFormatException e) { LOG.log(Level.WARNING, "INVALID_TIMEOUT_FORMAT", new Object[] { Message.RECEIVE_TIMEOUT, obj.toString() }); } } if (rtimeout > Integer.MAX_VALUE) { rtimeout = Integer.MAX_VALUE; } return (int)rtimeout; } protected static int determineConnectionTimeout(Message message, HTTPClientPolicy csPolicy) { long ctimeout = csPolicy.getConnectionTimeout(); if (message.get(Message.CONNECTION_TIMEOUT) != null) { Object obj = message.get(Message.CONNECTION_TIMEOUT); try { ctimeout = Long.parseLong(obj.toString()); } catch (NumberFormatException e) { LOG.log(Level.WARNING, "INVALID_TIMEOUT_FORMAT", new Object[] { Message.CONNECTION_TIMEOUT, obj.toString() }); } } if (ctimeout > Integer.MAX_VALUE) { ctimeout = Integer.MAX_VALUE; } return (int)ctimeout; } public void close(Message msg) throws IOException { InputStream in = msg.getContent(InputStream.class); try { if (in != null) { int count = 0; byte buffer[] = new byte[1024]; while (in.read(buffer) != -1 && count < 25) { //don't do anything, we just need to pull off the unread data (like //closing tags that we didn't need to read //however, limit it so we don't read off gigabytes of data we won't use. ++count; } } } finally { super.close(msg); } } /** * This function sets up a URL based on ENDPOINT_ADDRESS, PATH_INFO, * and QUERY_STRING properties in the Message. The QUERY_STRING gets * added with a "?" after the PATH_INFO. If the ENDPOINT_ADDRESS is not * set on the Message, the endpoint address is taken from the * "defaultEndpointURL". *

* The PATH_INFO is only added to the endpoint address string should * the PATH_INFO not equal the end of the endpoint address string. * * @param message The message holds the addressing information. * * @return The full URL specifying the HTTP request to the endpoint. * * @throws MalformedURLException * @throws URISyntaxException */ private URI setupURI(Message message) throws URISyntaxException { String result = (String)message.get(Message.ENDPOINT_ADDRESS); String pathInfo = (String)message.get(Message.PATH_INFO); String queryString = (String)message.get(Message.QUERY_STRING); if (result == null) { if (pathInfo == null && queryString == null) { URI uri = getURI(); message.put(Message.ENDPOINT_ADDRESS, defaultEndpointURIString); return uri; } result = getURI().toString(); message.put(Message.ENDPOINT_ADDRESS, result); } // REVISIT: is this really correct? if (null != pathInfo && !result.endsWith(pathInfo)) { result = result + pathInfo; } if (queryString != null) { result = result + "?" + queryString; } return new URI(result); } /** * Close the conduit */ public void close() { if (clientSidePolicy != null) { clientSidePolicy.removePropertyChangeListener(this); } } /** * @return the default target address */ public String getAddress() { if (defaultEndpointURI != null) { return defaultEndpointURIString; } else if (fromEndpointReferenceType) { return getTarget().getAddress().getValue(); } return endpointInfo.getAddress(); } /** * @return the default target URL */ protected URI getURI() throws URISyntaxException { return getURI(true); } /** * @param createOnDemand create URL on-demand if null * @return the default target URL * @throws URISyntaxException */ protected synchronized URI getURI(boolean createOnDemand) throws URISyntaxException { if (defaultEndpointURI == null && createOnDemand) { if (fromEndpointReferenceType && getTarget().getAddress().getValue() != null) { defaultEndpointURI = new URI(this.getTarget().getAddress().getValue()); defaultEndpointURIString = defaultEndpointURI.toString(); return defaultEndpointURI; } if (endpointInfo.getAddress() == null) { throw new URISyntaxException("", "Invalid address. Endpoint address cannot be null.", 0); } defaultEndpointURI = new URI(endpointInfo.getAddress()); defaultEndpointURIString = defaultEndpointURI.toString(); } return defaultEndpointURI; } /** * This call places HTTP Header strings into the headers that are relevant * to the Authorization policies that are set on this conduit by * configuration. *

* An AuthorizationPolicy may also be set on the message. If so, those * policies are merged. A user name or password set on the messsage * overrides settings in the AuthorizationPolicy is retrieved from the * configuration. *

* The precedence is as follows: * 1. AuthorizationPolicy that is set on the Message, if exists. * 2. Authorization from AuthSupplier, if exists. * 3. AuthorizationPolicy set/configured for conduit. * * REVISIT: Since the AuthorizationPolicy is set on the message by class, then * how does one override the ProxyAuthorizationPolicy which is the same * type? * * @param message * @param headers */ protected void setHeadersByAuthorizationPolicy( Message message, URI currentURI ) { Headers headers = new Headers(message); AuthorizationPolicy effectiveAuthPolicy = getEffectiveAuthPolicy(message); String authString = authSupplier.getAuthorization(effectiveAuthPolicy, currentURI, message, null); if (authString != null) { headers.setAuthorization(authString); } String proxyAuthString = authSupplier.getAuthorization(proxyAuthorizationPolicy, currentURI, message, null); if (proxyAuthString != null) { headers.setProxyAuthorization(proxyAuthString); } } /** * This is part of the Configurable interface which retrieves the * configuration from spring injection. */ // REVISIT:What happens when the endpoint/bean name is null? public String getBeanName() { if (endpointInfo.getName() != null) { return endpointInfo.getName().toString() + ".http-conduit"; } return null; } /** * Determines effective auth policy from message, conduit and empty default * with priority from first to last * * @param message * @return effective AthorizationPolicy */ public AuthorizationPolicy getEffectiveAuthPolicy(Message message) { AuthorizationPolicy authPolicy = getAuthorization(); AuthorizationPolicy newPolicy = message.get(AuthorizationPolicy.class); AuthorizationPolicy effectivePolicy = newPolicy; if (effectivePolicy == null) { effectivePolicy = authPolicy; } if (effectivePolicy == null) { effectivePolicy = new AuthorizationPolicy(); } return effectivePolicy; } /** * This method gets the Authorization Policy that was configured or * explicitly set for this HTTPConduit. */ public AuthorizationPolicy getAuthorization() { return authorizationPolicy; } /** * This method is used to set the Authorization Policy for this conduit. * Using this method will override any Authorization Policy set in * configuration. */ public void setAuthorization(AuthorizationPolicy authorization) { this.authorizationPolicy = authorization; } public HTTPClientPolicy getClient(Message message) { ClientPolicyCalculator cpc = new ClientPolicyCalculator(); HTTPClientPolicy pol = message.get(HTTPClientPolicy.class); if (pol != null) { pol = cpc.intersect(pol, clientSidePolicy); } else { pol = clientSidePolicy; } PolicyDataEngine policyDataEngine = bus.getExtension(PolicyDataEngine.class); if (policyDataEngine == null) { return pol; } return policyDataEngine.getPolicy(message, pol, cpc); } /** * This method retrieves the Client Side Policy set/configured for this * HTTPConduit. */ public HTTPClientPolicy getClient() { return clientSidePolicy; } /** * This method sets the Client Side Policy for this HTTPConduit. Using this * method will override any HTTPClientPolicy set in configuration. */ public void setClient(HTTPClientPolicy client) { if (this.clientSidePolicy != null) { this.clientSidePolicy.removePropertyChangeListener(this); } this.clientSidePolicy = client; clientSidePolicy.removePropertyChangeListener(this); //make sure we aren't added twice clientSidePolicy.addPropertyChangeListener(this); endpointInfo.setProperty("org.apache.cxf.ws.addressing.replyto", client.getDecoupledEndpoint()); } /** * This method retrieves the Proxy Authorization Policy for a proxy that is * set/configured for this HTTPConduit. */ public ProxyAuthorizationPolicy getProxyAuthorization() { return proxyAuthorizationPolicy; } /** * This method sets the Proxy Authorization Policy for a specified proxy. * Using this method overrides any Authorization Policy for the proxy * that is set in the configuration. */ public void setProxyAuthorization( ProxyAuthorizationPolicy proxyAuthorization ) { this.proxyAuthorizationPolicy = proxyAuthorization; } /** * This method returns the TLS Client Parameters that is set/configured * for this HTTPConduit. */ public TLSClientParameters getTlsClientParameters() { return tlsClientParameters; } /** * This method sets the TLS Client Parameters for this HTTPConduit. * Using this method overrides any TLS Client Parameters that is configured * for this HTTPConduit. */ public void setTlsClientParameters(TLSClientParameters params) { this.tlsClientParameters = params; if (this.tlsClientParameters != null) { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Conduit '" + getConduitName() + "' has been (re) configured for TLS " + "keyManagers " + Arrays.toString(tlsClientParameters.getKeyManagers()) + "trustManagers " + Arrays.toString(tlsClientParameters.getTrustManagers()) + "secureRandom " + tlsClientParameters.getSecureRandom()); } CertificateConstraintsType constraints = params.getCertConstraints(); if (constraints != null) { certConstraints = CertConstraintsJaxBUtils.createCertConstraints(constraints); } } else { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Conduit '" + getConduitName() + "' has been (re)configured for plain http."); } } } /** * This method gets the Trust Decider that was set/configured for this * HTTPConduit. * @return The Message Trust Decider or null. */ public MessageTrustDecider getTrustDecider() { return this.trustDecider; } /** * This method sets the Trust Decider for this HTTP Conduit. * Using this method overrides any trust decider configured for this * HTTPConduit. */ public void setTrustDecider(MessageTrustDecider decider) { this.trustDecider = decider; } /** * This method gets the Auth Supplier that was set/configured for this * HTTPConduit. * @return The Auth Supplier or null. */ public HttpAuthSupplier getAuthSupplier() { return this.authSupplier; } public void setAuthSupplier(HttpAuthSupplier supplier) { this.authSupplier = supplier; } public HttpAuthSupplier getProxyAuthSupplier() { return proxyAuthSupplier; } public void setProxyAuthSupplier(HttpAuthSupplier proxyAuthSupplier) { this.proxyAuthSupplier = proxyAuthSupplier; } /** * This method extracts the value of the "Location" Http * Response header. * * @param headers The Http response headers. * @return The value of the "Location" header, null if non-existent. * @throws MalformedURLException */ protected String extractLocation(Map> headers) throws MalformedURLException { for (Map.Entry> head : headers.entrySet()) { if ("Location".equalsIgnoreCase(head.getKey())) { List locs = head.getValue(); if (locs != null && locs.size() > 0) { String location = locs.get(0); if (location != null) { return location; } else { return null; } } } } return null; } /** * Used to set appropriate message properties, exchange etc. * as required for an incoming decoupled response (as opposed * what's normally set by the Destination for an incoming * request). */ protected class InterposedMessageObserver implements MessageObserver { /** * Called for an incoming message. * * @param inMessage */ public void onMessage(Message inMessage) { // disposable exchange, swapped with real Exchange on correlation inMessage.setExchange(new ExchangeImpl()); inMessage.getExchange().put(Bus.class, bus); inMessage.put(Message.DECOUPLED_CHANNEL_MESSAGE, Boolean.TRUE); // REVISIT: how to get response headers? //inMessage.put(Message.PROTOCOL_HEADERS, req.getXXX()); Headers.getSetProtocolHeaders(inMessage); inMessage.put(Message.RESPONSE_CODE, HttpURLConnection.HTTP_OK); // remove server-specific properties inMessage.remove(AbstractHTTPDestination.HTTP_REQUEST); inMessage.remove(AbstractHTTPDestination.HTTP_RESPONSE); inMessage.remove(Message.ASYNC_POST_RESPONSE_DISPATCH); //cache this inputstream since it's defer to use in case of async try { InputStream in = inMessage.getContent(InputStream.class); if (in != null) { CachedOutputStream cos = new CachedOutputStream(); IOUtils.copy(in, cos); inMessage.setContent(InputStream.class, cos.getInputStream()); } incomingObserver.onMessage(inMessage); } catch (IOException e) { e.printStackTrace(); } } } public void assertMessage(Message message) { PolicyDataEngine policyDataEngine = bus.getExtension(PolicyDataEngine.class); policyDataEngine.assertMessage(message, getClient(), new ClientPolicyCalculator()); } public boolean canAssert(QName type) { return new ClientPolicyCalculator().equals(type); } public void propertyChange(PropertyChangeEvent evt) { if (evt.getSource() == clientSidePolicy && "decoupledEndpoint".equals(evt.getPropertyName())) { this.endpointInfo.setProperty("org.apache.cxf.ws.addressing.replyto", evt.getNewValue()); } } /** * Wrapper output stream responsible for flushing headers and handling * the incoming HTTP-level response (not necessarily the MEP response). */ protected abstract class WrappedOutputStream extends AbstractThresholdOutputStream { /** * This boolean is true if the request must be cached. */ protected boolean cachingForRetransmission; /** * If we are going to be chunking, we won't flush till close which causes * new chunks, small network packets, etc.. */ protected final boolean chunking; /** * This field contains the output stream with which we cache * the request. It maybe null if we are not caching. */ protected CacheAndWriteOutputStream cachedStream; protected Message outMessage; protected String conduitName; protected URI url; protected WrappedOutputStream( Message outMessage, boolean possibleRetransmit, boolean isChunking, int chunkThreshold, String conduitName, URI url ) { super(chunkThreshold); this.outMessage = outMessage; this.cachingForRetransmission = possibleRetransmit; this.chunking = isChunking; this.conduitName = conduitName; this.url = url; } // This construction makes extending the HTTPConduit more easier protected WrappedOutputStream(WrappedOutputStream wos) { super(wos.threshold); this.outMessage = wos.outMessage; this.cachingForRetransmission = wos.cachingForRetransmission; this.chunking = wos.chunking; this.conduitName = wos.conduitName; this.url = wos.url; } @Override public void thresholdNotReached() { if (chunking) { setFixedLengthStreamingMode(buffer.size()); } } // methods used for the outgoing side protected abstract void setupWrappedStream() throws IOException; protected abstract HttpsURLConnectionInfo getHttpsURLConnectionInfo() throws IOException; protected abstract void setProtocolHeaders() throws IOException; protected abstract void setFixedLengthStreamingMode(int i); // methods used for the incoming side protected abstract int getResponseCode() throws IOException; protected abstract String getResponseMessage() throws IOException; protected abstract void updateResponseHeaders(Message inMessage) throws IOException; protected abstract void handleResponseAsync() throws IOException; protected abstract void closeInputStream() throws IOException; protected abstract boolean usingProxy(); protected abstract InputStream getInputStream() throws IOException; protected abstract InputStream getPartialResponse() throws IOException; //methods to support retransmission for auth or redirects protected abstract void setupNewConnection(String newURL) throws IOException; protected abstract void retransmitStream() throws IOException; protected abstract void updateCookiesBeforeRetransmit() throws IOException; protected void handleNoOutput() throws IOException { //For GET and DELETE and such, this will be called //For some implementations, this notice may be required to //actually execute the request } protected void handleResponseOnWorkqueue(boolean allowCurrentThread, boolean forceWQ) throws IOException { Runnable runnable = new Runnable() { public void run() { try { handleResponseInternal(); } catch (Throwable e) { ((PhaseInterceptorChain)outMessage.getInterceptorChain()).abort(); outMessage.setContent(Exception.class, e); ((PhaseInterceptorChain)outMessage.getInterceptorChain()).unwind(outMessage); MessageObserver mo = outMessage.getInterceptorChain().getFaultObserver(); if (mo == null) { mo = outMessage.getExchange().get(MessageObserver.class); } mo.onMessage(outMessage); } } }; HTTPClientPolicy policy = getClient(outMessage); try { Executor ex = outMessage.getExchange().get(Executor.class); if (forceWQ && ex != null) { final Executor ex2 = ex; final Runnable origRunnable = runnable; runnable = new Runnable() { public void run() { outMessage.getExchange().put(Executor.class.getName() + ".USING_SPECIFIED", Boolean.TRUE); ex2.execute(origRunnable); } }; } if (ex == null || forceWQ) { WorkQueueManager mgr = outMessage.getExchange().get(Bus.class) .getExtension(WorkQueueManager.class); AutomaticWorkQueue qu = mgr.getNamedWorkQueue("http-conduit"); if (qu == null) { qu = mgr.getAutomaticWorkQueue(); } long timeout = 1000; if (policy != null && policy.isSetAsyncExecuteTimeout()) { timeout = policy.getAsyncExecuteTimeout(); } if (timeout > 0) { qu.execute(runnable, timeout); } else { qu.execute(runnable); } } else { outMessage.getExchange().put(Executor.class.getName() + ".USING_SPECIFIED", Boolean.TRUE); ex.execute(runnable); } } catch (RejectedExecutionException rex) { if (allowCurrentThread && policy != null && policy.isSetAsyncExecuteTimeoutRejection() && policy.isAsyncExecuteTimeoutRejection()) { throw rex; } if (!hasLoggedAsyncWarning) { LOG.warning("EXECUTOR_FULL_WARNING"); hasLoggedAsyncWarning = true; } LOG.fine("EXECUTOR_FULL"); handleResponseInternal(); } } protected void retransmit(String newURL) throws IOException { setupNewConnection(newURL); outMessage.put("http.retransmit.url", newURL); if (cachedStream != null && cachedStream.size() < Integer.MAX_VALUE) { setFixedLengthStreamingMode((int)cachedStream.size()); } setProtocolHeaders(); // // This point is where the trust decision is made because the // Sun implementation of URLConnection will not let us // set/addRequestProperty after a connect() call, and // makeTrustDecision needs to make a connect() call to // make sure the proper information is available. // makeTrustDecision(); // If this is a GET method we must not touch the output // stream as this automagically turns the request into a POST. if (getMethod().equals("GET")) { handleNoOutput(); return; } // Trust is okay, write the cached request retransmitStream(); if (LOG.isLoggable(Level.FINE)) { LOG.fine("Conduit \"" + getConduitName() + "\" Retransmit message to: " + newURL + ": " + new String(cachedStream.getBytes())); } } /** * Perform any actions required on stream flush (freeze headers, * reset output stream ... etc.) */ @Override protected void onFirstWrite() throws IOException { try { handleHeadersTrustCaching(); } catch (IOException e) { if (e.getMessage() != null && e.getMessage().contains("HTTPS hostname wrong:")) { throw new IOException("The https URL hostname does not match the " + "Common Name (CN) on the server certificate in the client's truststore. " + "Make sure server certificate is correct, or to disable this check " + "(NOT recommended for production) set the CXF client TLS " + "configuration property \"disableCNCheck\" to true."); } else { throw e; } } } protected String getMethod() { return (String)outMessage.get(Message.HTTP_REQUEST_METHOD); } protected void handleHeadersTrustCaching() throws IOException { // Need to set the headers before the trust decision // because they are set before the connect(). setProtocolHeaders(); // // This point is where the trust decision is made because the // Sun implementation of URLConnection will not let us // set/addRequestProperty after a connect() call, and // makeTrustDecision needs to make a connect() call to // make sure the proper information is available. // makeTrustDecision(); // Trust is okay, set up for writing the request. String method = getMethod(); if (KNOWN_HTTP_VERBS_WITH_NO_CONTENT.contains(method)) { handleNoOutput(); return; } if (outMessage.get("org.apache.cxf.empty.request") != null) { handleNoOutput(); return; } setupWrappedStream(); } /** * Perform any actions required on stream closure (handle response etc.) */ public void close() throws IOException { try { if (buffer != null && buffer.size() > 0) { thresholdNotReached(); LoadingByteArrayOutputStream tmp = buffer; buffer = null; super.write(tmp.getRawBytes(), 0, tmp.size()); } if (!written) { handleHeadersTrustCaching(); } if (!cachingForRetransmission) { super.close(); } else if (cachedStream != null) { super.flush(); cachedStream.getOut().close(); cachedStream.closeFlowthroughStream(); } try { handleResponse(); } finally { if (cachingForRetransmission && cachedStream != null) { cachedStream.close(); } } } catch (HttpRetryException e) { handleHttpRetryException(e); } catch (IOException e) { String origMessage = e.getMessage(); if (origMessage != null && origMessage.contains(url.toString())) { throw e; } throw mapException(e.getClass().getSimpleName() + " invoking " + url + ": " + e.getMessage(), e, IOException.class); } catch (RuntimeException e) { throw mapException(e.getClass().getSimpleName() + " invoking " + url + ": " + e.getMessage(), e, RuntimeException.class); } } private T mapException(String msg, T ex, Class cls) { T ex2 = ex; try { ex2 = cls.cast(ex.getClass().getConstructor(String.class).newInstance(msg)); ex2.initCause(ex); } catch (Throwable e) { ex2 = ex; } return ex2; } /** * This procedure handles all retransmits, if any. * * @throws IOException */ protected void handleRetransmits() throws IOException { // If we have a cachedStream, we are caching the request. if (cachedStream != null || ("GET".equals(getMethod()) && getClient().isAutoRedirect())) { if (LOG.isLoggable(Level.FINE) && cachedStream != null) { LOG.fine("Conduit \"" + getConduitName() + "\" Transmit cached message to: " + url + ": " + new String(cachedStream.getBytes())); } int maxRetransmits = getMaxRetransmits(); updateCookiesBeforeRetransmit(); int nretransmits = 0; while ((maxRetransmits < 0 || nretransmits < maxRetransmits) && processRetransmit()) { nretransmits++; } } } /** * This function processes any retransmits at the direction of redirections * or "unauthorized" responses. * * @return true if there was a retransmit * @throws IOException */ protected boolean processRetransmit() throws IOException { int responseCode = getResponseCode(); if ((outMessage != null) && (outMessage.getExchange() != null)) { outMessage.getExchange().put(Message.RESPONSE_CODE, responseCode); } // Process Redirects first. switch(responseCode) { case HttpURLConnection.HTTP_MOVED_PERM: case HttpURLConnection.HTTP_MOVED_TEMP: case HttpURLConnection.HTTP_SEE_OTHER: case 307: return redirectRetransmit(); case HttpURLConnection.HTTP_UNAUTHORIZED: return authorizationRetransmit(); default: break; } return false; } protected boolean redirectRetransmit() throws IOException { // If we are not redirecting by policy, then we don't. if (!getClient(outMessage).isAutoRedirect()) { return false; } Message m = new MessageImpl(); updateResponseHeaders(m); String newURL = extractLocation(Headers.getSetProtocolHeaders(m)); String urlString = url.toString(); try { detectRedirectLoop(conduitName, urlString, newURL, outMessage); newURL = convertToAbsoluteUrlIfNeeded(conduitName, urlString, newURL, outMessage); checkSameBaseUriRedirect(conduitName, urlString, newURL, outMessage); } catch (IOException ex) { // Consider introducing ClientRedirectException instead - it will require // those client runtimes which want to check for it have a direct link to it outMessage.getExchange().put("client.redirect.exception", "true"); throw ex; } if (newURL != null) { new Headers(outMessage).removeAuthorizationHeaders(); // If user configured this Conduit with preemptive authorization // it is meant to make it to the end. (Too bad that information // went to every URL along the way, but that's what the user // wants! try { setHeadersByAuthorizationPolicy(outMessage, new URI(newURL)); } catch (URISyntaxException e) { throw new IOException(e); } retransmit(newURL); return true; } return false; } /** * This method performs a retransmit for authorization information. * * @param connection The currently active connection. * @param message The outbound message. * @param cachedStream The cached request. * @return A new connection if retransmitted. If not retransmitted * then this method returns the same connection. * @throws IOException */ protected boolean authorizationRetransmit() throws IOException { Message m = new MessageImpl(); updateResponseHeaders(m); HttpAuthHeader authHeader = new HttpAuthHeader(Headers.getSetProtocolHeaders(m).get("WWW-Authenticate")); URI currentURI = url; String realm = authHeader.getRealm(); detectAuthorizationLoop(getConduitName(), outMessage, currentURI, realm); AuthorizationPolicy effectiveAthPolicy = getEffectiveAuthPolicy(outMessage); String authorizationToken = authSupplier.getAuthorization( effectiveAthPolicy, currentURI, outMessage, authHeader.getFullHeader()); if (authorizationToken == null) { // authentication not possible => we give up return false; } try { closeInputStream(); } catch (Throwable t) { //ignore } new Headers(outMessage).setAuthorization(authorizationToken); cookies.writeToMessageHeaders(outMessage); retransmit(url.toString()); return true; } private int getMaxRetransmits() { HTTPClientPolicy policy = getClient(outMessage); // Default MaxRetransmits is -1 which means unlimited. return (policy == null) ? -1 : policy.getMaxRetransmits(); } /** * This procedure is called on the close of the output stream so * we are ready to handle the response from the connection. * We may retransmit until we finally get a response. * * @throws IOException */ protected void handleResponse() throws IOException { // Process retransmits until we fall out. handleRetransmits(); if (outMessage == null || outMessage.getExchange() == null || outMessage.getExchange().isSynchronous()) { handleResponseInternal(); } else { handleResponseAsync(); } } /** * This predicate returns true iff the exchange indicates * a oneway MEP. * * @param exchange The exchange in question */ private boolean isOneway(Exchange exchange) { return exchange != null && exchange.isOneWay(); } private boolean doProcessResponse(Message message) { // 1. Not oneWay if (!isOneway(message.getExchange())) { return true; } // 2. Context property return MessageUtils.getContextualBoolean(message, Message.PROCESS_ONEWAY_RESPONSE, false); } protected void handleResponseInternal() throws IOException { Exchange exchange = outMessage.getExchange(); int responseCode = getResponseCode(); if (responseCode == -1) { LOG.warning("HTTP Response code appears to be corrupted"); } if (exchange != null) { exchange.put(Message.RESPONSE_CODE, responseCode); } // This property should be set in case the exceptions should not be handled here // For example jax rs uses this boolean noExceptions = MessageUtils.isTrue(outMessage.getContextualProperty( "org.apache.cxf.http.no_io_exceptions")); if (responseCode >= 400 && responseCode != 500 && !noExceptions) { throw new HTTPException(responseCode, getResponseMessage(), url.toURL()); } InputStream in = null; // oneway or decoupled twoway calls may expect HTTP 202 with no content Message inMessage = new MessageImpl(); inMessage.setExchange(exchange); updateResponseHeaders(inMessage); inMessage.put(Message.RESPONSE_CODE, responseCode); if (isOneway(exchange) || HttpURLConnection.HTTP_ACCEPTED == responseCode) { in = getPartialResponse(); if ((in == null) || !doProcessResponse(outMessage)) { // oneway operation or decoupled MEP without // partial response closeInputStream(); if (isOneway(exchange) && responseCode > 300) { throw new HTTPException(responseCode, getResponseMessage(), url.toURL()); } ClientCallback cc = exchange.get(ClientCallback.class); if (null != cc) { //REVISIT move the decoupled destination property name into api Endpoint ep = exchange.getEndpoint(); if (null != ep && null != ep.getEndpointInfo() && null == ep.getEndpointInfo(). getProperty("org.apache.cxf.ws.addressing.MAPAggregator.decoupledDestination")) { cc.handleResponse(null, null); } } if (in != null) { in.close(); } return; } } else { //not going to be resending or anything, clear out the stuff in the out message //to free memory outMessage.removeContent(OutputStream.class); if (cachingForRetransmission && cachedStream != null) { cachedStream.close(); } cachedStream = null; } String charset = HttpHeaderHelper.findCharset((String)inMessage.get(Message.CONTENT_TYPE)); String normalizedEncoding = HttpHeaderHelper.mapCharset(charset); if (normalizedEncoding == null) { String m = new org.apache.cxf.common.i18n.Message("INVALID_ENCODING_MSG", LOG, charset).toString(); LOG.log(Level.WARNING, m); throw new IOException(m); } inMessage.put(Message.ENCODING, normalizedEncoding); if (in == null) { in = getInputStream(); } if (in == null) { // Create an empty stream to avoid NullPointerExceptions in = new ByteArrayInputStream(new byte[] {}); } inMessage.setContent(InputStream.class, in); incomingObserver.onMessage(inMessage); } protected void handleHttpRetryException(HttpRetryException e) throws IOException { String msg = "HTTP response '" + e.responseCode() + ": " + getResponseMessage() + "' invoking " + url; switch (e.responseCode()) { case HttpURLConnection.HTTP_MOVED_PERM: // 301 case HttpURLConnection.HTTP_MOVED_TEMP: // 302 case HttpURLConnection.HTTP_SEE_OTHER: // 303 case 307: msg += " that returned location header '" + e.getLocation() + "'"; break; case HttpURLConnection.HTTP_UNAUTHORIZED: // 401 if (authorizationPolicy == null || authorizationPolicy.getUserName() == null) { msg += " with NO authorization username configured in conduit " + getConduitName(); } else { msg += " with authorization username '" + authorizationPolicy.getUserName() + "'"; } break; case HttpURLConnection.HTTP_PROXY_AUTH: // 407 if (proxyAuthorizationPolicy == null || proxyAuthorizationPolicy.getUserName() == null) { msg += " with NO proxy authorization configured in conduit " + getConduitName(); } else { msg += " with proxy authorization username '" + proxyAuthorizationPolicy.getUserName() + "'"; } if (clientSidePolicy == null || clientSidePolicy.getProxyServer() == null) { if (usingProxy()) { msg += " using a proxy even if NONE is configured in CXF conduit " + getConduitName() + " (maybe one is configured by java.net.ProxySelector)"; } else { msg += " but NO proxy was used by the connection (none configured in cxf " + "conduit and none selected by java.net.ProxySelector)"; } } else { msg += " using " + clientSidePolicy.getProxyServerType() + " proxy " + clientSidePolicy.getProxyServer() + ":" + clientSidePolicy.getProxyServerPort(); } break; default: // No other type of HttpRetryException should be thrown break; } throw new IOException(msg, e); } /** * This call must take place before anything is written to the * URLConnection. The URLConnection.connect() will be called in order * to get the connection information. * * This method is invoked just after setURLRequestHeaders() from the * WrappedOutputStream before it writes data to the URLConnection. * * If trust cannot be established the Trust Decider implemenation * throws an IOException. * * @param message The message being sent. * @throws IOException This exception is thrown if trust cannot be * established by the configured MessageTrustDecider. * @see MessageTrustDecider */ protected void makeTrustDecision() throws IOException { MessageTrustDecider decider2 = outMessage.get(MessageTrustDecider.class); if (trustDecider != null || decider2 != null) { try { // We must connect or we will not get the credentials. // The call is (said to be) ignored internally if // already connected. HttpsURLConnectionInfo info = getHttpsURLConnectionInfo(); if (trustDecider != null) { trustDecider.establishTrust( conduitName, info, outMessage); if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Trust Decider " + trustDecider.getLogicalName() + " considers Conduit " + conduitName + " trusted."); } } if (decider2 != null) { decider2.establishTrust(conduitName, info, outMessage); if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Trust Decider " + decider2.getLogicalName() + " considers Conduit " + conduitName + " trusted."); } } } catch (UntrustedURLConnectionIOException untrustedEx) { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Trust Decider " + (trustDecider != null ? trustDecider.getLogicalName() : decider2.getLogicalName()) + " considers Conduit " + conduitName + " untrusted.", untrustedEx); } throw untrustedEx; } } else { // This case, when there is no trust decider, a trust // decision should be a matter of policy. if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "No Trust Decider for Conduit '" + conduitName + "'. An afirmative Trust Decision is assumed."); } } } } private static void checkSameBaseUriRedirect(String conduitName, String lastURL, String newURL, Message message) throws IOException { if (newURL != null && MessageUtils.isTrue(message.getContextualProperty(AUTO_REDIRECT_SAME_HOST_ONLY))) { URI newUri = URI.create(newURL); URI lastUri = URI.create(lastURL); // This can be further restricted to make sure newURL completely contains lastURL // though making sure the same HTTP scheme and host are preserved should be enough if (!newUri.getScheme().equals(lastUri.getScheme()) || !newUri.getHost().equals(lastUri.getHost())) { String msg = "Different HTTP Scheme or Host Redirect detected on Conduit '" + conduitName + "' on '" + newURL + "'"; LOG.log(Level.INFO, msg); throw new IOException(msg); } } } // http://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-23#section-7.1.2 // Relative Location values are also supported private static String convertToAbsoluteUrlIfNeeded(String conduitName, String lastURL, String newURL, Message message) throws IOException { if (newURL != null && !newURL.startsWith("http")) { if (MessageUtils.isTrue(message.getContextualProperty(AUTO_REDIRECT_ALLOW_REL_URI))) { int queryInd = lastURL.lastIndexOf('?'); String query = queryInd == -1 ? null : lastURL.substring(queryInd); String newAbsURL = queryInd == -1 ? lastURL : lastURL.substring(0, queryInd); if (newAbsURL.endsWith("/")) { newAbsURL = newAbsURL.substring(0, newAbsURL.length() - 1); } newAbsURL = newAbsURL + newURL; if (query != null) { if (newAbsURL.lastIndexOf("?") != -1) { newAbsURL += "&"; query = query.substring(1); } newAbsURL += query; } return newAbsURL; } else { String msg = "Relative Redirect detected on Conduit '" + conduitName + "' on '" + newURL + "'"; LOG.log(Level.INFO, msg); throw new IOException(msg); } } else { return newURL; } } private static void detectRedirectLoop(String conduitName, String lastURL, String newURL, Message message) throws IOException { @SuppressWarnings("unchecked") Set visitedURLs = (Set) message.get(KEY_VISITED_URLS); if (visitedURLs == null) { visitedURLs = new HashSet(); message.put(KEY_VISITED_URLS, visitedURLs); } visitedURLs.add(lastURL); if (newURL != null) { if (visitedURLs.contains(newURL)) { // See if we are being redirected in a loop as best we can, // using string equality on URL. // We are in a redirect loop; -- bail String msg = "Redirect loop detected on Conduit '" + conduitName + "' on '" + newURL + "'"; LOG.log(Level.INFO, msg); throw new IOException(msg); } // Important to prevent looping on relative URIs if (!newURL.startsWith("http")) { visitedURLs.add(newURL); } } } private static void detectAuthorizationLoop(String conduitName, Message message, URI currentURL, String realm) throws IOException { @SuppressWarnings("unchecked") Set authURLs = (Set) message.get(KEY_AUTH_URLS); if (authURLs == null) { authURLs = new HashSet(); message.put(KEY_AUTH_URLS, authURLs); } // If we have been here (URL & Realm) before for this particular message // retransmit, it means we have already supplied information // which must have been wrong, or we wouldn't be here again. // Otherwise, the server may be 401 looping us around the realms. if (authURLs.contains(currentURL.toString() + realm)) { String logMessage = "Authorization loop detected on Conduit \"" + conduitName + "\" on URL \"" + currentURL + "\" with realm \"" + realm + "\""; if (LOG.isLoggable(Level.INFO)) { LOG.log(Level.INFO, logMessage); } throw new IOException(logMessage); } // Register that we have been here before we go. authURLs.add(currentURL.toString() + realm); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy