You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by db...@apache.org on 2021/05/03 05:28:55 UTC
[tomee-jakarta] 03/06: cxf-rt-transports-http changes since 3.4.3
This is an automated email from the ASF dual-hosted git repository.
dblevins pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tomee-jakarta.git
commit 4aefc31fd3a2b11fdcc07bd9b84ef9033bcb833d
Author: David Blevins <da...@gmail.com>
AuthorDate: Sun May 2 21:52:12 2021 -0700
cxf-rt-transports-http changes since 3.4.3
---
.../transport/http/AbstractHTTPDestination.java | 956 ++++++++++
.../cxf/transport/http/CXFAuthenticator.java | 177 ++
.../org/apache/cxf/transport/http/HTTPConduit.java | 1952 ++++++++++++++++++++
.../cxf/transport/http/HTTPTransportFactory.java | 297 +++
.../org/apache/cxf/transport/http/Headers.java | 583 ++++++
.../transport/http/HttpServletRequestSnapshot.java | 277 +++
.../transport/http/ReferencingAuthenticator.java | 234 +++
.../http/Servlet3ContinuationProvider.java | 281 +++
.../transport/http/URLConnectionHTTPConduit.java | 439 +++++
.../cxf/transport/http/auth/HttpAuthHeader.java | 154 ++
.../transport/https/HttpsURLConnectionFactory.java | 247 +++
.../transport/https/HttpsURLConnectionInfo.java | 168 ++
.../servicelist/ServiceListJAASAuthenticator.java | 160 ++
13 files changed, 5925 insertions(+)
diff --git a/transform/src/patch/java/org/apache/cxf/transport/http/AbstractHTTPDestination.java b/transform/src/patch/java/org/apache/cxf/transport/http/AbstractHTTPDestination.java
new file mode 100644
index 0000000..4bee0d3
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/http/AbstractHTTPDestination.java
@@ -0,0 +1,956 @@
+/**
+ * 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.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.namespace.QName;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.attachment.AttachmentDataSource;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.Base64Exception;
+import org.apache.cxf.common.util.Base64Utility;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.configuration.Configurable;
+import org.apache.cxf.configuration.security.AuthorizationPolicy;
+import org.apache.cxf.continuations.ContinuationProvider;
+import org.apache.cxf.continuations.SuspendedInvocationException;
+import org.apache.cxf.helpers.HttpHeaderHelper;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.interceptor.Interceptor;
+import org.apache.cxf.io.AbstractWrappedOutputStream;
+import org.apache.cxf.io.CopyingOutputStream;
+import org.apache.cxf.io.DelegatingInputStream;
+import org.apache.cxf.message.Attachment;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.ExchangeImpl;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageImpl;
+import org.apache.cxf.message.MessageUtils;
+import org.apache.cxf.policy.PolicyDataEngine;
+import org.apache.cxf.security.SecurityContext;
+import org.apache.cxf.security.transport.TLSSessionInfo;
+import org.apache.cxf.service.model.EndpointInfo;
+import org.apache.cxf.transport.AbstractDestination;
+import org.apache.cxf.transport.AbstractMultiplexDestination;
+import org.apache.cxf.transport.Assertor;
+import org.apache.cxf.transport.Conduit;
+import org.apache.cxf.transport.http.policy.impl.ServerPolicyCalculator;
+import org.apache.cxf.transport.https.CertConstraints;
+import org.apache.cxf.transport.https.CertConstraintsInterceptor;
+import org.apache.cxf.transports.http.configuration.HTTPServerPolicy;
+import org.apache.cxf.ws.addressing.AddressingProperties;
+import org.apache.cxf.ws.addressing.ContextUtils;
+import org.apache.cxf.ws.addressing.EndpointReferenceType;
+import org.apache.cxf.ws.addressing.EndpointReferenceUtils;
+
+/**
+ * Common base for HTTP Destination implementations.
+ */
+public abstract class AbstractHTTPDestination
+ extends AbstractMultiplexDestination
+ implements Configurable, Assertor {
+
+ public static final String HTTP_REQUEST = "HTTP.REQUEST";
+ public static final String HTTP_RESPONSE = "HTTP.RESPONSE";
+ public static final String HTTP_CONTEXT = "HTTP.CONTEXT";
+ public static final String HTTP_CONFIG = "HTTP.CONFIG";
+ public static final String HTTP_CONTEXT_MATCH_STRATEGY = "HTTP_CONTEXT_MATCH_STRATEGY";
+
+ public static final String RESPONSE_HEADERS_COPIED = "http.headers.copied";
+ public static final String RESPONSE_COMMITED = "http.response.done";
+ public static final String REQUEST_REDIRECTED = "http.request.redirected";
+ public static final String CXF_CONTINUATION_MESSAGE = "cxf.continuation.message";
+ public static final String CXF_ASYNC_CONTEXT = "cxf.async.context";
+
+ public static final String SERVICE_REDIRECTION = "http.service.redirection";
+ private static final String HTTP_BASE_PATH = "http.base.path";
+
+ private static final String SSL_CIPHER_SUITE_ATTRIBUTE = "javax.servlet.request.cipher_suite";
+ private static final String SSL_PEER_CERT_CHAIN_ATTRIBUTE = "javax.servlet.request.X509Certificate";
+
+ private static final String DECODE_BASIC_AUTH_WITH_ISO8859 = "decode.basicauth.with.iso8859";
+
+ private static final Logger LOG = LogUtils.getL7dLogger(AbstractHTTPDestination.class);
+
+ protected final Bus bus;
+ protected DestinationRegistry registry;
+ protected final String path;
+
+ // Configuration values
+ protected volatile HTTPServerPolicy serverPolicy;
+ protected String contextMatchStrategy = "stem";
+ protected boolean fixedParameterOrder;
+ protected boolean multiplexWithAddress;
+ protected CertConstraints certConstraints;
+ protected boolean isServlet3;
+ protected boolean decodeBasicAuthWithIso8859;
+ protected ContinuationProviderFactory cproviderFactory;
+ protected boolean enableWebSocket;
+
+ private volatile boolean serverPolicyCalced;
+
+ /**
+ * Constructor
+ *
+ * @param b the associated Bus
+ * @param registry the destination registry
+ * @param ei the endpoint info of the destination
+ * @param path the path
+ * @param dp true for adding the default port if it is missing
+ * @throws IOException
+ */
+ public AbstractHTTPDestination(Bus b,
+ DestinationRegistry registry,
+ EndpointInfo ei,
+ String path,
+ boolean dp)
+ throws IOException {
+ super(b, getTargetReference(getAddressValue(ei, dp), b), ei);
+ this.bus = b;
+ this.registry = registry;
+ this.path = path;
+ try {
+ ServletRequest.class.getMethod("isAsyncSupported");
+ isServlet3 = true;
+ } catch (Throwable t) {
+ //servlet 2.5 or earlier, no async support
+ }
+ decodeBasicAuthWithIso8859 = PropertyUtils.isTrue(bus.getProperty(DECODE_BASIC_AUTH_WITH_ISO8859));
+
+ initConfig();
+ }
+
+ public Bus getBus() {
+ return bus;
+ }
+
+ private AuthorizationPolicy getAuthorizationPolicyFromMessage(String credentials, SecurityContext sc) {
+ if (credentials == null || StringUtils.isEmpty(credentials.trim())) {
+ return null;
+ }
+
+ final String[] creds = credentials.split(" ");
+ String authType = creds[0];
+ if ("Basic".equals(authType) && creds.length == 2) {
+ String authEncoded = creds[1];
+ try {
+ byte[] authBytes = Base64Utility.decode(authEncoded);
+
+ if (authBytes == null) {
+ throw new Base64Exception(new Throwable("Invalid Base64 data."));
+ }
+
+ String authDecoded = decodeBasicAuthWithIso8859
+ ? new String(authBytes, StandardCharsets.ISO_8859_1) : new String(authBytes);
+
+ int idx = authDecoded.indexOf(':');
+ final String username;
+ String password = null;
+ if (idx == -1) {
+ username = authDecoded;
+ } else {
+ username = authDecoded.substring(0, idx);
+ if (idx < (authDecoded.length() - 1)) {
+ password = authDecoded.substring(idx + 1);
+ }
+ }
+
+ AuthorizationPolicy policy = sc.getUserPrincipal() == null
+ ? new AuthorizationPolicy() : new PrincipalAuthorizationPolicy(sc);
+ policy.setUserName(username);
+ policy.setPassword(password);
+ policy.setAuthorizationType(authType);
+ return policy;
+ } catch (Base64Exception ex) {
+ // Invalid authentication => treat as not authenticated or use the Principal
+ }
+ }
+ if (sc.getUserPrincipal() != null) {
+ AuthorizationPolicy policy = new PrincipalAuthorizationPolicy(sc);
+ policy.setAuthorization(credentials);
+ policy.setAuthorizationType(authType);
+ return policy;
+ }
+ return null;
+ }
+ public static final class PrincipalAuthorizationPolicy extends AuthorizationPolicy {
+ final SecurityContext sc;
+ public PrincipalAuthorizationPolicy(SecurityContext sc) {
+ this.sc = sc;
+ }
+ public Principal getPrincipal() {
+ return sc.getUserPrincipal();
+ }
+ @Override
+ public String getUserName() {
+ String name = super.getUserName();
+ if (name != null) {
+ return name;
+ }
+ Principal pp = getPrincipal();
+ return pp != null ? pp.getName() : null;
+ }
+ }
+
+ /**
+ * @param message the message under consideration
+ * @return true iff the message has been marked as oneway
+ */
+ protected final boolean isOneWay(Message message) {
+ return MessageUtils.isOneWay(message);
+ }
+
+ public void invoke(final ServletConfig config,
+ final ServletContext context,
+ final HttpServletRequest req,
+ final HttpServletResponse resp) throws IOException {
+ Message inMessage = retrieveFromContinuation(req);
+ if (inMessage == null) {
+ LOG.fine("Create a new message for processing");
+ inMessage = new MessageImpl();
+ ExchangeImpl exchange = new ExchangeImpl();
+ exchange.setInMessage(inMessage);
+ setupMessage(inMessage,
+ config,
+ context,
+ req,
+ resp);
+
+ exchange.setSession(new HTTPSession(req));
+ ((MessageImpl)inMessage).setDestination(this);
+ } else {
+ LOG.fine("Get the message from the request for processing");
+ }
+
+ copyKnownRequestAttributes(req, inMessage);
+
+ try {
+ incomingObserver.onMessage(inMessage);
+ invokeComplete(context, req, resp, inMessage);
+ } catch (SuspendedInvocationException ex) {
+ if (ex.getRuntimeException() != null) {
+ throw ex.getRuntimeException();
+ }
+ //else nothing to do, just finishing the processing
+ } catch (Fault ex) {
+ Throwable cause = ex.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException)cause;
+ }
+ throw ex;
+ } catch (RuntimeException ex) {
+ throw ex;
+ } finally {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Finished servicing http request on thread: " + Thread.currentThread());
+ }
+ }
+ }
+
+ protected void invokeComplete(final ServletContext context,
+ final HttpServletRequest req,
+ final HttpServletResponse resp,
+ Message m) throws IOException {
+ ContinuationProvider p = m.get(ContinuationProvider.class);
+ if (p != null) {
+ p.complete();
+ }
+ }
+
+ private void copyKnownRequestAttributes(HttpServletRequest request, Message message) {
+ message.put(SERVICE_REDIRECTION, request.getAttribute(SERVICE_REDIRECTION));
+ }
+
+ protected void setupMessage(final Message inMessage,
+ final ServletConfig config,
+ final ServletContext context,
+ final HttpServletRequest req,
+ final HttpServletResponse resp) throws IOException {
+ setupContinuation(inMessage,
+ req,
+ resp);
+
+ final Exchange exchange = inMessage.getExchange();
+ DelegatingInputStream in = new DelegatingInputStream(req.getInputStream()) {
+ public void cacheInput() {
+ if (!cached && (exchange.isOneWay() || isWSAddressingReplyToSpecified(exchange))) {
+ //For one-ways and WS-Addressing invocations with ReplyTo address,
+ //we need to cache the values of the HttpServletRequest
+ //so they can be queried later for things like paths and schemes
+ //and such like that.
+ //Please note, exchange used to always get the "current" message
+ exchange.getInMessage().put(HTTP_REQUEST, new HttpServletRequestSnapshot(req));
+ }
+ super.cacheInput();
+ }
+ private boolean isWSAddressingReplyToSpecified(Exchange ex) {
+ AddressingProperties map = ContextUtils.retrieveMAPs(ex.getInMessage(), false, false, false);
+ return map != null && !ContextUtils.isGenericAddress(map.getReplyTo());
+ }
+ };
+
+ inMessage.setContent(DelegatingInputStream.class, in);
+ inMessage.setContent(InputStream.class, in);
+ inMessage.put(HTTP_REQUEST, req);
+ inMessage.put(HTTP_RESPONSE, resp);
+ inMessage.put(HTTP_CONTEXT, context);
+ inMessage.put(HTTP_CONFIG, config);
+ inMessage.put(HTTP_CONTEXT_MATCH_STRATEGY, contextMatchStrategy);
+
+ inMessage.put(Message.HTTP_REQUEST_METHOD, req.getMethod());
+ String requestURI = req.getRequestURI();
+ inMessage.put(Message.REQUEST_URI, requestURI);
+ String requestURL = req.getRequestURL().toString();
+ inMessage.put(Message.REQUEST_URL, requestURL);
+ String contextPath = req.getContextPath();
+ if (contextPath == null) {
+ contextPath = "";
+ }
+ String servletPath = req.getServletPath();
+ if (servletPath == null) {
+ servletPath = "";
+ }
+ String contextServletPath = contextPath + servletPath;
+ String pathInfo = req.getPathInfo();
+ if (pathInfo != null) {
+ inMessage.put(Message.PATH_INFO, contextServletPath + pathInfo);
+ } else {
+ inMessage.put(Message.PATH_INFO, requestURI);
+ }
+ if (!StringUtils.isEmpty(requestURI)) {
+ int index = requestURL.indexOf(requestURI);
+ if (index > 0) {
+ // Can be useful for referencing resources with URIs not covered by CXFServlet.
+ // For example, if we a have web application name 'app' and CXFServlet listening
+ // on "/services/*" then having HTTP_BASE_PATH pointing to say
+ // http://localhost:8080/app will make it easy to refer to non CXF resources
+ String schemaInfo = requestURL.substring(0, index);
+ String basePathWithContextOnly = schemaInfo + contextPath;
+ inMessage.put(HTTP_BASE_PATH, basePathWithContextOnly);
+ }
+ } else if (!StringUtils.isEmpty(servletPath) && requestURL.endsWith(servletPath)) {
+ int index = requestURL.lastIndexOf(servletPath);
+ if (index > 0) {
+ inMessage.put(HTTP_BASE_PATH, requestURL.substring(0, index));
+ }
+ }
+ String contentType = req.getContentType();
+ inMessage.put(Message.CONTENT_TYPE, contentType);
+ setEncoding(inMessage, req, contentType);
+
+ inMessage.put(Message.QUERY_STRING, req.getQueryString());
+
+ inMessage.put(Message.ACCEPT_CONTENT_TYPE, req.getHeader("Accept"));
+ String basePath = getBasePath(contextServletPath);
+ if (!StringUtils.isEmpty(basePath)) {
+ inMessage.put(Message.BASE_PATH, basePath);
+ }
+ inMessage.put(Message.FIXED_PARAMETER_ORDER, isFixedParameterOrder());
+ inMessage.put(Message.ASYNC_POST_RESPONSE_DISPATCH, Boolean.TRUE);
+
+ SecurityContext httpSecurityContext = new SecurityContext() {
+ public Principal getUserPrincipal() {
+ return req.getUserPrincipal();
+ }
+ public boolean isUserInRole(String role) {
+ return req.isUserInRole(role);
+ }
+ };
+
+ inMessage.put(SecurityContext.class, httpSecurityContext);
+
+
+ Headers headers = new Headers(inMessage);
+ headers.copyFromRequest(req);
+ String credentials = headers.getAuthorization();
+ AuthorizationPolicy authPolicy = getAuthorizationPolicyFromMessage(credentials,
+ httpSecurityContext);
+ inMessage.put(AuthorizationPolicy.class, authPolicy);
+
+ propogateSecureSession(req, inMessage);
+
+ inMessage.put(CertConstraints.class.getName(), certConstraints);
+ inMessage.put(Message.IN_INTERCEPTORS,
+ Arrays.asList(new Interceptor[] {CertConstraintsInterceptor.INSTANCE}));
+
+ }
+ /**
+ * Propogate in the message a TLSSessionInfo instance representative
+ * of the TLS-specific information in the HTTP request.
+ *
+ * @param request the Jetty request
+ * @param message the Message
+ */
+ private static void propogateSecureSession(HttpServletRequest request,
+ Message message) {
+ final String cipherSuite =
+ (String) request.getAttribute(SSL_CIPHER_SUITE_ATTRIBUTE);
+ if (cipherSuite != null) {
+ final java.security.cert.Certificate[] certs =
+ (java.security.cert.Certificate[]) request.getAttribute(SSL_PEER_CERT_CHAIN_ATTRIBUTE);
+ message.put(TLSSessionInfo.class,
+ new TLSSessionInfo(cipherSuite,
+ null,
+ certs));
+ }
+ }
+ private String setEncoding(final Message inMessage,
+ final HttpServletRequest req,
+ final String contentType) throws IOException {
+
+ String enc = HttpHeaderHelper.findCharset(contentType);
+ if (enc == null) {
+ enc = req.getCharacterEncoding();
+ }
+ // work around a bug with Jetty which results in the character
+ // encoding not being trimmed correctly.
+ if (enc != null && enc.endsWith("\"")) {
+ enc = enc.substring(0, enc.length() - 1);
+ }
+ if (enc != null || "POST".equals(req.getMethod()) || "PUT".equals(req.getMethod())) {
+ //allow gets/deletes/options to not specify an encoding
+ String normalizedEncoding = HttpHeaderHelper.mapCharset(enc);
+ if (normalizedEncoding == null) {
+ String m = new org.apache.cxf.common.i18n.Message("INVALID_ENCODING_MSG",
+ LOG, enc).toString();
+ LOG.log(Level.WARNING, m);
+ throw new IOException(m);
+ }
+ inMessage.put(Message.ENCODING, normalizedEncoding);
+ }
+ return contentType;
+ }
+ protected Message retrieveFromContinuation(HttpServletRequest req) {
+ if (!isServlet3) {
+ if (cproviderFactory != null) {
+ return cproviderFactory.retrieveFromContinuation(req);
+ }
+ return null;
+ }
+ return retrieveFromServlet3Async(req);
+ }
+
+ protected Message retrieveFromServlet3Async(HttpServletRequest req) {
+ try {
+ return (Message)req.getAttribute(CXF_CONTINUATION_MESSAGE);
+ } catch (Throwable ex) {
+ // the request may not implement the Servlet3 API
+ }
+ return null;
+ }
+
+ protected void setupContinuation(Message inMessage,
+ final HttpServletRequest req,
+ final HttpServletResponse resp) {
+ try {
+ if (isServlet3 && req.isAsyncSupported()) {
+ inMessage.put(ContinuationProvider.class.getName(),
+ new Servlet3ContinuationProvider(req, resp, inMessage));
+ } else if (cproviderFactory != null) {
+ ContinuationProvider p = cproviderFactory.createContinuationProvider(inMessage, req, resp);
+ if (p != null) {
+ inMessage.put(ContinuationProvider.class.getName(), p);
+ }
+ }
+ } catch (Throwable ex) {
+ // the request may not implement the Servlet3 API
+ }
+ }
+ protected String getBasePath(String contextPath) throws IOException {
+ if (StringUtils.isEmpty(endpointInfo.getAddress())) {
+ return "";
+ }
+ return new URL(endpointInfo.getAddress()).getPath();
+ }
+
+ protected static EndpointInfo getAddressValue(EndpointInfo ei) {
+ return getAddressValue(ei, true);
+ }
+
+ protected static EndpointInfo getAddressValue(EndpointInfo ei, boolean dp) {
+ if (dp) {
+
+ String eiAddress = ei.getAddress();
+ if (eiAddress == null) {
+ try {
+ ServerSocket s = new ServerSocket(0);
+ ei.setAddress("http://localhost:" + s.getLocalPort());
+ s.close();
+ return ei;
+ } catch (IOException ex) {
+ // problem allocating a random port, go to the default one
+ ei.setAddress("http://localhost");
+ }
+ }
+
+ String addr = StringUtils.addDefaultPortIfMissing(ei.getAddress());
+ if (addr != null) {
+ ei.setAddress(addr);
+ }
+ }
+ return ei;
+ }
+
+ /**
+ * @param inMessage the incoming message
+ * @return the inbuilt backchannel
+ */
+ protected Conduit getInbuiltBackChannel(Message inMessage) {
+ HttpServletResponse response = (HttpServletResponse)inMessage.get(HTTP_RESPONSE);
+ return new BackChannelConduit(response);
+ }
+
+ private void initConfig() {
+
+ cproviderFactory = bus.getExtension(ContinuationProviderFactory.class);
+ }
+
+ private synchronized HTTPServerPolicy calcServerPolicyInternal(Message m) {
+ HTTPServerPolicy sp = serverPolicy;
+ if (!serverPolicyCalced) {
+ PolicyDataEngine pde = bus.getExtension(PolicyDataEngine.class);
+ if (pde != null) {
+ sp = pde.getServerEndpointPolicy(m, endpointInfo, this, new ServerPolicyCalculator());
+ }
+ if (null == sp) {
+ sp = endpointInfo.getTraversedExtensor(
+ new HTTPServerPolicy(), HTTPServerPolicy.class);
+ }
+ serverPolicy = sp;
+ serverPolicyCalced = true;
+ }
+ return sp;
+ }
+ private HTTPServerPolicy calcServerPolicy(Message m) {
+ HTTPServerPolicy sp = serverPolicy;
+ if (!serverPolicyCalced) {
+ sp = calcServerPolicyInternal(m);
+ }
+ return sp;
+ }
+
+ /**
+ * On first write, we need to make sure any attachments and such that are still on the incoming stream
+ * are read in. Otherwise we can get into a deadlock where the client is still trying to send the
+ * request, but the server is trying to send the response. Neither side is reading and both blocked
+ * on full buffers. Not a good situation.
+ * @param outMessage
+ */
+ private void cacheInput(Message outMessage) {
+ if (outMessage.getExchange() == null) {
+ return;
+ }
+ Message inMessage = outMessage.getExchange().getInMessage();
+ if (inMessage == null) {
+ return;
+ }
+ Object o = inMessage.get("cxf.io.cacheinput");
+ DelegatingInputStream in = inMessage.getContent(DelegatingInputStream.class);
+ if (PropertyUtils.isTrue(o)) {
+ Collection<Attachment> atts = inMessage.getAttachments();
+ if (atts != null) {
+ for (Attachment a : atts) {
+ if (a.getDataHandler().getDataSource() instanceof AttachmentDataSource) {
+ try {
+ ((AttachmentDataSource)a.getDataHandler().getDataSource()).cache(inMessage);
+ } catch (IOException e) {
+ throw new Fault(e);
+ }
+ }
+ }
+ }
+ if (in != null) {
+ in.cacheInput();
+ }
+ } else if (in != null) {
+ //We don't need to cache it, but we may need to consume it in order for the client
+ // to be able to receive a response. (could be blocked sending)
+ //However, also don't want to consume indefinitely. We'll limit to 16M.
+ try {
+ IOUtils.consume(in, 16 * 1024 * 1024);
+ } catch (Exception ioe) {
+ //ignore
+ }
+ }
+ }
+
+ protected OutputStream flushHeaders(Message outMessage) throws IOException {
+ return flushHeaders(outMessage, true);
+ }
+ protected OutputStream flushHeaders(Message outMessage, boolean getStream) throws IOException {
+ if (isResponseRedirected(outMessage)) {
+ return null;
+ }
+
+ cacheInput(outMessage);
+ HTTPServerPolicy sp = calcServerPolicy(outMessage);
+ if (sp != null) {
+ new Headers(outMessage).setFromServerPolicy(sp);
+ }
+
+ OutputStream responseStream = null;
+ boolean oneWay = isOneWay(outMessage);
+
+ HttpServletResponse response = getHttpResponseFromMessage(outMessage);
+
+ int responseCode = MessageUtils.getReponseCodeFromMessage(outMessage);
+ if (responseCode >= 300) {
+ String ec = (String)outMessage.get(Message.ERROR_MESSAGE);
+ if (!StringUtils.isEmpty(ec)) {
+ response.sendError(responseCode, ec);
+ return null;
+ }
+ }
+ response.setStatus(responseCode);
+ new Headers(outMessage).copyToResponse(response);
+
+ outMessage.put(RESPONSE_HEADERS_COPIED, "true");
+
+ if (MessageUtils.hasNoResponseContent(outMessage)) {
+ response.setContentLength(0);
+ response.flushBuffer();
+ closeResponseOutputStream(response);
+ } else if (!getStream) {
+ closeResponseOutputStream(response);
+ } else {
+ responseStream = response.getOutputStream();
+ }
+
+ if (oneWay) {
+ outMessage.remove(HTTP_RESPONSE);
+ }
+ return responseStream;
+ }
+
+ private void closeResponseOutputStream(HttpServletResponse response) throws IOException {
+ try {
+ response.getOutputStream().close();
+ } catch (IllegalStateException ex) {
+ // response.getWriter() has already been called
+ }
+ }
+
+
+ private HttpServletResponse getHttpResponseFromMessage(Message message) throws IOException {
+ Object responseObj = message.get(HTTP_RESPONSE);
+ if (responseObj instanceof HttpServletResponse) {
+ return (HttpServletResponse)responseObj;
+ } else if (null != responseObj) {
+ String m = (new org.apache.cxf.common.i18n.Message("UNEXPECTED_RESPONSE_TYPE_MSG",
+ LOG, responseObj.getClass())).toString();
+ LOG.log(Level.WARNING, m);
+ throw new IOException(m);
+ } else {
+ String m = (new org.apache.cxf.common.i18n.Message("NULL_RESPONSE_MSG", LOG)).toString();
+ LOG.log(Level.WARNING, m);
+ throw new IOException(m);
+ }
+ }
+
+ private boolean isResponseRedirected(Message outMessage) {
+ Exchange exchange = outMessage.getExchange();
+ return exchange != null
+ && Boolean.TRUE.equals(exchange.get(REQUEST_REDIRECTED));
+ }
+
+ /**
+ * Backchannel conduit.
+ */
+ public class BackChannelConduit
+ extends AbstractDestination.AbstractBackChannelConduit {
+
+ protected HttpServletResponse response;
+
+ BackChannelConduit(HttpServletResponse resp) {
+ response = resp;
+ }
+
+ /**
+ * Send an outbound message, assumed to contain all the name-value
+ * mappings of the corresponding input message (if any).
+ *
+ * @param message the message to be sent.
+ */
+ public void prepare(Message message) throws IOException {
+ message.put(HTTP_RESPONSE, response);
+ OutputStream os = message.getContent(OutputStream.class);
+ if (os == null) {
+ message.setContent(OutputStream.class,
+ new WrappedOutputStream(message));
+ }
+ }
+
+ @Override
+ public void close(Message msg) throws IOException {
+ super.close(msg);
+ if (msg.getExchange() == null) {
+ return;
+ }
+ Message m = msg.getExchange().getInMessage();
+ if (m == null) {
+ return;
+ }
+ InputStream is = m.getContent(InputStream.class);
+ if (is != null) {
+ try {
+ is.close();
+ m.removeContent(InputStream.class);
+ } catch (IOException ioex) {
+ //ignore
+ }
+ }
+ }
+ }
+
+ /**
+ * Wrapper stream responsible for flushing headers and committing outgoing
+ * HTTP-level response.
+ */
+ private class WrappedOutputStream extends AbstractWrappedOutputStream implements CopyingOutputStream {
+
+ private Message outMessage;
+
+ WrappedOutputStream(Message m) {
+ super();
+ this.outMessage = m;
+ }
+
+
+ @Override
+ public int copyFrom(InputStream in) throws IOException {
+ if (!written) {
+ onFirstWrite();
+ written = true;
+ }
+ if (wrappedStream != null) {
+ return IOUtils.copy(in, wrappedStream);
+ }
+ return IOUtils.copy(in, this, IOUtils.DEFAULT_BUFFER_SIZE);
+ }
+
+ /**
+ * Perform any actions required on stream flush (freeze headers,
+ * reset output stream ... etc.)
+ */
+ protected void onFirstWrite() throws IOException {
+ OutputStream responseStream = flushHeaders(outMessage);
+ if (null != responseStream) {
+ wrappedStream = responseStream;
+ }
+ }
+
+ /**
+ * Perform any actions required on stream closure (handle response etc.)
+ */
+ public void close() throws IOException {
+ if (!written && wrappedStream == null) {
+ OutputStream responseStream = flushHeaders(outMessage, false);
+ if (null != responseStream) {
+ wrappedStream = responseStream;
+ }
+ }
+ if (wrappedStream != null) {
+ // closing the stream should indirectly call the servlet response's flushBuffer
+ wrappedStream.close();
+ }
+ /*
+ try {
+ //make sure the input stream is also closed in this
+ //case so that any resources it may have is cleaned up
+ Message m = outMessage.getExchange().getInMessage();
+ if (m != null) {
+ InputStream ins = m.getContent(InputStream.class);
+ if (ins != null) {
+ ins.close();
+ }
+ }
+ } catch (IOException ex) {
+ //ignore
+ }
+ */
+ }
+
+ }
+
+ protected boolean contextMatchOnExact() {
+ return "exact".equals(contextMatchStrategy);
+ }
+
+ public void finalizeConfig() {
+ }
+
+ public String getBeanName() {
+ String beanName = null;
+ if (endpointInfo.getName() != null) {
+ beanName = endpointInfo.getName().toString() + ".http-destination";
+ }
+ return beanName;
+ }
+
+ /*
+ * Implement multiplex via the address URL to avoid the need for ws-a.
+ * Requires contextMatchStrategy of stem.
+ *
+ * @see org.apache.cxf.transport.AbstractMultiplexDestination#getAddressWithId(java.lang.String)
+ */
+ public EndpointReferenceType getAddressWithId(String id) {
+ final EndpointReferenceType ref;
+
+ if (isMultiplexWithAddress()) {
+ String address = EndpointReferenceUtils.getAddress(reference);
+ ref = EndpointReferenceUtils.duplicate(reference);
+ if (address.endsWith("/")) {
+ EndpointReferenceUtils.setAddress(ref, address + id);
+ } else {
+ EndpointReferenceUtils.setAddress(ref, address + "/" + id);
+ }
+ } else {
+ ref = super.getAddressWithId(id);
+ }
+ return ref;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.cxf.transport.AbstractMultiplexDestination#getId(java.util.Map)
+ */
+ @Override
+ public String getId(Map<String, Object> context) {
+ String id = null;
+
+ if (isMultiplexWithAddress()) {
+ String address = (String)context.get(Message.PATH_INFO);
+ if (null != address) {
+ int afterLastSlashIndex = address.lastIndexOf('/') + 1;
+ if (afterLastSlashIndex > 0
+ && afterLastSlashIndex < address.length()) {
+ id = address.substring(afterLastSlashIndex);
+ }
+ } else {
+ getLogger().log(Level.WARNING,
+ new org.apache.cxf.common.i18n.Message(
+ "MISSING_PATH_INFO", LOG).toString());
+ }
+ } else {
+ return super.getId(context);
+ }
+ return id;
+ }
+
+ public String getContextMatchStrategy() {
+ return contextMatchStrategy;
+ }
+
+ public void setContextMatchStrategy(String contextMatchStrategy) {
+ this.contextMatchStrategy = contextMatchStrategy;
+ }
+
+ public boolean isFixedParameterOrder() {
+ return fixedParameterOrder;
+ }
+
+ public void setFixedParameterOrder(boolean fixedParameterOrder) {
+ this.fixedParameterOrder = fixedParameterOrder;
+ }
+
+ public boolean isMultiplexWithAddress() {
+ return multiplexWithAddress;
+ }
+
+ public void setMultiplexWithAddress(boolean multiplexWithAddress) {
+ this.multiplexWithAddress = multiplexWithAddress;
+ }
+
+ public HTTPServerPolicy getServer() {
+ return calcServerPolicy(null);
+ }
+
+ public void setServer(HTTPServerPolicy server) {
+ this.serverPolicy = server;
+ if (server != null) {
+ serverPolicyCalced = true;
+ }
+ }
+
+ public void assertMessage(Message message) {
+ PolicyDataEngine pde = bus.getExtension(PolicyDataEngine.class);
+ pde.assertMessage(message, calcServerPolicy(message), new ServerPolicyCalculator());
+ }
+
+ public boolean canAssert(QName type) {
+ return new ServerPolicyCalculator().getDataClassName().equals(type);
+ }
+
+ public void releaseRegistry() {
+ registry = null;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ protected void activate() {
+ synchronized (this) {
+ if (registry != null) {
+ registry.addDestination(this);
+ }
+ }
+ }
+ @Override
+ protected void deactivate() {
+ synchronized (this) {
+ if (registry != null) {
+ registry.removeDestination(path);
+ }
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ synchronized (this) {
+ if (registry != null) {
+ registry.removeDestination(path);
+ }
+ }
+ super.shutdown();
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/transport/http/CXFAuthenticator.java b/transform/src/patch/java/org/apache/cxf/transport/http/CXFAuthenticator.java
new file mode 100644
index 0000000..6b968f6
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/http/CXFAuthenticator.java
@@ -0,0 +1,177 @@
+/**
+ * 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.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.apache.cxf.common.util.ReflectionUtil;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.helpers.JavaUtils;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+import org.apache.cxf.transport.Conduit;
+
+/**
+ *
+ */
+public class CXFAuthenticator extends Authenticator {
+ static CXFAuthenticator instance;
+
+
+ public CXFAuthenticator() {
+ }
+
+ public static synchronized void addAuthenticator() {
+ if (instance == null) {
+ instance = new CXFAuthenticator();
+ Authenticator wrapped = null;
+ if (JavaUtils.isJava9Compatible()) {
+ try {
+ Method m = ReflectionUtil.getMethod(Authenticator.class, "getDefault");
+ wrapped = (Authenticator)m.invoke(null);
+ } catch (Exception e) {
+ // ignore
+ }
+
+
+ } else {
+ for (final Field f : ReflectionUtil.getDeclaredFields(Authenticator.class)) {
+ if (f.getType().equals(Authenticator.class)) {
+ ReflectionUtil.setAccessible(f);
+ try {
+ wrapped = (Authenticator)f.get(null);
+ if (wrapped != null && wrapped.getClass().getName()
+ .equals(ReferencingAuthenticator.class.getName())) {
+ Method m = wrapped.getClass().getMethod("check");
+ m.setAccessible(true);
+ m.invoke(wrapped);
+ }
+ wrapped = (Authenticator)f.get(null);
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ try {
+ Class<?> cls;
+ InputStream ins = ReferencingAuthenticator.class
+ .getResourceAsStream("ReferencingAuthenticator.class");
+ byte[] b = IOUtils.readBytesFromStream(ins);
+ if (JavaUtils.isJava9Compatible()) {
+ Class<?> methodHandles = Class.forName("java.lang.invoke.MethodHandles");
+ Method m = ReflectionUtil.getMethod(methodHandles, "lookup");
+ Object lookup = m.invoke(null);
+ m = ReflectionUtil.getMethod(lookup.getClass(), "findClass", String.class);
+ try {
+ cls = (Class<?>)m.invoke(lookup, "org.apache.cxf.transport.http.ReferencingAuthenticator");
+ } catch (InvocationTargetException e) {
+ //use defineClass as fallback
+ m = ReflectionUtil.getMethod(lookup.getClass(), "defineClass", byte[].class);
+ cls = (Class<?>)m.invoke(lookup, b);
+ }
+ } else {
+ ClassLoader loader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ public ClassLoader run() {
+ return new URLClassLoader(new URL[0], ClassLoader.getSystemClassLoader());
+ }
+ }, null);
+ Method m = ReflectionUtil.getDeclaredMethod(ClassLoader.class, "defineClass",
+ String.class, byte[].class, Integer.TYPE,
+ Integer.TYPE);
+
+ ReflectionUtil.setAccessible(m).invoke(loader, ReferencingAuthenticator.class.getName(),
+ b, 0, b.length);
+ cls = loader.loadClass(ReferencingAuthenticator.class.getName());
+ try {
+ //clear the acc field that can hold onto the webapp classloader
+ Field f = ReflectionUtil.getDeclaredField(loader.getClass(), "acc");
+ ReflectionUtil.setAccessible(f).set(loader, null);
+ } catch (Throwable t) {
+ //ignore
+ }
+ }
+ final Authenticator auth = (Authenticator)cls.getConstructor(Authenticator.class, Authenticator.class)
+ .newInstance(instance, wrapped);
+
+ if (System.getSecurityManager() == null) {
+ Authenticator.setDefault(auth);
+ } else {
+ AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+ public Boolean run() {
+ Authenticator.setDefault(auth);
+ return true;
+ }
+ });
+
+ }
+
+ } catch (Throwable t) {
+ //ignore
+ }
+ }
+ }
+
+ protected PasswordAuthentication getPasswordAuthentication() {
+ PasswordAuthentication auth = null;
+ Message m = PhaseInterceptorChain.getCurrentMessage();
+ if (m != null) {
+ Exchange exchange = m.getExchange();
+ Conduit conduit = exchange.getConduit(m);
+ if (conduit instanceof HTTPConduit) {
+ HTTPConduit httpConduit = (HTTPConduit)conduit;
+ if (getRequestorType() == RequestorType.PROXY
+ && httpConduit.getProxyAuthorization() != null) {
+ String un = httpConduit.getProxyAuthorization().getUserName();
+ String pwd = httpConduit.getProxyAuthorization().getPassword();
+ if (un != null && pwd != null) {
+ auth = new PasswordAuthentication(un, pwd.toCharArray());
+ }
+ } else if (getRequestorType() == RequestorType.SERVER
+ && httpConduit.getAuthorization() != null) {
+
+ if ("basic".equals(getRequestingScheme()) || "digest".equals(getRequestingScheme())) {
+ return null;
+ }
+
+ String un = httpConduit.getAuthorization().getUserName();
+ String pwd = httpConduit.getAuthorization().getPassword();
+ if (un != null && pwd != null) {
+ auth = new PasswordAuthentication(un, pwd.toCharArray());
+ }
+ }
+ }
+ }
+ // else PhaseInterceptorChain.getCurrentMessage() is null,
+ // this HTTP call has therefore not been generated by CXF
+ return auth;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/transport/http/HTTPConduit.java b/transform/src/patch/java/org/apache/cxf/transport/http/HTTPConduit.java
new file mode 100644
index 0000000..3904b10
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/http/HTTPConduit.java
@@ -0,0 +1,1952 @@
+/**
+ * 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.io.PrintWriter;
+import java.io.StringWriter;
+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.HashMap;
+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.common.util.PropertyUtils;
+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.CastUtils;
+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.Conduit;
+import org.apache.cxf.transport.MessageObserver;
+import org.apache.cxf.transport.http.auth.CustomAuthSupplier;
+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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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!
+ * <p>
+ * 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.
+ * <p>
+ * 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";
+ public static final String KEY_HTTP_CONNECTION_ADDRESS = "http.connection.address";
+
+ public static final String SET_HTTP_RESPONSE_MESSAGE = "org.apache.cxf.transport.http.set.response.message";
+ public static final String HTTP_RESPONSE_MESSAGE = "http.responseMessage";
+
+ public static final String PROCESS_FAULT_ON_HTTP_400 = "org.apache.cxf.transport.process_fault_on_http_400";
+ public static final String NO_IO_EXCEPTIONS = "org.apache.cxf.transport.no_io_exceptions";
+ /**
+ * 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 AUTO_REDIRECT_ALLOWED_URI = "http.redirect.allowed.uri";
+ private static final String AUTO_REDIRECT_MAX_SAME_URI_COUNT = "http.redirect.max.same.uri.count";
+
+ private static final String HTTP_POST_METHOD = "POST";
+ private static final String HTTP_GET_METHOD = "GET";
+ private static final Set<String> KNOWN_HTTP_VERBS_WITH_NO_CONTENT =
+ new HashSet<>(Arrays.asList(new String[]{"GET", "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 volatile Address defaultAddress;
+
+ 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 volatile HttpAuthSupplier authSupplier;
+
+ /**
+ * Implements the proxy authentication handling. If it is not set
+ * it will be created from the proxyAuthorizationPolicy.authType
+ */
+ protected volatile HttpAuthSupplier proxyAuthSupplier;
+
+ protected Cookies cookies;
+
+ protected CertConstraints certConstraints;
+
+ private volatile boolean clientSidePolicyCalced;
+
+
+ /**
+ * 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 ei 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();
+ }
+
+ /**
+ * 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(Message m) {
+ if (!clientSidePolicyCalced) {
+ PolicyDataEngine policyEngine = bus.getExtension(PolicyDataEngine.class);
+ if (policyEngine != null && endpointInfo.getService() != null) {
+ clientSidePolicy = policyEngine.getClientEndpointPolicy(m,
+ endpointInfo,
+ this,
+ new ClientPolicyCalculator());
+ if (clientSidePolicy != null) {
+ clientSidePolicy.removePropertyChangeListener(this); //make sure we aren't added twice
+ clientSidePolicy.addPropertyChangeListener(this);
+ }
+ }
+ }
+ clientSidePolicyCalced = true;
+ }
+
+ private void updateClientPolicy() {
+ if (!clientSidePolicyCalced) {
+ //do no spend time on building Message and Exchange (which basically
+ //are ConcurrentHashMap instances) if the policy is already available
+ Message m = new MessageImpl();
+ m.setExchange(new ExchangeImpl());
+ m.getExchange().put(EndpointInfo.class, this.endpointInfo);
+ updateClientPolicy(m);
+ }
+ }
+
+ /**
+ * 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());
+ }
+ }
+
+ /**
+ * Allow access to the cookies that the conduit is maintaining
+ * @return the sessionCookies map
+ */
+ public Map<String, Cookie> getCookies() {
+ return cookies.getSessionCookies();
+ }
+
+
+ protected abstract void setupConnection(Message message, Address address, HTTPClientPolicy csPolicy)
+ throws IOException;
+
+ /**
+ * Prepare to send an outbound HTTP message over this http conduit to a
+ * particular endpoint.
+ * <P>
+ * 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 "?".
+ * <P>
+ * If the Message.HTTP_REQUEST_METHOD property is NOT set, the
+ * Http request method defaults to "POST".
+ * <P>
+ * If the Message.PROTOCOL_HEADERS is not set on the message, it is
+ * initialized to an empty map.
+ * <P>
+ * 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.
+ Address currentAddress;
+ try {
+ currentAddress = setupAddress(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, currentAddress, 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);
+ }
+
+ if (this.proxyAuthSupplier == null) {
+ this.proxyAuthSupplier = createAuthSupplier(proxyAuthorizationPolicy);
+ }
+
+ if (this.authSupplier.requiresRequestCaching()) {
+ needToCacheRequest = true;
+ isChunking = false;
+ LOG.log(Level.FINE,
+ "Auth Supplier, but no Preemptive 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, currentAddress.getURI());
+ new Headers(message).setFromClientPolicy(getClient(message));
+
+ // set the OutputStream on the ProxyOutputStream
+ ProxyOutputStream pos = message.getContent(ProxyOutputStream.class);
+ if (pos != null && message.getContent(OutputStream.class) != null) {
+ pos.setWrappedOutputStream(createOutputStream(message,
+ needToCacheRequest,
+ isChunking,
+ chunkThreshold));
+ } else {
+ 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;
+ } else if (!HTTP_GET_METHOD.equals(httpMethod)) {
+ MessageContentsList objs = MessageContentsList.getContentsList(message);
+ if (objs != null && !objs.isEmpty()) {
+ Object obj = objs.get(0);
+ return obj.getClass() != String.class
+ || (obj.getClass() == String.class && ((String)obj).length() > 0);
+ }
+ }
+ return false;
+ }
+
+ protected abstract OutputStream createOutputStream(Message message,
+ boolean needToCacheRequest,
+ boolean isChunking,
+ int chunkThreshold) throws IOException;
+
+ private HttpAuthSupplier createAuthSupplier(AuthorizationPolicy authzPolicy) {
+ String authType = authzPolicy.getAuthorizationType();
+ if (HttpAuthHeader.AUTH_TYPE_NEGOTIATE.equals(authType)) {
+ return new SpnegoAuthSupplier();
+ } else if (HttpAuthHeader.AUTH_TYPE_DIGEST.equals(authType)) {
+ return new DigestAuthSupplier();
+ } else if (authType != null && !HttpAuthHeader.AUTH_TYPE_BASIC.equals(authType)
+ && authzPolicy.getAuthorization() != null) {
+ return new CustomAuthSupplier();
+ } 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".
+ * <p>
+ * 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 Address setupAddress(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);
+ setAndGetDefaultAddress();
+ if (result == null) {
+ if (pathInfo == null && queryString == null) {
+ if (defaultAddress != null) {
+ message.put(Message.ENDPOINT_ADDRESS, defaultAddress.getString());
+ }
+ return defaultAddress;
+ }
+ if (defaultAddress != null) {
+ result = defaultAddress.getString();
+ 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;
+ }
+ if (defaultAddress == null) {
+ return setAndGetDefaultAddress(result);
+ }
+ return result.equals(defaultAddress.getString()) ? defaultAddress : new Address(result);
+ }
+
+ /**
+ * Close the conduit
+ */
+ public void close() {
+ if (clientSidePolicy != null) {
+ clientSidePolicy.removePropertyChangeListener(this);
+ }
+ }
+
+ /**
+ * @return the default target address
+ */
+ public String getAddress() {
+ if (defaultAddress != null) {
+ return defaultAddress.getString();
+ } else if (fromEndpointReferenceType) {
+ return getTarget().getAddress().getValue();
+ }
+ return endpointInfo.getAddress();
+ }
+
+ /**
+ * @return the default target URL
+ */
+ protected URI getURI() throws URISyntaxException {
+ return setAndGetDefaultAddress().getURI();
+ }
+
+ private Address setAndGetDefaultAddress() throws URISyntaxException {
+ if (defaultAddress == null) {
+ synchronized (this) {
+ if (defaultAddress == null) {
+ if (fromEndpointReferenceType && getTarget().getAddress().getValue() != null) {
+ defaultAddress = new Address(this.getTarget().getAddress().getValue());
+ } else if (endpointInfo.getAddress() != null) {
+ defaultAddress = new Address(endpointInfo.getAddress());
+ }
+ }
+ }
+ }
+ return defaultAddress;
+ }
+
+ private Address setAndGetDefaultAddress(String curAddr) throws URISyntaxException {
+ if (defaultAddress == null) {
+ synchronized (this) {
+ if (defaultAddress == null) {
+ if (curAddr != null) {
+ defaultAddress = new Address(curAddr);
+ } else {
+ throw new URISyntaxException("<null>",
+ "Invalid address. Endpoint address cannot be null.", 0);
+ }
+ }
+ }
+ }
+ return defaultAddress;
+ }
+ /**
+ * This call places HTTP Header strings into the headers that are relevant
+ * to the Authorization policies that are set on this conduit by
+ * configuration.
+ * <p>
+ * 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.
+ * <p>
+ * 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 currentURI
+ */
+ 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 = proxyAuthSupplier.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);
+ updateClientPolicy(message);
+ 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() {
+ updateClientPolicy();
+ 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.clientSidePolicyCalced = true;
+ 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<String, List<String>> headers) throws MalformedURLException {
+ for (Map.Entry<String, List<String>> head : headers.entrySet()) {
+ if ("Location".equalsIgnoreCase(head.getKey())) {
+ List<String> locs = head.getValue();
+ if (locs != null && !locs.isEmpty()) {
+ String location = locs.get(0);
+ if (location != null) {
+ return location;
+ }
+ 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) {
+ logStackTrace(e);
+ }
+ }
+ }
+
+ protected void logStackTrace(Throwable ex) {
+ StringWriter sw = new StringWriter();
+ ex.printStackTrace(new PrintWriter(sw));
+ LOG.warning(sw.toString());
+ }
+
+ public void assertMessage(Message message) {
+ PolicyDataEngine policyDataEngine = bus.getExtension(PolicyDataEngine.class);
+ policyDataEngine.assertMessage(message, getClient(), new ClientPolicyCalculator());
+ }
+
+ public boolean canAssert(QName type) {
+ return type.equals(new QName("http://cxf.apache.org/transports/http/configuration", "client"));
+ }
+
+ 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);
+ boolean exceptionSet = outMessage.getContent(Exception.class) != null;
+ if (!exceptionSet) {
+ 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().getBus()
+ .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);
+ 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 ("GET".equals(getMethod()) || cachedStream == null) {
+ 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.");
+ }
+ 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)
+ || PropertyUtils.isTrue(outMessage.get(Headers.EMPTY_REQUEST_PROPERTY))) {
+ 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());
+ }
+ boolean exceptionSet = outMessage.getContent(Exception.class) != null;
+ if (!written && !exceptionSet) {
+ 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 extends Exception> T mapException(String msg,
+ T ex, Class<T> cls) {
+ T ex2;
+ 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
+ || getClient().isAutoRedirect() && KNOWN_HTTP_VERBS_WITH_NO_CONTENT.contains(getMethod())
+ || authSupplier != null && authSupplier.requiresRequestCaching()) {
+
+ if (LOG.isLoggable(Level.FINE) && cachedStream != null) {
+ StringBuilder b = new StringBuilder(4096);
+ b.append("Conduit \"").append(getConduitName())
+ .append("\" Transmit cached message to: ")
+ .append(url)
+ .append(": ");
+ cachedStream.writeCacheTo(b, 16L * 1024L);
+ LOG.fine(b.toString());
+ }
+
+
+ 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:
+ case 308:
+ return redirectRetransmit();
+ case HttpURLConnection.HTTP_UNAUTHORIZED:
+ case HttpURLConnection.HTTP_PROXY_AUTH:
+ 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 {
+ newURL = convertToAbsoluteUrlIfNeeded(conduitName, urlString, newURL, outMessage);
+ detectRedirectLoop(conduitName, urlString, newURL, outMessage);
+ checkAllowedRedirectUri(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);
+ }
+ cookies.writeToMessageHeaders(outMessage);
+ outMessage.put("transport.retransmit.url", newURL);
+ retransmit(newURL);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This method performs a retransmit for authorization information.
+ *
+ * @return true if there was a retransmit
+ * @throws IOException
+ */
+ protected boolean authorizationRetransmit() throws IOException {
+ Message m = new MessageImpl();
+ updateResponseHeaders(m);
+ List<String> authHeaderValues = Headers.getSetProtocolHeaders(m).get("WWW-Authenticate");
+ if (authHeaderValues == null) {
+ LOG.warning("WWW-Authenticate response header is not set");
+ return false;
+ }
+ HttpAuthHeader authHeader = new HttpAuthHeader(authHeaderValues);
+ 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 if 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, int responseCode) {
+ // 1. Not oneWay
+ if (!isOneway(message.getExchange())) {
+ return true;
+ }
+ // 2. Robust OneWays could have a fault
+ return responseCode == 500 && MessageUtils.getContextualBoolean(message, Message.ROBUST_ONEWAY, false);
+ }
+
+ protected int doProcessResponseCode() throws IOException {
+ Exchange exchange = outMessage.getExchange();
+ int rc = getResponseCode();
+ if (rc == -1) {
+ LOG.warning("HTTP Response code appears to be corrupted");
+ }
+ if (exchange != null) {
+ exchange.put(Message.RESPONSE_CODE, rc);
+ if (rc == 404 || rc == 503 || rc == 429) {
+ exchange.put("org.apache.cxf.transport.service_not_available", true);
+ }
+ }
+
+ // "org.apache.cxf.transport.no_io_exceptions" property should be set in case the exceptions
+ // should not be handled here; for example jax rs uses this
+
+ // "org.apache.cxf.transport.process_fault_on_http_400" property should be set in case a
+ // soap fault because of a HTTP 400 should be returned back to the client (SOAP 1.2 spec)
+
+ if (rc >= 400 && rc != 500
+ && !MessageUtils.getContextualBoolean(outMessage, NO_IO_EXCEPTIONS)
+ && (rc > 400 || !MessageUtils.getContextualBoolean(outMessage, PROCESS_FAULT_ON_HTTP_400))) {
+
+ throw new HTTPException(rc, getResponseMessage(), url.toURL());
+ }
+ return rc;
+ }
+
+ protected void handleResponseInternal() throws IOException {
+ Exchange exchange = outMessage.getExchange();
+ int responseCode = doProcessResponseCode();
+
+ 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 (MessageUtils.getContextualBoolean(outMessage, SET_HTTP_RESPONSE_MESSAGE, false)) {
+ inMessage.put(HTTP_RESPONSE_MESSAGE, getResponseMessage());
+ }
+ propagateConduit(exchange, inMessage);
+
+ if ((!doProcessResponse(outMessage, responseCode)
+ || HttpURLConnection.HTTP_ACCEPTED == responseCode)
+ && MessageUtils.getContextualBoolean(outMessage,
+ Message.PROCESS_202_RESPONSE_ONEWAY_OR_PARTIAL, true)) {
+ in = getPartialResponse();
+ if (in == null
+ || !MessageUtils.getContextualBoolean(outMessage, Message.PROCESS_ONEWAY_RESPONSE, false)) {
+ // oneway operation or decoupled MEP without
+ // partial response
+ closeInputStream();
+ if (isOneway(exchange) && responseCode > 300) {
+ throw new HTTPException(responseCode, getResponseMessage(), url.toURL());
+ }
+ //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")) {
+ // remove callback so that it won't be invoked twice
+ ClientCallback cc = exchange.remove(ClientCallback.class);
+ if (null != cc) {
+ cc.handleResponse(null, null);
+ }
+ }
+ exchange.put("IN_CHAIN_COMPLETE", Boolean.TRUE);
+
+ exchange.setInMessage(inMessage);
+ 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 propagateConduit(Exchange exchange, Message in) {
+ if (exchange != null) {
+ Message out = exchange.getOutMessage();
+ if (out != null) {
+ in.put(Conduit.class, out.get(Conduit.class));
+ }
+ }
+ }
+
+ 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.
+ *
+ * @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 affirmative Trust Decision is assumed.");
+ }
+ }
+ }
+ }
+
+ private static void checkAllowedRedirectUri(String conduitName,
+ String lastURL,
+ String newURL,
+ Message message) throws IOException {
+ if (newURL != null) {
+ URI newUri = URI.create(newURL);
+
+ if (MessageUtils.getContextualBoolean(message, AUTO_REDIRECT_SAME_HOST_ONLY)) {
+
+ 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);
+ }
+ }
+
+ String allowedRedirectURI = (String)message.getContextualProperty(AUTO_REDIRECT_ALLOWED_URI);
+ if (allowedRedirectURI != null && !newURL.startsWith(allowedRedirectURI)) {
+ String msg = "Forbidden Redirect URI " + newURL + "detected on Conduit '" + conduitName;
+ 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.getContextualBoolean(message, AUTO_REDIRECT_ALLOW_REL_URI)) {
+ return URI.create(lastURL).resolve(newURL).toString();
+ }
+ String msg = "Relative Redirect detected on Conduit '"
+ + conduitName + "' on '" + newURL + "'";
+ LOG.log(Level.INFO, msg);
+ throw new IOException(msg);
+ }
+ return newURL;
+
+ }
+
+ private static void detectRedirectLoop(String conduitName,
+ String lastURL,
+ String newURL,
+ Message message) throws IOException {
+ Map<String, Integer> visitedURLs = CastUtils.cast((Map<?, ?>)message.get(KEY_VISITED_URLS));
+ if (visitedURLs == null) {
+ visitedURLs = new HashMap<>();
+ message.put(KEY_VISITED_URLS, visitedURLs);
+ }
+ Integer visitCount = visitedURLs.get(lastURL);
+ if (visitCount == null) {
+ visitCount = 1;
+ } else {
+ visitCount++;
+ }
+ visitedURLs.put(lastURL, visitCount);
+
+ Integer newURLCount = visitedURLs.get(newURL);
+ if (newURL != null && newURLCount != null) {
+ // See if we are being redirected in a loop as best we can,
+ // using string equality on URL.
+ boolean invalidLoopDetected = newURL.equals(lastURL);
+
+ Integer maxSameURICount = PropertyUtils.getInteger(message, AUTO_REDIRECT_MAX_SAME_URI_COUNT);
+
+ if (!invalidLoopDetected) {
+ // This new URI was already recorded earlier even though it is not equal to the last URI
+ // Example: a-b-a, where 'a' is the new URI. Check if a limited number of occurrences of this URI
+ // is allowed, fail by default.
+ if (maxSameURICount == null || newURLCount > maxSameURICount) {
+ invalidLoopDetected = true;
+ }
+ } else if (maxSameURICount != null && newURLCount <= maxSameURICount) {
+ // This new URI was already recorded earlier and is the same as the last URI.
+ // Example: a-a. But we have a property supporting a limited number of occurrences of this URI.
+ // Continue the invocation.
+ invalidLoopDetected = false;
+ }
+ if (invalidLoopDetected) {
+ // 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);
+ }
+ }
+ }
+ private static void detectAuthorizationLoop(String conduitName, Message message,
+ URI currentURL, String realm) throws IOException {
+ @SuppressWarnings("unchecked")
+ Set<String> authURLs = (Set<String>) 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.add(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);
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/transport/http/HTTPTransportFactory.java b/transform/src/patch/java/org/apache/cxf/transport/http/HTTPTransportFactory.java
new file mode 100644
index 0000000..b9088b9
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/http/HTTPTransportFactory.java
@@ -0,0 +1,297 @@
+/**
+ * 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.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.injection.NoJSR250Annotations;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.configuration.Configurer;
+import org.apache.cxf.service.Service;
+import org.apache.cxf.service.model.BindingInfo;
+import org.apache.cxf.service.model.EndpointInfo;
+import org.apache.cxf.service.model.ServiceInfo;
+import org.apache.cxf.transport.AbstractTransportFactory;
+import org.apache.cxf.transport.Conduit;
+import org.apache.cxf.transport.ConduitInitiator;
+import org.apache.cxf.transport.Destination;
+import org.apache.cxf.transport.DestinationFactory;
+import org.apache.cxf.transport.servlet.ServletDestinationFactory;
+import org.apache.cxf.ws.addressing.EndpointReferenceType;
+import org.apache.cxf.wsdl.http.AddressType;
+
+/**
+ *
+ */
+@NoJSR250Annotations
+public class HTTPTransportFactory
+ extends AbstractTransportFactory
+ implements ConduitInitiator, DestinationFactory {
+
+
+ public static final List<String> DEFAULT_NAMESPACES
+ = Collections.unmodifiableList(Arrays.asList(
+ "http://cxf.apache.org/transports/http",
+ "http://cxf.apache.org/transports/http/configuration",
+ "http://schemas.xmlsoap.org/wsdl/http",
+ "http://schemas.xmlsoap.org/wsdl/http/"
+ ));
+
+ private static final Logger LOG = LogUtils.getL7dLogger(HTTPTransportFactory.class);
+
+ /**
+ * This constant holds the prefixes served by this factory.
+ */
+ private static final Set<String> URI_PREFIXES = new HashSet<>();
+ static {
+ URI_PREFIXES.add("http://");
+ URI_PREFIXES.add("https://");
+ }
+
+ protected DestinationRegistry registry;
+
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
+ private final Lock r = lock.readLock();
+ private final Lock w = lock.writeLock();
+
+ public HTTPTransportFactory() {
+ this(new DestinationRegistryImpl());
+ }
+
+ public HTTPTransportFactory(DestinationRegistry registry) {
+ this(DEFAULT_NAMESPACES, registry);
+ }
+
+ protected HTTPTransportFactory(List<String> transportIds, DestinationRegistry registry) {
+ super(transportIds);
+ if (registry == null) {
+ registry = new DestinationRegistryImpl();
+ }
+ this.registry = registry;
+ }
+
+ public DestinationRegistry getRegistry() {
+ return registry;
+ }
+
+ public void setRegistry(DestinationRegistry newRegistry) {
+ w.lock();
+ try {
+ if (registry.getDestinations().isEmpty()) {
+ this.registry = newRegistry;
+ } else {
+ String m = new org.apache.cxf.common.i18n.Message("CANNOT_CHANGE_REGISTRY_ALREADY_IN_USE",
+ LOG).toString();
+ LOG.log(Level.SEVERE, m);
+ throw new RuntimeException(m);
+ }
+ } finally {
+ w.unlock();
+ }
+ }
+
+ /**
+ * This call is used by CXF ExtensionManager to inject the activationNamespaces
+ * @param ans The transport ids.
+ */
+ public void setActivationNamespaces(Collection<String> ans) {
+ setTransportIds(new ArrayList<>(ans));
+ }
+
+ public EndpointInfo createEndpointInfo(
+ ServiceInfo serviceInfo,
+ BindingInfo b,
+ List<?> ees
+ ) {
+ if (ees != null) {
+ for (Iterator<?> itr = ees.iterator(); itr.hasNext();) {
+ Object extensor = itr.next();
+
+ if (extensor instanceof AddressType) {
+ final AddressType httpAdd = (AddressType)extensor;
+
+ EndpointInfo info =
+ new HttpEndpointInfo(serviceInfo,
+ "http://schemas.xmlsoap.org/wsdl/http/");
+ info.setAddress(httpAdd.getLocation());
+ info.addExtensor(httpAdd);
+ return info;
+ }
+ }
+ }
+
+ HttpEndpointInfo hei = new HttpEndpointInfo(serviceInfo,
+ "http://schemas.xmlsoap.org/wsdl/http/");
+ AddressType at = new AddressType();
+ hei.addExtensor(at);
+
+ return hei;
+ }
+
+ public void createPortExtensors(EndpointInfo ei, Service service) {
+ // TODO
+ }
+
+ public Set<String> getUriPrefixes() {
+ return URI_PREFIXES;
+ }
+
+ /**
+ * This call uses the Configurer from the bus to configure
+ * a bean.
+ *
+ * @param bean
+ */
+ protected void configure(Bus b, Object bean) {
+ configure(b, bean, null, null);
+ }
+
+ protected void configure(Bus bus, Object bean, String name, String extraName) {
+ Configurer configurer = bus.getExtension(Configurer.class);
+ if (null != configurer) {
+ configurer.configureBean(name, bean);
+ if (extraName != null) {
+ configurer.configureBean(extraName, bean);
+ }
+ }
+ }
+
+ private static class HttpEndpointInfo extends EndpointInfo {
+ AddressType saddress;
+ HttpEndpointInfo(ServiceInfo serv, String trans) {
+ super(serv, trans);
+ }
+ public void setAddress(String s) {
+ super.setAddress(s);
+ if (saddress != null) {
+ saddress.setLocation(s);
+ }
+ }
+
+ public void addExtensor(Object el) {
+ super.addExtensor(el);
+ if (el instanceof AddressType) {
+ saddress = (AddressType)el;
+ }
+ }
+ }
+
+ /**
+ * This call creates a new HTTPConduit for the endpoint. It is equivalent
+ * to calling getConduit without an EndpointReferenceType.
+ */
+ public Conduit getConduit(EndpointInfo endpointInfo, Bus bus) throws IOException {
+ return getConduit(endpointInfo, endpointInfo.getTarget(), bus);
+ }
+
+ /**
+ * This call creates a new HTTP Conduit based on the EndpointInfo and
+ * EndpointReferenceType.
+ * TODO: What are the formal constraints on EndpointInfo and
+ * EndpointReferenceType values?
+ */
+ public Conduit getConduit(
+ EndpointInfo endpointInfo,
+ EndpointReferenceType target,
+ Bus bus
+ ) throws IOException {
+
+ HTTPConduitFactory factory = findFactory(endpointInfo, bus);
+ HTTPConduit conduit = null;
+ if (factory != null) {
+ conduit = factory.createConduit(this, bus, endpointInfo, target);
+ }
+ if (conduit == null) {
+ conduit = new URLConnectionHTTPConduit(bus, endpointInfo, target);
+ }
+
+ // Spring configure the conduit.
+ String address = conduit.getAddress();
+ if (address != null && address.indexOf('?') != -1) {
+ address = address.substring(0, address.indexOf('?'));
+ }
+ HTTPConduitConfigurer c1 = bus.getExtension(HTTPConduitConfigurer.class);
+ if (c1 != null) {
+ c1.configure(conduit.getBeanName(), address, conduit);
+ }
+ configure(bus, conduit, conduit.getBeanName(), address);
+ conduit.finalizeConfig();
+ return conduit;
+ }
+
+ protected HTTPConduitFactory findFactory(EndpointInfo endpointInfo, Bus bus) {
+ HTTPConduitFactory f = endpointInfo.getProperty(HTTPConduitFactory.class.getName(), HTTPConduitFactory.class);
+ if (f == null) {
+ f = bus.getExtension(HTTPConduitFactory.class);
+ }
+ return f;
+ }
+ public Destination getDestination(EndpointInfo endpointInfo, Bus bus) throws IOException {
+ if (endpointInfo == null) {
+ throw new IllegalArgumentException("EndpointInfo cannot be null");
+ }
+ r.lock();
+ try {
+ synchronized (registry) {
+ AbstractHTTPDestination d = registry.getDestinationForPath(endpointInfo.getAddress());
+ if (d == null) {
+ HttpDestinationFactory jettyFactory = bus.getExtension(HttpDestinationFactory.class);
+ String addr = endpointInfo.getAddress();
+ if (jettyFactory == null && addr != null && addr.startsWith("http")) {
+ String m =
+ new org.apache.cxf.common.i18n.Message("NO_HTTP_DESTINATION_FACTORY_FOUND",
+ LOG).toString();
+ LOG.log(Level.SEVERE, m);
+ throw new IOException(m);
+ }
+ final HttpDestinationFactory factory;
+ if (jettyFactory != null && (addr == null || addr.startsWith("http"))) {
+ factory = jettyFactory;
+ } else {
+ factory = new ServletDestinationFactory();
+ }
+
+ d = factory.createDestination(endpointInfo, bus, registry);
+ registry.addDestination(d);
+ configure(bus, d);
+ d.finalizeConfig();
+ }
+ return d;
+ }
+ } finally {
+ r.unlock();
+ }
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/transport/http/Headers.java b/transform/src/patch/java/org/apache/cxf/transport/http/Headers.java
new file mode 100644
index 0000000..db2dbff
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/http/Headers.java
@@ -0,0 +1,583 @@
+/**
+ * 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.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URLConnection;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TimeZone;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.helpers.HttpHeaderHelper;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
+import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
+import org.apache.cxf.transports.http.configuration.HTTPServerPolicy;
+import org.apache.cxf.version.Version;
+
+public class Headers {
+ /**
+ * 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";
+ /**
+ * Each header value is added as a separate HTTP header, example, given A header with 'a' and 'b'
+ * values, two A headers will be added as opposed to a single A header with the "a,b" value.
+ */
+ public static final String ADD_HEADERS_PROPERTY = "org.apache.cxf.http.add-headers";
+
+ public static final String PROTOCOL_HEADERS_CONTENT_TYPE = Message.CONTENT_TYPE.toLowerCase();
+ public static final String HTTP_HEADERS_SETCOOKIE = "Set-Cookie";
+ public static final String HTTP_HEADERS_LINK = "Link";
+ public static final String EMPTY_REQUEST_PROPERTY = "org.apache.cxf.empty.request";
+ private static final String SET_EMPTY_REQUEST_CT_PROPERTY = "set.content.type.for.empty.request";
+ private static final TimeZone TIME_ZONE_GMT = TimeZone.getTimeZone("GMT");
+ private static final Logger LOG = LogUtils.getL7dLogger(Headers.class);
+
+ private static final List<String> SENSITIVE_HEADERS = Arrays.asList("Authorization", "Proxy-Authorization");
+ private static final List<Object> SENSITIVE_HEADER_MARKER = Arrays.asList("***");
+ private static final String ALLOW_LOGGING_SENSITIVE_HEADERS = "allow.logging.sensitive.headers";
+ private static final String USER_AGENT = initUserAgent();
+
+ private final Message message;
+ private final Map<String, List<String>> headers;
+
+ public Headers(Message message) {
+ this.message = message;
+ this.headers = getSetProtocolHeaders(message);
+ }
+ public Headers() {
+ this.headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ this.message = null;
+ }
+
+ public static String getUserAgent() {
+ return USER_AGENT;
+ }
+
+ private static String initUserAgent() {
+ String name = Version.getName();
+ if ("Apache CXF".equals(name)) {
+ name = "Apache-CXF";
+ }
+ String version = Version.getCurrentVersion();
+ return name + "/" + version;
+ }
+
+ /**
+ * Returns a traceable string representation of the passed-in headers map.
+ * The value for any keys in the map that are in the <code>SENSITIVE_HEADERS</code>
+ * array will be filtered out of the returned string.
+ * Note that this method is expensive as it will copy the map (except for the
+ * filtered keys), so it should be used sparingly - i.e. only when debug is
+ * enabled.
+ */
+ static String toString(Map<String, List<Object>> headers, boolean logSensitiveHeaders) {
+ Map<String, List<Object>> filteredHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ filteredHeaders.putAll(headers);
+ if (!logSensitiveHeaders) {
+ for (String filteredKey : SENSITIVE_HEADERS) {
+ filteredHeaders.put(filteredKey, SENSITIVE_HEADER_MARKER);
+ }
+ }
+ return filteredHeaders.toString();
+ }
+
+ public Map<String, List<String>> headerMap() {
+ return headers;
+ }
+
+
+ /**
+ * Write cookie header from given session cookies
+ *
+ * @param sessionCookies
+ */
+ public void writeSessionCookies(Map<String, Cookie> sessionCookies) {
+ List<String> cookies = null;
+ for (String s : headers.keySet()) {
+ if (HttpHeaderHelper.COOKIE.equalsIgnoreCase(s)) {
+ cookies = headers.remove(s);
+ break;
+ }
+ }
+ if (cookies == null) {
+ cookies = new ArrayList<>();
+ } else {
+ cookies = new ArrayList<>(cookies);
+ }
+ headers.put(HttpHeaderHelper.COOKIE, cookies);
+ for (Cookie c : sessionCookies.values()) {
+ cookies.add(c.requestCookieHeader());
+ }
+ }
+
+ /**
+ * This call places HTTP Header strings into the headers that are relevant
+ * to the ClientPolicy that is set on this conduit by configuration.
+ *
+ * REVISIT: A cookie is set statically from configuration?
+ */
+ void setFromClientPolicy(HTTPClientPolicy policy) {
+ if (policy == null) {
+ return;
+ }
+ if (policy.isSetCacheControl()) {
+ headers.put("Cache-Control",
+ createMutableList(policy.getCacheControl()));
+ }
+ if (policy.isSetHost()) {
+ headers.put("Host",
+ createMutableList(policy.getHost()));
+ }
+ if (policy.isSetConnection()) {
+ headers.put("Connection",
+ createMutableList(policy.getConnection().value()));
+ }
+ if (policy.isSetAccept()) {
+ headers.put("Accept",
+ createMutableList(policy.getAccept()));
+ } else if (!headers.containsKey("Accept")) {
+ headers.put("Accept", createMutableList("*/*"));
+ }
+ if (policy.isSetAcceptEncoding()) {
+ headers.put("Accept-Encoding",
+ createMutableList(policy.getAcceptEncoding()));
+ }
+ if (policy.isSetAcceptLanguage()) {
+ headers.put("Accept-Language",
+ createMutableList(policy.getAcceptLanguage()));
+ }
+ if (policy.isSetContentType()) {
+ message.put(Message.CONTENT_TYPE, policy.getContentType());
+ }
+ if (policy.isSetCookie()) {
+ headers.put("Cookie",
+ createMutableList(policy.getCookie()));
+ }
+ if (policy.isSetBrowserType()) {
+ headers.put("User-Agent",
+ createMutableList(policy.getBrowserType()));
+ }
+ if (policy.isSetReferer()) {
+ headers.put("Referer",
+ createMutableList(policy.getReferer()));
+ }
+ }
+
+ void setFromServerPolicy(HTTPServerPolicy policy) {
+ if (policy.isSetCacheControl()) {
+ headers.put("Cache-Control",
+ createMutableList(policy.getCacheControl()));
+ }
+ if (policy.isSetContentLocation()) {
+ headers.put("Content-Location",
+ createMutableList(policy.getContentLocation()));
+ }
+ if (policy.isSetContentEncoding()) {
+ headers.put("Content-Encoding",
+ createMutableList(policy.getContentEncoding()));
+ }
+ if (policy.isSetContentType()) {
+ headers.put(HttpHeaderHelper.CONTENT_TYPE,
+ createMutableList(policy.getContentType()));
+ }
+ if (policy.isSetServerType()) {
+ headers.put("Server",
+ createMutableList(policy.getServerType()));
+ }
+ if (policy.isSetHonorKeepAlive() && !policy.isHonorKeepAlive()) {
+ headers.put("Connection",
+ createMutableList("close"));
+ } else if (policy.isSetKeepAliveParameters()) {
+ headers.put("Keep-Alive", createMutableList(policy.getKeepAliveParameters()));
+ }
+
+
+ /*
+ * TODO - hook up these policies
+ <xs:attribute name="SuppressClientSendErrors" type="xs:boolean" use="optional" default="false">
+ <xs:attribute name="SuppressClientReceiveErrors" type="xs:boolean" use="optional" default="false">
+ */
+ }
+
+ public void removeAuthorizationHeaders() {
+ headers.remove("Authorization");
+ headers.remove("Proxy-Authorization");
+ }
+
+ public void setAuthorization(String authorization) {
+ headers.put("Authorization",
+ createMutableList(authorization));
+ }
+
+ public void setProxyAuthorization(String authorization) {
+ headers.put("Proxy-Authorization",
+ createMutableList(authorization));
+ }
+
+
+ /**
+ * While extracting the Message.PROTOCOL_HEADERS property from the Message,
+ * this call ensures that the Message.PROTOCOL_HEADERS property is
+ * set on the Message. If it is not set, an empty map is placed there, and
+ * then returned.
+ *
+ * @param message The outbound message
+ * @return The PROTOCOL_HEADERS map
+ */
+ public static Map<String, List<String>> getSetProtocolHeaders(final Message message) {
+ Map<String, List<String>> headers =
+ CastUtils.cast((Map<?, ?>)message.get(Message.PROTOCOL_HEADERS));
+ if (null == headers) {
+ headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ } else if (headers instanceof HashMap) {
+ Map<String, List<String>> headers2
+ = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ headers2.putAll(headers);
+ headers = headers2;
+ }
+ message.put(Message.PROTOCOL_HEADERS, headers);
+ return headers;
+ }
+
+ public void readFromConnection(HttpURLConnection connection) {
+ Map<String, List<String>> origHeaders = connection.getHeaderFields();
+ headers.clear();
+ for (Entry<String, List<String>> entry : origHeaders.entrySet()) {
+ if (entry.getKey() != null) {
+ String key = HttpHeaderHelper.getHeaderKey(entry.getKey());
+ List<String> old = headers.get(key);
+ if (old != null) {
+ List<String> nl = new ArrayList<>(old.size() + entry.getValue().size());
+ nl.addAll(old);
+ nl.addAll(entry.getValue());
+ headers.put(key, nl);
+ } else {
+ headers.put(key, entry.getValue());
+ }
+ }
+ }
+ }
+
+ private static List<String> createMutableList(String val) {
+ return new ArrayList<>(Arrays.asList(val));
+ }
+
+ /**
+ * This procedure logs the PROTOCOL_HEADERS from the
+ * Message at the specified logging level.
+ *
+ * @param logger The Logger to log to.
+ * @param level The Logging Level.
+ * @param headersMap The Message protocol headers.
+ * @param logSensitiveHeaders whether to log sensitive headers
+ */
+ static void logProtocolHeaders(Logger logger, Level level,
+ Map<String, List<Object>> headersMap,
+ boolean logSensitiveHeaders) {
+ if (logger.isLoggable(level)) {
+ for (Map.Entry<String, List<Object>> entry : headersMap.entrySet()) {
+ String key = entry.getKey();
+ boolean sensitive = !logSensitiveHeaders && SENSITIVE_HEADERS.contains(key);
+ List<Object> headerList = sensitive ? SENSITIVE_HEADER_MARKER : entry.getValue();
+ for (Object value : headerList) {
+ logger.log(level, key + ": "
+ + (value == null ? "<null>" : value.toString()));
+ }
+ }
+ }
+ }
+
+ /**
+ * Set content type and protocol headers (Message.PROTOCOL_HEADERS) headers into the URL
+ * connection.
+ * Note, this does not mean they immediately get written to the output
+ * stream or the wire. They just just get set on the HTTP request.
+ *
+ * @param connection
+ * @throws IOException
+ */
+ public void setProtocolHeadersInConnection(HttpURLConnection connection) throws IOException {
+ // If no Content-Type is set for empty requests then HttpUrlConnection:
+ // - sets a form Content-Type for empty POST
+ // - replaces custom Accept value with */* if HTTP proxy is used
+ boolean contentTypeSet = headers.containsKey(Message.CONTENT_TYPE);
+ if (!contentTypeSet) {
+ // if CT is not set then assume it has to be set by default
+ boolean dropContentType = false;
+ boolean getRequest = "GET".equals(message.get(Message.HTTP_REQUEST_METHOD));
+ boolean emptyRequest = getRequest || PropertyUtils.isTrue(message.get(EMPTY_REQUEST_PROPERTY));
+ // If it is an empty request (without a request body) then check further if CT still needs be set
+ if (emptyRequest) {
+ Object setCtForEmptyRequestProp = message.getContextualProperty(SET_EMPTY_REQUEST_CT_PROPERTY);
+ if (setCtForEmptyRequestProp != null) {
+ // If SET_EMPTY_REQUEST_CT_PROPERTY is set then do as a user prefers.
+ // CT will be dropped if setting CT for empty requests was explicitly disabled
+ dropContentType = PropertyUtils.isFalse(setCtForEmptyRequestProp);
+ } else if (getRequest) {
+ // otherwise if it is GET then just drop it
+ dropContentType = true;
+ }
+ }
+ if (!dropContentType) {
+ String ct = emptyRequest && !contentTypeSet ? "*/*" : determineContentType();
+ connection.setRequestProperty(HttpHeaderHelper.CONTENT_TYPE, ct);
+ }
+ } else {
+ connection.setRequestProperty(HttpHeaderHelper.CONTENT_TYPE, determineContentType());
+ }
+
+ transferProtocolHeadersToURLConnection(connection);
+
+ Map<String, List<Object>> theHeaders = CastUtils.cast(headers);
+ logProtocolHeaders(LOG, Level.FINE, theHeaders, logSensitiveHeaders());
+ }
+
+ public String determineContentType() {
+ String ct;
+ List<Object> ctList = CastUtils.cast(headers.get(Message.CONTENT_TYPE));
+ if (ctList != null && ctList.size() == 1 && ctList.get(0) != null) {
+ ct = ctList.get(0).toString();
+ } else {
+ ct = (String)message.get(Message.CONTENT_TYPE);
+ }
+
+ String enc = (String)message.get(Message.ENCODING);
+
+ if (null != ct) {
+ if (enc != null
+ && ct.indexOf("charset=") == -1
+ && !ct.toLowerCase().contains("multipart/related")) {
+ ct = ct + "; charset=" + enc;
+ }
+ } else if (enc != null) {
+ ct = "text/xml; charset=" + enc;
+ } else {
+ ct = "text/xml";
+ }
+ return ct;
+ }
+
+ /**
+ * This procedure sets the URLConnection request properties
+ * from the PROTOCOL_HEADERS in the message.
+ */
+ private void transferProtocolHeadersToURLConnection(URLConnection connection) {
+ boolean addHeaders = MessageUtils.getContextualBoolean(message, ADD_HEADERS_PROPERTY, false);
+ for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+ String header = entry.getKey();
+ if (HttpHeaderHelper.CONTENT_TYPE.equalsIgnoreCase(header)) {
+ continue;
+ }
+
+ List<String> headerList = entry.getValue();
+ if (addHeaders || HttpHeaderHelper.COOKIE.equalsIgnoreCase(header)) {
+ headerList.forEach(s -> connection.addRequestProperty(header, s));
+ } else {
+ connection.setRequestProperty(header, String.join(",", headerList));
+ }
+ }
+ // make sure we don't add more than one User-Agent header
+ if (connection.getRequestProperty("User-Agent") == null) {
+ connection.addRequestProperty("User-Agent", USER_AGENT);
+ }
+ }
+
+ /**
+ * Copy the request headers into the message.
+ *
+ * @param req the current servlet request
+ */
+ protected void copyFromRequest(HttpServletRequest req) {
+
+ //TODO how to deal with the fields
+ for (Enumeration<String> e = req.getHeaderNames(); e.hasMoreElements();) {
+ String fname = e.nextElement();
+ String mappedName = HttpHeaderHelper.getHeaderKey(fname);
+ List<String> values = headers.get(mappedName);
+ if (values == null) {
+ values = new ArrayList<>();
+ headers.put(mappedName, values);
+ }
+ for (Enumeration<String> e2 = req.getHeaders(fname); e2.hasMoreElements();) {
+ String val = e2.nextElement();
+ if ("Accept".equals(mappedName) && !values.isEmpty()) {
+ //ensure we collapse Accept into first line
+ String firstAccept = values.get(0);
+ firstAccept = firstAccept + ", " + val;
+ values.set(0, firstAccept);
+ }
+ values.add(val);
+ }
+ }
+ if (!headers.containsKey(Message.CONTENT_TYPE)) {
+ headers.put(Message.CONTENT_TYPE, Collections.singletonList(req.getContentType()));
+ }
+ if (LOG.isLoggable(Level.FINE)) {
+ Map<String, List<Object>> theHeaders = CastUtils.cast(headers);
+ LOG.log(Level.FINE, "Request Headers: " + toString(theHeaders,
+ logSensitiveHeaders()));
+ }
+ }
+
+ private boolean logSensitiveHeaders() {
+ // Not allowed by default
+ return PropertyUtils.isTrue(message.getContextualProperty(ALLOW_LOGGING_SENSITIVE_HEADERS));
+ }
+
+ private String getContentTypeFromMessage() {
+ final String ct = (String)message.get(Message.CONTENT_TYPE);
+ final String enc = (String)message.get(Message.ENCODING);
+
+ if (null != ct
+ && null != enc
+ && ct.indexOf("charset=") == -1
+ && !ct.toLowerCase().contains("multipart/related")) {
+ return ct + "; charset=" + enc;
+ }
+ return ct;
+ }
+
+ // Assumes that response body is not available only
+ // if Content-Length is available and set to 0
+ private boolean isResponseBodyAvailable() {
+ List<String> ctLen = headers.get("Content-Length");
+ if (ctLen == null || ctLen.size() != 1) {
+ return true;
+ }
+ try {
+ if (Integer.parseInt(ctLen.get(0)) == 0) {
+ return false;
+ }
+ } catch (NumberFormatException ex) {
+ // ignore
+ }
+ return true;
+ }
+
+ private boolean isSingleHeader(String header) {
+ return HTTP_HEADERS_SETCOOKIE.equalsIgnoreCase(header) || HTTP_HEADERS_LINK.equalsIgnoreCase(header);
+ }
+
+ /**
+ * Copy the response headers into the response.
+ *
+ * @param response the current ServletResponse
+ */
+ protected void copyToResponse(HttpServletResponse response) {
+ String contentType = getContentTypeFromMessage();
+
+ if (!headers.containsKey(Message.CONTENT_TYPE) && contentType != null
+ && isResponseBodyAvailable()) {
+ response.setContentType(contentType);
+ }
+
+ boolean addHeaders = MessageUtils.getContextualBoolean(message, ADD_HEADERS_PROPERTY, false);
+ for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+ String header = entry.getKey();
+ List<?> headerList = entry.getValue();
+
+ if (addHeaders || isSingleHeader(header)) {
+ for (int i = 0; i < headerList.size(); i++) {
+ Object headerObject = headerList.get(i);
+ if (headerObject != null) {
+ response.addHeader(header, headerObjectToString(headerObject));
+ }
+ }
+ } else {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < headerList.size(); i++) {
+ Object headerObject = headerList.get(i);
+ if (headerObject != null) {
+ sb.append(headerObjectToString(headerObject));
+ }
+
+ if (i + 1 < headerList.size()) {
+ sb.append(',');
+ }
+ }
+ response.setHeader(header, sb.toString());
+ }
+ }
+ }
+
+ private String headerObjectToString(Object headerObject) {
+ if (headerObject.getClass() == String.class) {
+ // Most likely
+ return headerObject.toString();
+ }
+ String headerString;
+ if (headerObject instanceof Date) {
+ headerString = toHttpDate((Date)headerObject);
+ } else if (headerObject instanceof Locale) {
+ headerString = toHttpLanguage((Locale)headerObject);
+ } else {
+ headerString = headerObject.toString();
+ }
+ return headerString;
+ }
+
+ void removeContentType() {
+ headers.remove(PROTOCOL_HEADERS_CONTENT_TYPE);
+ }
+
+ public String getAuthorization() {
+ List<String> authorizationLines = headers.get("Authorization");
+ if (authorizationLines != null && !authorizationLines.isEmpty()) {
+ return authorizationLines.get(0);
+ }
+ return null;
+ }
+
+ public static SimpleDateFormat getHttpDateFormat() {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+ dateFormat.setTimeZone(TIME_ZONE_GMT);
+ return dateFormat;
+ }
+
+ public static String toHttpDate(Date date) {
+ SimpleDateFormat format = getHttpDateFormat();
+ return format.format(date);
+ }
+
+ public static String toHttpLanguage(Locale locale) {
+ return locale.toString().replace('_', '-');
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/transport/http/HttpServletRequestSnapshot.java b/transform/src/patch/java/org/apache/cxf/transport/http/HttpServletRequestSnapshot.java
new file mode 100644
index 0000000..6ae7a19
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/http/HttpServletRequestSnapshot.java
@@ -0,0 +1,277 @@
+/**
+ * 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.security.Principal;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+
+public class HttpServletRequestSnapshot extends HttpServletRequestWrapper {
+ private String authType;
+ private String characterEncoding;
+ private int contentLength;
+ private String contentType;
+ private String contextPath;
+ private Cookie[] cookies;
+ private String localAddr;
+ private Locale local;
+ @SuppressWarnings("rawtypes")
+ private Enumeration locals;
+ private String localName;
+ private final int localPort;
+ private String method;
+ private String pathInfo;
+ private String pathTranslated;
+ private String protocol;
+ private String queryString;
+ private String remoteAddr;
+ private String remoteHost;
+ private final int remotePort;
+ private String remoteUser;
+ private String requestURI;
+ private StringBuffer requestURL;
+ private String schema;
+ private String serverName;
+ private final int serverPort;
+ private String servletPath;
+ private HttpSession session;
+ private Principal principal;
+ private Enumeration<String> requestHeaderNames;
+ private Map<String, Enumeration<String>> headersMap =
+ new java.util.concurrent.ConcurrentHashMap<>();
+ private String requestedSessionId;
+
+ public HttpServletRequestSnapshot(HttpServletRequest request) {
+ super(request);
+ authType = request.getAuthType();
+ characterEncoding = request.getCharacterEncoding();
+ contentLength = request.getContentLength();
+ contentType = request.getContentType();
+ contextPath = request.getContextPath();
+ cookies = request.getCookies();
+ requestHeaderNames = request.getHeaderNames();
+ Enumeration<String> tmp = request.getHeaderNames();
+ while (tmp.hasMoreElements()) {
+ String key = tmp.nextElement();
+ headersMap.put(key, request.getHeaders(key));
+ }
+ localAddr = request.getLocalAddr();
+ local = request.getLocale();
+ localName = request.getLocalName();
+ localPort = request.getLocalPort();
+ method = request.getMethod();
+ pathInfo = request.getPathInfo();
+ pathTranslated = request.getPathTranslated();
+ protocol = request.getProtocol();
+ queryString = request.getQueryString();
+ remoteAddr = request.getRemoteAddr();
+ remoteHost = request.getRemoteHost();
+ remotePort = request.getRemotePort();
+ remoteUser = request.getRemoteUser();
+ requestURI = request.getRequestURI();
+ requestURL = request.getRequestURL();
+ requestedSessionId = request.getRequestedSessionId();
+ schema = request.getScheme();
+ serverName = request.getServerName();
+ serverPort = request.getServerPort();
+ servletPath = request.getServletPath();
+ if (request.isRequestedSessionIdValid()) {
+ session = request.getSession();
+ }
+ principal = request.getUserPrincipal();
+ }
+
+ @Override
+ public String getAuthType() {
+ return this.authType;
+ }
+
+ @Override
+ public String getContextPath() {
+ return this.contextPath;
+ }
+
+ @Override
+ public Cookie[] getCookies() {
+ return this.cookies;
+ }
+
+ @Override
+ public String getHeader(String name) {
+ if (headersMap.get(name) != null && headersMap.get(name).hasMoreElements()) {
+ return headersMap.get(name).nextElement();
+ }
+ return null;
+ }
+
+ @SuppressWarnings({
+ "unchecked", "rawtypes"
+ })
+ @Override
+ public Enumeration getHeaderNames() {
+ return this.requestHeaderNames;
+ }
+
+ @SuppressWarnings({
+ "unchecked", "rawtypes"
+ })
+ @Override
+ public Enumeration getHeaders(String name) {
+ return headersMap.get(name);
+ }
+
+ @Override
+ public String getMethod() {
+ return this.method;
+ }
+
+ @Override
+ public String getPathInfo() {
+ return this.pathInfo;
+ }
+
+ @Override
+ public String getPathTranslated() {
+ return this.pathTranslated;
+ }
+
+ @Override
+ public String getQueryString() {
+ return this.queryString;
+ }
+
+ @Override
+ public String getRemoteUser() {
+ return this.remoteUser;
+ }
+
+ @Override
+ public String getRequestURI() {
+ return this.requestURI;
+ }
+
+ @Override
+ public StringBuffer getRequestURL() {
+ return this.requestURL;
+ }
+
+ @Override
+ public String getRequestedSessionId() {
+ return this.requestedSessionId;
+ }
+
+ @Override
+ public String getServletPath() {
+ return this.servletPath;
+ }
+
+ @Override
+ public HttpSession getSession() {
+ return this.session;
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return this.principal;
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ return this.characterEncoding;
+ }
+
+ @Override
+ public int getContentLength() {
+ return this.contentLength;
+ }
+
+ @Override
+ public String getContentType() {
+ return this.contentType;
+ }
+
+ @Override
+ public String getLocalAddr() {
+ return this.localAddr;
+ }
+
+ @Override
+ public String getLocalName() {
+ return this.localName;
+ }
+
+ @Override
+ public int getLocalPort() {
+ return this.localPort;
+ }
+
+ @Override
+ public Locale getLocale() {
+ return this.local;
+ }
+
+ @SuppressWarnings({
+ "unchecked", "rawtypes"
+ })
+ @Override
+ public Enumeration getLocales() {
+ return this.locals;
+ }
+
+ @Override
+ public String getProtocol() {
+ return this.protocol;
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ return this.remoteAddr;
+ }
+
+ @Override
+ public String getRemoteHost() {
+ return this.remoteHost;
+ }
+
+ @Override
+ public int getRemotePort() {
+ return this.remotePort;
+ }
+
+ @Override
+ public String getScheme() {
+ return this.schema;
+ }
+
+ @Override
+ public String getServerName() {
+ return this.serverName;
+ }
+
+ @Override
+ public int getServerPort() {
+ return this.serverPort;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/transport/http/ReferencingAuthenticator.java b/transform/src/patch/java/org/apache/cxf/transport/http/ReferencingAuthenticator.java
new file mode 100644
index 0000000..7872015
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/http/ReferencingAuthenticator.java
@@ -0,0 +1,234 @@
+/**
+ * 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.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.Authenticator;
+import java.net.InetAddress;
+import java.net.PasswordAuthentication;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+public class ReferencingAuthenticator extends Authenticator {
+ private static final boolean SKIPCHECK = System.getSecurityManager() == null;
+ final Reference<Authenticator> auth;
+ final Authenticator wrapped;
+
+ public ReferencingAuthenticator(Authenticator cxfauth, Authenticator wrapped) {
+ this.auth = new WeakReference<>(cxfauth);
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ Authenticator cxfauth = auth.get();
+ if (cxfauth == null) {
+ remove();
+ }
+ PasswordAuthentication pauth = null;
+ if (wrapped != null) {
+ try {
+ pauth = tryWith(wrapped);
+ if (pauth != null) {
+ return pauth;
+ }
+ } catch (Exception e) {
+ pauth = null;
+ }
+ }
+ if (cxfauth != null) {
+ try {
+ pauth = tryWith(cxfauth);
+ } catch (Exception e1) {
+ pauth = null;
+ }
+ }
+ return pauth;
+ }
+
+ public final void check() {
+ Authenticator cxfauth = auth.get();
+ if (cxfauth == null) {
+ remove();
+ }
+ if (wrapped != null && wrapped.getClass().getName().equals(ReferencingAuthenticator.class.getName())) {
+ try {
+ Method m = wrapped.getClass().getMethod("check");
+ m.setAccessible(true);
+ m.invoke(wrapped);
+ } catch (Throwable t) {
+ //ignore
+ }
+ }
+ }
+
+ private void remove() {
+ try {
+ // Try Authenticator.getDefault() first, JDK9+
+ final MethodHandle mt = MethodHandles
+ .lookup()
+ .findStatic(Authenticator.class, "getDefault", MethodType.methodType(Authenticator.class));
+ removeInternal((Authenticator)mt.invoke());
+ } catch (final NoSuchMethodException | IllegalAccessException ex) {
+ removeInternal();
+ } catch (Throwable e) {
+ //ignore
+ }
+ }
+
+ private void removeInternal(final Authenticator def) {
+ try {
+ if (def == this) {
+ //this is at the root of any chain of authenticators
+ Authenticator.setDefault(wrapped);
+ } else {
+ removeFromChain(def);
+ }
+ } catch (Throwable t) {
+ //ignore
+ }
+ }
+
+ private void removeInternal() {
+ try {
+ for (final Field f : Authenticator.class.getDeclaredFields()) {
+ if (f.getType().equals(Authenticator.class)) {
+ try {
+ f.setAccessible(true);
+ Authenticator o = (Authenticator) f.get(null);
+ if (o == this) {
+ //this is at the root of any chain of authenticators
+ Authenticator.setDefault(wrapped);
+ } else {
+ removeFromChain(o);
+ }
+ } catch (Exception e) {
+ //ignore
+ }
+ }
+ }
+ } catch (Throwable t) {
+ //ignore
+ }
+ }
+
+ private void removeFromChain(Authenticator a) {
+ try {
+ if (a.getClass().getName().equals(ReferencingAuthenticator.class.getName())) {
+ //multiple referencing authenticators, we can remove ourself
+ Field f2 = a.getClass().getDeclaredField("wrapped");
+ f2.setAccessible(true);
+ Authenticator a2 = (Authenticator) f2.get(a);
+ if (a2 == this) {
+ f2.set(a, wrapped);
+ } else {
+ removeFromChain(a2);
+ }
+ }
+ } catch (Throwable t) {
+ //ignore
+ }
+ }
+
+ PasswordAuthentication tryWith(Authenticator a) throws Exception {
+ if (a == null) {
+ return null;
+ }
+
+ try {
+ // Try Authenticator.requestPasswordAuthentication() first, JDK9+
+ final MethodHandle mt = MethodHandles
+ .lookup()
+ .findStatic(Authenticator.class, "requestPasswordAuthentication",
+ MethodType.methodType(PasswordAuthentication.class, new Class<?>[] {
+ Authenticator.class,
+ String.class,
+ InetAddress.class,
+ int.class,
+ String.class,
+ String.class,
+ String.class,
+ URL.class,
+ RequestorType.class
+ }));
+
+ return (PasswordAuthentication)mt.invoke(a, getRequestingHost(), getRequestingSite(),
+ getRequestingPort(), getRequestingProtocol(), getRequestingPrompt(), getRequestingScheme(),
+ getRequestingURL(), getRequestorType());
+ } catch (final NoSuchMethodException | IllegalAccessException ex) {
+ return tryWithInternal(a);
+ } catch (final Throwable ex) {
+ if (ex instanceof Exception) {
+ throw (Exception)ex;
+ } else {
+ throw new Exception(ex);
+ }
+ }
+ }
+
+ private PasswordAuthentication tryWithInternal(Authenticator a) throws Exception {
+ if (a == null) {
+ return null;
+ }
+ final Field[] fields;
+ if (SKIPCHECK) {
+ fields = Authenticator.class.getDeclaredFields();
+ } else {
+ fields = AccessController.doPrivileged(
+ (PrivilegedAction<Field[]>) () -> Authenticator.class.getDeclaredFields());
+
+ }
+
+ for (final Field f : fields) {
+ if (!Modifier.isStatic(f.getModifiers())) {
+ f.setAccessible(true);
+ Object o = f.get(this);
+ f.set(a, o);
+ }
+ }
+ Method method;
+ if (SKIPCHECK) {
+ method = Authenticator.class.getDeclaredMethod("getPasswordAuthentication");
+ method.setAccessible(true);
+ } else {
+ method = AccessController.doPrivileged(
+ (PrivilegedAction<Method>) () -> {
+ try {
+ return Authenticator.class.getDeclaredMethod("getPasswordAuthentication");
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
+ method.setAccessible(true);
+ return null;
+ });
+ }
+
+ return (PasswordAuthentication) method.invoke(a);
+ }
+}
\ No newline at end of file
diff --git a/transform/src/patch/java/org/apache/cxf/transport/http/Servlet3ContinuationProvider.java b/transform/src/patch/java/org/apache/cxf/transport/http/Servlet3ContinuationProvider.java
new file mode 100644
index 0000000..f593a3f
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/http/Servlet3ContinuationProvider.java
@@ -0,0 +1,281 @@
+/**
+ * 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.io.IOException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.continuations.Continuation;
+import org.apache.cxf.continuations.ContinuationCallback;
+import org.apache.cxf.continuations.ContinuationProvider;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.PhaseInterceptorChain;
+
+/**
+ *
+ */
+public class Servlet3ContinuationProvider implements ContinuationProvider {
+ static final boolean IS_31;
+ static {
+ boolean is31;
+ try {
+ ClassLoaderUtils.loadClass("javax.servlet.WriteListener", HttpServletRequest.class);
+ is31 = true;
+ } catch (Throwable t) {
+ is31 = false;
+ }
+ IS_31 = is31;
+ }
+
+ HttpServletRequest req;
+ HttpServletResponse resp;
+ Message inMessage;
+ Servlet3Continuation continuation;
+
+ public Servlet3ContinuationProvider(HttpServletRequest req,
+ HttpServletResponse resp,
+ Message inMessage) {
+ this.inMessage = inMessage;
+ this.req = req;
+ this.resp = resp;
+ }
+
+ public void complete() {
+ if (continuation != null) {
+ continuation.reset();
+ continuation = null;
+ }
+ }
+
+
+ /** {@inheritDoc}*/
+ public Continuation getContinuation() {
+ if (inMessage.getExchange().isOneWay()) {
+ return null;
+ }
+
+ if (continuation == null) {
+ continuation = IS_31 ? new Servlet31Continuation() : new Servlet3Continuation();
+ } else {
+ continuation.startAsyncAgain();
+ }
+ return continuation;
+ }
+
+ public class Servlet3Continuation implements Continuation, AsyncListener {
+ private static final String BLOCK_RESTART = "org.apache.cxf.continuation.block.restart";
+ AsyncContext context;
+ volatile boolean isNew = true;
+ volatile boolean isResumed;
+ volatile boolean isPending;
+ volatile boolean isComplete;
+ volatile boolean isTimeout;
+ volatile Object obj;
+ private ContinuationCallback callback;
+ private boolean blockRestart;
+
+ public Servlet3Continuation() {
+ req.setAttribute(AbstractHTTPDestination.CXF_CONTINUATION_MESSAGE,
+ inMessage.getExchange().getInMessage());
+ callback = inMessage.getExchange().get(ContinuationCallback.class);
+ blockRestart = PropertyUtils.isTrue(inMessage.getContextualProperty(BLOCK_RESTART));
+ context = req.startAsync();
+ context.addListener(this);
+ }
+
+ void startAsyncAgain() {
+ if (blockRestart) {
+ return;
+ }
+ AsyncContext old = context;
+ try {
+ context = req.startAsync();
+ context.addListener(this);
+ isComplete = false;
+ } catch (IllegalStateException ex) {
+ context = old;
+ }
+ }
+
+ public boolean suspend(long timeout) {
+ if (isPending && timeout != 0) {
+ long currentTimeout = context.getTimeout();
+ timeout = currentTimeout + timeout;
+ } else {
+ isPending = true;
+ }
+ isNew = false;
+ isResumed = false;
+
+ context.setTimeout(timeout);
+
+ updateMessageForSuspend();
+ return true;
+ }
+ protected void updateMessageForSuspend() {
+ inMessage.getExchange().getInMessage().getInterceptorChain().suspend();
+ }
+ public void redispatch() {
+ if (!isComplete) {
+ context.dispatch();
+ }
+ }
+ public void resume() {
+ isResumed = true;
+ isPending = false;
+ redispatch();
+ }
+
+ public void reset() {
+ isComplete = true;
+ try {
+ context.complete();
+ } catch (IllegalStateException ex) {
+ // ignore
+ }
+ isPending = false;
+ isResumed = false;
+ isNew = false;
+ isTimeout = false;
+ obj = null;
+ if (callback != null) {
+ final Exception ex = inMessage.getExchange().get(Exception.class);
+ Throwable cause = isCausedByIO(ex);
+
+ if (cause != null && isClientDisconnected(cause)) {
+ callback.onDisconnect();
+ }
+ }
+ }
+
+ public boolean isNew() {
+ return isNew;
+ }
+
+ public boolean isPending() {
+ return isPending;
+ }
+
+ public boolean isResumed() {
+ return isResumed;
+ }
+
+ public Object getObject() {
+ return obj;
+ }
+
+ public void setObject(Object o) {
+ obj = o;
+ }
+
+ public void onComplete(AsyncEvent event) throws IOException {
+ inMessage.getExchange().getInMessage()
+ .remove(AbstractHTTPDestination.CXF_CONTINUATION_MESSAGE);
+ if (callback != null) {
+ final Exception ex = inMessage.getExchange().get(Exception.class);
+ if (ex == null) {
+ callback.onComplete();
+ } else {
+ callback.onError(ex);
+ }
+ }
+ isResumed = false;
+ isPending = false;
+ }
+ public void onError(AsyncEvent event) throws IOException {
+ if (callback != null) {
+ callback.onError(event.getThrowable());
+ }
+ }
+ public void onStartAsync(AsyncEvent event) throws IOException {
+ }
+ public void onTimeout(AsyncEvent event) throws IOException {
+ resume();
+ isTimeout = true;
+ }
+
+ private Throwable isCausedByIO(final Exception ex) {
+ Throwable cause = ex;
+
+ while (cause != null && !(cause instanceof IOException)) {
+ cause = cause.getCause();
+ }
+
+ return cause;
+ }
+
+ private boolean isClientDisconnected(Throwable ex) {
+ String exName = (String)inMessage.getContextualProperty("disconnected.client.exception.class");
+ if (exName != null) {
+ return exName.equals(IOException.class.getName()) || exName.equals(ex.getClass().getName());
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isReadyForWrite() {
+ return true;
+ }
+
+ protected ServletOutputStream getOutputStream() {
+ try {
+ return resp.getOutputStream();
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Override
+ public boolean isTimeout() {
+ return isTimeout;
+ }
+ }
+ public class Servlet31Continuation extends Servlet3Continuation {
+ public Servlet31Continuation() {
+ }
+
+ @Override
+ protected void updateMessageForSuspend() {
+ Message currentMessage = PhaseInterceptorChain.getCurrentMessage();
+ if (currentMessage.get(WriteListener.class) != null) {
+ // CXF Continuation WriteListener will likely need to be introduced
+ // for NIO supported with non-Servlet specific mechanisms
+ getOutputStream().setWriteListener(currentMessage.get(WriteListener.class));
+ currentMessage.getInterceptorChain().suspend();
+ } else {
+ inMessage.getExchange().getInMessage().getInterceptorChain().suspend();
+ }
+ }
+
+ @Override
+ public boolean isReadyForWrite() {
+ return getOutputStream().isReady();
+ }
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/transport/http/URLConnectionHTTPConduit.java b/transform/src/patch/java/org/apache/cxf/transport/http/URLConnectionHTTPConduit.java
new file mode 100644
index 0000000..e31b2c5
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/http/URLConnectionHTTPConduit.java
@@ -0,0 +1,439 @@
+/**
+ * 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.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.ProtocolException;
+import java.net.Proxy;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.logging.Level;
+
+import javax.net.ssl.HttpsURLConnection;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.common.util.ReflectionUtil;
+import org.apache.cxf.common.util.SystemPropertyAction;
+import org.apache.cxf.configuration.jsse.TLSClientParameters;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.io.CacheAndWriteOutputStream;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
+import org.apache.cxf.service.model.EndpointInfo;
+import org.apache.cxf.transport.https.HttpsURLConnectionFactory;
+import org.apache.cxf.transport.https.HttpsURLConnectionInfo;
+import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
+import org.apache.cxf.ws.addressing.EndpointReferenceType;
+
+/**
+ *
+ */
+public class URLConnectionHTTPConduit extends HTTPConduit {
+ public static final String HTTPURL_CONNECTION_METHOD_REFLECTION = "use.httpurlconnection.method.reflection";
+ public static final String SET_REASON_PHRASE_NOT_NULL = "set.reason.phrase.not.null";
+
+ private static final boolean DEFAULT_USE_REFLECTION;
+ private static final boolean SET_REASON_PHRASE;
+ static {
+ DEFAULT_USE_REFLECTION =
+ Boolean.valueOf(SystemPropertyAction.getProperty(HTTPURL_CONNECTION_METHOD_REFLECTION, "false"));
+ SET_REASON_PHRASE =
+ Boolean.valueOf(SystemPropertyAction.getProperty(SET_REASON_PHRASE_NOT_NULL, "false"));
+ }
+
+ /**
+ * This field holds the connection factory, which primarily is used to
+ * factor out SSL specific code from this implementation.
+ * <p>
+ * This field is "protected" to facilitate some contrived UnitTesting so
+ * that an extended class may alter its value with an EasyMock URLConnection
+ * Factory.
+ */
+ protected HttpsURLConnectionFactory connectionFactory;
+
+
+ public URLConnectionHTTPConduit(Bus b, EndpointInfo ei) throws IOException {
+ super(b, ei);
+ connectionFactory = new HttpsURLConnectionFactory();
+ CXFAuthenticator.addAuthenticator();
+ }
+
+ public URLConnectionHTTPConduit(Bus b, EndpointInfo ei, EndpointReferenceType t) throws IOException {
+ super(b, ei, t);
+ connectionFactory = new HttpsURLConnectionFactory();
+ CXFAuthenticator.addAuthenticator();
+ }
+
+ /**
+ * Close the conduit
+ */
+ public void close() {
+ super.close();
+ if (defaultAddress != null) {
+ try {
+ URLConnection connect = defaultAddress.getURL().openConnection();
+ if (connect instanceof HttpURLConnection) {
+ ((HttpURLConnection)connect).disconnect();
+ }
+ } catch (IOException ex) {
+ //ignore
+ }
+ //defaultEndpointURL = null;
+ }
+ }
+
+ private HttpURLConnection createConnection(Message message, Address address, HTTPClientPolicy csPolicy)
+ throws IOException {
+ URL url = address.getURL();
+ URI uri = address.getURI();
+ Proxy proxy = proxyFactory.createProxy(csPolicy, uri);
+ message.put("http.scheme", uri.getScheme());
+ // check tlsClientParameters from message header
+ TLSClientParameters clientParameters = message.get(TLSClientParameters.class);
+ if (clientParameters == null) {
+ clientParameters = tlsClientParameters;
+ }
+ return connectionFactory.createConnection(clientParameters,
+ proxy != null ? proxy : address.getDefaultProxy(), url);
+ }
+ protected void setupConnection(Message message, Address address, HTTPClientPolicy csPolicy) throws IOException {
+ HttpURLConnection connection = createConnection(message, address, csPolicy);
+ connection.setDoOutput(true);
+
+ int ctimeout = determineConnectionTimeout(message, csPolicy);
+ connection.setConnectTimeout(ctimeout);
+
+ int rtimeout = determineReceiveTimeout(message, csPolicy);
+ connection.setReadTimeout(rtimeout);
+
+ connection.setUseCaches(false);
+ // We implement redirects in this conduit. We do not
+ // rely on the underlying URLConnection implementation
+ // because of trust issues.
+ connection.setInstanceFollowRedirects(false);
+
+ // 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");
+ }
+ try {
+ connection.setRequestMethod(httpRequestMethod);
+ } catch (java.net.ProtocolException ex) {
+ boolean b = MessageUtils.getContextualBoolean(message,
+ HTTPURL_CONNECTION_METHOD_REFLECTION,
+ DEFAULT_USE_REFLECTION);
+ if (b) {
+ try {
+ java.lang.reflect.Field f = ReflectionUtil.getDeclaredField(HttpURLConnection.class, "method");
+ if (connection instanceof HttpsURLConnection) {
+ try {
+ java.lang.reflect.Field f2 = ReflectionUtil.getDeclaredField(connection.getClass(),
+ "delegate");
+ Object c = ReflectionUtil.setAccessible(f2).get(connection);
+ if (c instanceof HttpURLConnection) {
+ ReflectionUtil.setAccessible(f).set(c, httpRequestMethod);
+ }
+
+ f2 = ReflectionUtil.getDeclaredField(c.getClass(), "httpsURLConnection");
+ HttpsURLConnection c2 = (HttpsURLConnection)ReflectionUtil.setAccessible(f2)
+ .get(c);
+
+ ReflectionUtil.setAccessible(f).set(c2, httpRequestMethod);
+ } catch (Throwable t) {
+ //ignore
+ logStackTrace(t);
+ }
+ }
+ ReflectionUtil.setAccessible(f).set(connection, httpRequestMethod);
+ message.put(HTTPURL_CONNECTION_METHOD_REFLECTION, true);
+ } catch (Throwable t) {
+ logStackTrace(t);
+ throw ex;
+ }
+ } else {
+ throw ex;
+ }
+ }
+
+ // We place the connection on the message to pick it up
+ // in the WrappedOutputStream.
+ message.put(KEY_HTTP_CONNECTION, connection);
+ message.put(KEY_HTTP_CONNECTION_ADDRESS, address);
+ }
+
+
+ protected OutputStream createOutputStream(Message message,
+ boolean needToCacheRequest,
+ boolean isChunking,
+ int chunkThreshold) throws IOException {
+ HttpURLConnection connection = (HttpURLConnection)message.get(KEY_HTTP_CONNECTION);
+
+ if (isChunking && chunkThreshold <= 0) {
+ chunkThreshold = 0;
+ connection.setChunkedStreamingMode(-1);
+ }
+ try {
+ return new URLConnectionWrappedOutputStream(message, connection,
+ needToCacheRequest,
+ isChunking,
+ chunkThreshold,
+ getConduitName());
+ } catch (URISyntaxException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private static URI computeURI(Message message, HttpURLConnection connection) throws URISyntaxException {
+ Address address = (Address)message.get(KEY_HTTP_CONNECTION_ADDRESS);
+ return address != null ? address.getURI() : connection.getURL().toURI();
+ }
+
+ class URLConnectionWrappedOutputStream extends WrappedOutputStream {
+ HttpURLConnection connection;
+ URLConnectionWrappedOutputStream(Message message, HttpURLConnection connection,
+ boolean needToCacheRequest, boolean isChunking,
+ int chunkThreshold, String conduitName) throws URISyntaxException {
+ super(message, needToCacheRequest, isChunking,
+ chunkThreshold, conduitName,
+ computeURI(message, connection));
+ this.connection = connection;
+ }
+
+ // This construction makes extending the HTTPConduit more easier
+ protected URLConnectionWrappedOutputStream(URLConnectionWrappedOutputStream wos) {
+ super(wos);
+ this.connection = wos.connection;
+ }
+ private OutputStream connectAndGetOutputStream(Boolean b) throws IOException {
+ OutputStream cout = null;
+
+ if (b != null && b) {
+ String method = connection.getRequestMethod();
+ connection.connect();
+ try {
+ java.lang.reflect.Field f = ReflectionUtil.getDeclaredField(HttpURLConnection.class, "method");
+ ReflectionUtil.setAccessible(f).set(connection, "POST");
+ cout = connection.getOutputStream();
+ ReflectionUtil.setAccessible(f).set(connection, method);
+ } catch (Throwable t) {
+ logStackTrace(t);
+ }
+
+ } else {
+ cout = connection.getOutputStream();
+ }
+ return cout;
+ }
+ protected void setupWrappedStream() throws IOException {
+ // If we need to cache for retransmission, store data in a
+ // CacheAndWriteOutputStream. Otherwise write directly to the output stream.
+ OutputStream cout;
+ try {
+ try {
+// cout = connection.getOutputStream();
+ if (System.getSecurityManager() != null) {
+ try {
+ cout = AccessController.doPrivileged(new PrivilegedExceptionAction<OutputStream>() {
+ @Override
+ public OutputStream run() throws IOException {
+ return connection.getOutputStream();
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ throw (IOException) e.getException();
+ }
+ } else {
+ cout = connection.getOutputStream();
+ }
+ } catch (ProtocolException pe) {
+ Boolean b = (Boolean)outMessage.get(HTTPURL_CONNECTION_METHOD_REFLECTION);
+ cout = connectAndGetOutputStream(b);
+ }
+ } catch (Exception e) {
+ if ("Socket Closed".equals(e.getMessage())
+ || "HostnameVerifier, socket reset for TTL".equals(e.getMessage())) {
+ connection.connect();
+ cout = connectAndGetOutputStream((Boolean)outMessage.get(HTTPURL_CONNECTION_METHOD_REFLECTION));
+ } else {
+ throw e;
+ }
+ }
+ if (cachingForRetransmission) {
+ cachedStream =
+ new CacheAndWriteOutputStream(cout);
+ wrappedStream = cachedStream;
+ } else {
+ wrappedStream = cout;
+ }
+ }
+
+ @Override
+ public void thresholdReached() {
+ if (chunking) {
+ connection.setChunkedStreamingMode(
+ URLConnectionHTTPConduit.this.getClient().getChunkLength());
+ }
+ }
+ @Override
+ protected void onFirstWrite() throws IOException {
+ super.onFirstWrite();
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Sending "
+ + connection.getRequestMethod()
+ + " Message with Headers to "
+ + url
+ + " Conduit :"
+ + conduitName
+ + "\n");
+ }
+ }
+ protected void setProtocolHeaders() throws IOException {
+ new Headers(outMessage).setProtocolHeadersInConnection(connection);
+ }
+
+ protected HttpsURLConnectionInfo getHttpsURLConnectionInfo() throws IOException {
+ connection.connect();
+ return new HttpsURLConnectionInfo(connection);
+ }
+ protected void updateResponseHeaders(Message inMessage) {
+ Headers h = new Headers(inMessage);
+ h.readFromConnection(connection);
+ inMessage.put(Message.CONTENT_TYPE, connection.getContentType());
+ cookies.readFromHeaders(h);
+ }
+ protected void handleResponseAsync() throws IOException {
+ handleResponseOnWorkqueue(true, false);
+ }
+ protected void updateCookiesBeforeRetransmit() {
+ Headers h = new Headers();
+ h.readFromConnection(connection);
+ cookies.readFromHeaders(h);
+ }
+
+ protected InputStream getInputStream() throws IOException {
+ InputStream in;
+ if (getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
+ in = connection.getErrorStream();
+ if (in == null) {
+ try {
+ // just in case - but this will most likely cause an exception
+ in = connection.getInputStream();
+ } catch (IOException ex) {
+ // ignore
+ }
+ }
+ } else {
+ in = connection.getInputStream();
+ }
+ return in;
+ }
+
+
+ protected void closeInputStream() throws IOException {
+ //try and consume any content so that the connection might be reusable
+ InputStream ins = connection.getErrorStream();
+ if (ins == null) {
+ ins = connection.getInputStream();
+ }
+ if (ins != null) {
+ IOUtils.consume(ins);
+ ins.close();
+ }
+ }
+ protected int getResponseCode() throws IOException {
+ try {
+ return AccessController.doPrivileged(new PrivilegedExceptionAction<Integer>() {
+
+ @Override
+ public Integer run() throws IOException {
+ return connection.getResponseCode();
+ } });
+ } catch (PrivilegedActionException e) {
+ Throwable t = e.getCause();
+ if (t instanceof IOException) {
+ throw (IOException) t;
+ }
+ throw new RuntimeException(t);
+ }
+ }
+ protected String getResponseMessage() throws IOException {
+ boolean b = MessageUtils.getContextualBoolean(this.outMessage,
+ SET_REASON_PHRASE_NOT_NULL,
+ SET_REASON_PHRASE);
+ if (connection.getResponseMessage() == null && b) {
+ //some http server like tomcat 8.5+ won't return the
+ //reason phrase in response, return a informative value
+ //to tell user no reason phrase in the response instead of null
+ return "no reason phrase in the response";
+ }
+ return connection.getResponseMessage();
+ }
+ protected InputStream getPartialResponse() throws IOException {
+ return ChunkedUtil.getPartialResponse(connection, connection.getResponseCode());
+ }
+ protected boolean usingProxy() {
+ return connection.usingProxy();
+ }
+ protected void setFixedLengthStreamingMode(int i) {
+ // [CXF-6227] do not call connection.setFixedLengthStreamingMode(i)
+ // to prevent https://bugs.openjdk.java.net/browse/JDK-8044726
+ }
+ protected void handleNoOutput() throws IOException {
+ if ("POST".equals(getMethod())) {
+ connection.getOutputStream().close();
+ }
+ }
+ protected void setupNewConnection(String newURL) throws IOException {
+ HTTPClientPolicy cp = getClient(outMessage);
+ Address address;
+ try {
+ if (defaultAddress.getString().equals(newURL)) {
+ address = defaultAddress;
+ } else {
+ address = new Address(newURL);
+ }
+ } catch (URISyntaxException e) {
+ throw new IOException(e);
+ }
+ setupConnection(outMessage, address, cp);
+ this.url = address.getURI();
+ connection = (HttpURLConnection)outMessage.get(KEY_HTTP_CONNECTION);
+ }
+
+ @Override
+ protected void retransmitStream() throws IOException {
+ Boolean b = (Boolean)outMessage.get(HTTPURL_CONNECTION_METHOD_REFLECTION);
+ OutputStream out = connectAndGetOutputStream(b);
+ cachedStream.writeCacheTo(out);
+ }
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/transport/http/auth/HttpAuthHeader.java b/transform/src/patch/java/org/apache/cxf/transport/http/auth/HttpAuthHeader.java
new file mode 100644
index 0000000..ee72518
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/http/auth/HttpAuthHeader.java
@@ -0,0 +1,154 @@
+/**
+ * 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.auth;
+
+import java.io.IOException;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class HttpAuthHeader {
+ public static final String AUTH_TYPE_BASIC = "Basic";
+ public static final String AUTH_TYPE_DIGEST = "Digest";
+ public static final String AUTH_TYPE_NEGOTIATE = "Negotiate";
+
+ private final String fullHeader;
+ private final String authType;
+ private final String fullContent;
+ private final Map<String, String> params;
+
+ public HttpAuthHeader(String fullHeader) {
+ this.fullHeader = (fullHeader == null) ? "" : fullHeader;
+ int spacePos = this.fullHeader.indexOf(' ');
+ if (spacePos == -1) {
+ this.authType = this.fullHeader;
+ this.fullContent = "";
+ } else {
+ this.authType = this.fullHeader.substring(0, spacePos);
+ this.fullContent = this.fullHeader.substring(spacePos + 1);
+ }
+ this.params = parseHeader();
+ }
+ public HttpAuthHeader(List<String> params) {
+ fullHeader = String.join(", ", params);
+ int spacePos = this.fullHeader.indexOf(' ');
+ if (spacePos == -1) {
+ this.authType = this.fullHeader;
+ this.fullContent = "";
+ } else {
+ this.authType = this.fullHeader.substring(0, spacePos);
+ this.fullContent = this.fullHeader.substring(spacePos + 1);
+ }
+ this.params = parseHeader();
+ }
+
+ public HttpAuthHeader(String authType, Map<String, String> params) {
+ this.authType = authType;
+ this.params = params;
+ this.fullContent = paramsToString();
+ this.fullHeader = authType + " " + fullContent;
+ }
+
+ private String paramsToString() {
+ StringBuilder builder = new StringBuilder();
+ boolean first = true;
+ for (Map.Entry<String, String> entry : params.entrySet()) {
+ String param = entry.getValue();
+ if (param != null) {
+ if (!first) {
+ builder.append(", ");
+ }
+ if ("nc".equals(entry.getKey())
+ || "qop".equals(entry.getKey())
+ || "algorithm".equals(entry.getKey())) {
+ builder.append(entry.getKey()).append('=').append(param);
+ } else {
+ builder.append(entry.getKey()).append("=\"").append(param).append('"');
+ }
+ first = false;
+ }
+ }
+ return builder.toString();
+ }
+
+ private Map<String, String> parseHeader() {
+ Map<String, String> map = new HashMap<>();
+ try {
+ StreamTokenizer tok = new StreamTokenizer(new StringReader(this.fullContent)) {
+ @Override
+ public void parseNumbers() {
+ // skip parse numbers
+ wordChars('0', '9');
+ wordChars('.', '.');
+ wordChars('-', '-');
+ }
+ };
+ tok.whitespaceChars('=', '=');
+ tok.whitespaceChars(',', ',');
+
+ while (tok.nextToken() != StreamTokenizer.TT_EOF) {
+ map.put(tok.sval, tok.nextToken() != StreamTokenizer.TT_EOF ? tok.sval : null);
+ }
+ } catch (IOException ex) {
+ //ignore can't happen for StringReader
+ }
+ return map;
+ }
+
+ /**
+ * Extracts the authorization realm from the
+ * "WWW-Authenticate" Http response header.
+ *
+ * @return The realm, or null if it is non-existent.
+ */
+ public String getRealm() {
+ return params.get("realm");
+ }
+
+ public boolean authTypeIsDigest() {
+ return AUTH_TYPE_DIGEST.equals(this.authType);
+ }
+
+ public boolean authTypeIsBasic() {
+ return AUTH_TYPE_BASIC.equals(this.authType);
+ }
+
+ public boolean authTypeIsNegotiate() {
+ return AUTH_TYPE_NEGOTIATE.equals(this.authType);
+ }
+
+ public String getAuthType() {
+ return authType;
+ }
+
+ public String getFullContent() {
+ return fullContent;
+ }
+
+ public String getFullHeader() {
+ return this.fullHeader;
+ }
+
+ public Map<String, String> getParams() {
+ return params;
+ }
+
+}
diff --git a/transform/src/patch/java/org/apache/cxf/transport/https/HttpsURLConnectionFactory.java b/transform/src/patch/java/org/apache/cxf/transport/https/HttpsURLConnectionFactory.java
new file mode 100644
index 0000000..d42549d
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/https/HttpsURLConnectionFactory.java
@@ -0,0 +1,247 @@
+/**
+ * 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.https;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.GeneralSecurityException;
+import java.security.PrivilegedAction;
+import java.util.logging.Handler;
+import java.util.logging.Logger;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.ReflectionInvokationHandler;
+import org.apache.cxf.common.util.ReflectionUtil;
+import org.apache.cxf.configuration.jsse.SSLUtils;
+import org.apache.cxf.configuration.jsse.TLSClientParameters;
+
+
+/**
+ * This HttpsURLConnectionFactory implements the HttpURLConnectionFactory
+ * for using the given SSL Policy to configure TLS connections for "https:"
+ * URLs.
+ */
+public class HttpsURLConnectionFactory {
+
+ /**
+ * This constant holds the URL Protocol Identifier for HTTPS
+ */
+ public static final String HTTPS_URL_PROTOCOL_ID = "https";
+
+ private static final Logger LOG =
+ LogUtils.getL7dLogger(HttpsURLConnectionFactory.class);
+
+ private static boolean weblogicWarned;
+
+ /**
+ * Cache the last SSLContext to avoid recreation
+ */
+ SSLSocketFactory socketFactory;
+ int lastTlsHash;
+
+ /**
+ * This constructor initialized the factory with the configured TLS
+ * Client Parameters for the HTTPConduit for which this factory is used.
+ */
+ public HttpsURLConnectionFactory() {
+ }
+
+ /**
+ * Create a HttpURLConnection, proxified if necessary.
+ *
+ *
+ * @param proxy This parameter is non-null if connection should be proxied.
+ * @param url The target URL.
+ *
+ * @return The HttpURLConnection for the given URL.
+ * @throws IOException
+ */
+ public HttpURLConnection createConnection(TLSClientParameters tlsClientParameters,
+ Proxy proxy, URL url) throws IOException {
+
+ HttpURLConnection connection =
+ (HttpURLConnection) (proxy != null
+ ? url.openConnection(proxy)
+ : url.openConnection());
+ if (HTTPS_URL_PROTOCOL_ID.equals(url.getProtocol())) {
+
+ if (tlsClientParameters == null) {
+ tlsClientParameters = new TLSClientParameters();
+ }
+
+ try {
+ decorateWithTLS(tlsClientParameters, connection);
+ } catch (Throwable ex) {
+ throw new IOException("Error while initializing secure socket", ex);
+ }
+ }
+
+ return connection;
+ }
+
+ /**
+ * This method assigns the various TLS parameters on the HttpsURLConnection
+ * from the TLS Client Parameters. Connection parameter is of supertype HttpURLConnection,
+ * which allows internal cast to potentially divergent subtype (https) implementations.
+ */
+ protected synchronized void decorateWithTLS(TLSClientParameters tlsClientParameters,
+ HttpURLConnection connection) throws GeneralSecurityException {
+
+
+ int hash = tlsClientParameters.hashCode();
+ if (hash != lastTlsHash) {
+ lastTlsHash = hash;
+ socketFactory = null;
+ }
+
+ // always reload socketFactory from HttpsURLConnection.defaultSSLSocketFactory and
+ // tlsClientParameters.sslSocketFactory to allow runtime configuration change
+ if (tlsClientParameters.isUseHttpsURLConnectionDefaultSslSocketFactory()) {
+ socketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
+
+ } else if (tlsClientParameters.getSSLSocketFactory() != null) {
+ // see if an SSLSocketFactory was set. This allows easy interop
+ // with not-yet-commons-ssl.jar, or even just people who like doing their
+ // own JSSE.
+ socketFactory = tlsClientParameters.getSSLSocketFactory();
+
+ } else if (socketFactory == null) {
+
+ final SSLContext ctx;
+ if (tlsClientParameters.getSslContext() != null) {
+ // Use the SSLContext which was set
+ ctx = tlsClientParameters.getSslContext();
+ } else {
+ // Create socketfactory with tlsClientParameters's Trust Managers, Key Managers, etc
+ ctx = org.apache.cxf.transport.https.SSLUtils.getSSLContext(tlsClientParameters);
+ }
+
+ String[] cipherSuites =
+ SSLUtils.getCiphersuitesToInclude(tlsClientParameters.getCipherSuites(),
+ tlsClientParameters.getCipherSuitesFilter(),
+ ctx.getSocketFactory().getDefaultCipherSuites(),
+ SSLUtils.getSupportedCipherSuites(ctx),
+ LOG);
+
+ // The SSLSocketFactoryWrapper enables certain cipher suites from the policy.
+ String protocol = tlsClientParameters.getSecureSocketProtocol() != null ? tlsClientParameters
+ .getSecureSocketProtocol() : ctx.getProtocol();
+ socketFactory = new SSLSocketFactoryWrapper(ctx.getSocketFactory(), cipherSuites,
+ protocol);
+ //recalc the hashcode since some of the above MAY have changed the tlsClientParameters
+ lastTlsHash = tlsClientParameters.hashCode();
+ } else {
+ // ssl socket factory already initialized, reuse it to benefit of keep alive
+ }
+
+
+ HostnameVerifier verifier = org.apache.cxf.transport.https.SSLUtils
+ .getHostnameVerifier(tlsClientParameters);
+
+ if (connection instanceof HttpsURLConnection) {
+ // handle the expected case (javax.net.ssl)
+ HttpsURLConnection conn = (HttpsURLConnection) connection;
+ conn.setHostnameVerifier(verifier);
+ AccessController.doPrivileged(new PrivilegedAction<Void>() {
+
+ @Override
+ public Void run() {
+ conn.setSSLSocketFactory(socketFactory);
+ return null;
+ } });
+ } else {
+ // handle the deprecated sun case and other possible hidden API's
+ // that are similar to the Sun cases
+ try {
+ Method method = connection.getClass().getMethod("getHostnameVerifier");
+
+ InvocationHandler handler = new ReflectionInvokationHandler(verifier) {
+ public Object invoke(Object proxy,
+ Method method,
+ Object[] args) throws Throwable {
+ try {
+ return super.invoke(proxy, method, args);
+ } catch (Exception ex) {
+ return false;
+ }
+ }
+ };
+ Object proxy = java.lang.reflect.Proxy.newProxyInstance(this.getClass().getClassLoader(),
+ new Class[] {method.getReturnType()},
+ handler);
+
+ method = connection.getClass().getMethod("setHostnameVerifier", method.getReturnType());
+ method.invoke(connection, proxy);
+ } catch (Exception ex) {
+ //Ignore this one
+ }
+ try {
+ Method getSSLSocketFactory = connection.getClass().getMethod("getSSLSocketFactory");
+ Method setSSLSocketFactory = connection.getClass()
+ .getMethod("setSSLSocketFactory", getSSLSocketFactory.getReturnType());
+ if (getSSLSocketFactory.getReturnType().isInstance(socketFactory)) {
+ setSSLSocketFactory.invoke(connection, socketFactory);
+ } else {
+ //need to see if we can create one - mostly the weblogic case. The
+ //weblogic SSLSocketFactory has a protected constructor that can take
+ //a JSSE SSLSocketFactory so we'll try and use that
+ Constructor<?> c = getSSLSocketFactory.getReturnType()
+ .getDeclaredConstructor(SSLSocketFactory.class);
+ ReflectionUtil.setAccessible(c);
+ setSSLSocketFactory.invoke(connection, c.newInstance(socketFactory));
+ }
+ } catch (Exception ex) {
+ if (connection.getClass().getName().contains("weblogic")) {
+ if (!weblogicWarned) {
+ weblogicWarned = true;
+ LOG.warning("Could not configure SSLSocketFactory on Weblogic. "
+ + " Use the Weblogic control panel to configure the SSL settings.");
+ }
+ return;
+ }
+ //if we cannot set the SSLSocketFactory, we're in serious trouble.
+ throw new IllegalArgumentException("Error decorating connection class "
+ + connection.getClass().getName(), ex);
+ }
+ }
+ }
+
+ /*
+ * For development and testing only
+ */
+ protected void addLogHandler(Handler handler) {
+ LOG.addHandler(handler);
+ }
+
+}
+
+
+
diff --git a/transform/src/patch/java/org/apache/cxf/transport/https/HttpsURLConnectionInfo.java b/transform/src/patch/java/org/apache/cxf/transport/https/HttpsURLConnectionInfo.java
new file mode 100644
index 0000000..31bff30
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/https/HttpsURLConnectionInfo.java
@@ -0,0 +1,168 @@
+/**
+ * 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.https;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.security.Principal;
+import java.security.cert.Certificate;
+
+import javax.net.ssl.HttpsURLConnection;
+
+import org.apache.cxf.transport.http.HttpURLConnectionInfo;
+
+/**
+ * This class holds information about the HttpsURLConnection. This
+ * class should be used when the getURL().getProtocol() is "https".
+ */
+public class HttpsURLConnectionInfo extends HttpURLConnectionInfo {
+
+ /**
+ * This field contains the cipherSuite enabled in the
+ * HTTPS URLconnection.
+ */
+ protected String enabledCipherSuite;
+
+ /**
+ * This field contains the certificates that were used to
+ * authenticate the connection to the peer.
+ */
+ protected Certificate[] localCertificates;
+
+ /**
+ * This field contains the Principal that authenticated to the
+ * peer.
+ */
+ protected Principal localPrincipal;
+
+ /**
+ * This field contains the certificates the server presented
+ * to authenticate.
+ */
+ protected Certificate[] serverCertificates;
+
+ /**
+ * This field contains the Principal that represents the
+ * authenticated peer.
+ */
+ protected Principal peerPrincipal;
+
+
+ public HttpsURLConnectionInfo(URI uri,
+ String method,
+ String cipherSuite,
+ Certificate[] localCerts,
+ Principal principal,
+ Certificate[] serverCerts,
+ Principal peer) {
+ super(uri, method);
+ enabledCipherSuite = cipherSuite;
+ localCertificates = localCerts;
+ localPrincipal = principal;
+ serverCertificates = serverCerts;
+ peerPrincipal = peer;
+ }
+
+
+
+ /**
+ * This constructor is used to create the info object
+ * representing the this HttpsURLConnection. Connection parameter is
+ * of supertype HttpURLConnection, which allows internal cast to
+ * potentially divergent subtype (Https) implementations.
+ */
+ public HttpsURLConnectionInfo(HttpURLConnection connection)
+ throws IOException {
+ super(connection.getURL(), connection.getRequestMethod());
+ if (connection instanceof HttpsURLConnection) {
+ HttpsURLConnection conn = (HttpsURLConnection) connection;
+ enabledCipherSuite = conn.getCipherSuite();
+ localCertificates = conn.getLocalCertificates();
+ localPrincipal = conn.getLocalPrincipal();
+ serverCertificates = conn.getServerCertificates();
+ peerPrincipal = conn.getPeerPrincipal();
+ } else {
+ try {
+ Method method = connection.getClass().getMethod("getCipherSuite", (Class[]) null);
+ enabledCipherSuite = (String) method.invoke(connection, (Object[]) null);
+ method = connection.getClass().getMethod("getLocalCertificates", (Class[]) null);
+ localCertificates = (Certificate[]) method.invoke(connection, (Object[]) null);
+ method = connection.getClass().getMethod("getServerCertificates", (Class[]) null);
+ serverCertificates = (Certificate[]) method.invoke(connection, (Object[]) null);
+
+ //TODO Obtain localPrincipal and peerPrincipal using the com.sun.net.ssl api
+ } catch (Exception e) {
+ Throwable ex = e;
+ if (e instanceof InvocationTargetException) {
+ ex = ((InvocationTargetException) e).getTargetException();
+ }
+ if (ex instanceof IOException) {
+ throw (IOException) ex;
+ }
+ throw new IOException("Error constructing HttpsURLConnectionInfo "
+ + "for connection class "
+ + connection.getClass().getName(), ex);
+ }
+ }
+ }
+
+ /**
+ * This method returns the cipher suite employed in this
+ * HttpsURLConnection.
+ */
+ public String getEnabledCipherSuite() {
+ return enabledCipherSuite;
+ }
+
+ /**
+ * This method returns the certificates that were used to
+ * authenticate to the peer.
+ */
+ public Certificate[] getLocalCertificates() {
+ return localCertificates;
+ }
+
+ /**
+ * This method returns the Princpal that authenticated to
+ * the peer.
+ */
+ public Principal getLocalPrincipal() {
+ return localPrincipal;
+ }
+
+ /**
+ * This method returns the certificates presented by the
+ * peer for authentication.
+ */
+ public Certificate[] getServerCertificates() {
+ return serverCertificates;
+ }
+
+ /**
+ * This method returns the Principal that represents the
+ * authenticated peer.
+ */
+ public Principal getPeerPrincipal() {
+ return peerPrincipal;
+ }
+}
diff --git a/transform/src/patch/java/org/apache/cxf/transport/servlet/servicelist/ServiceListJAASAuthenticator.java b/transform/src/patch/java/org/apache/cxf/transport/servlet/servicelist/ServiceListJAASAuthenticator.java
new file mode 100644
index 0000000..8562cd3
--- /dev/null
+++ b/transform/src/patch/java/org/apache/cxf/transport/servlet/servicelist/ServiceListJAASAuthenticator.java
@@ -0,0 +1,160 @@
+/**
+ * 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.servlet.servicelist;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.AccountException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.Base64Exception;
+import org.apache.cxf.common.util.Base64Utility;
+import org.apache.cxf.transport.http.blueprint.HttpDestinationBPBeanDefinitionParser;
+
+
+
+public class ServiceListJAASAuthenticator {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(HttpDestinationBPBeanDefinitionParser.class);
+
+ private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+
+ private static final String HEADER_AUTHORIZATION = "Authorization";
+
+ private static final String AUTHENTICATION_SCHEME_BASIC = "Basic";
+
+ private String realm;
+
+ public String getRealm() {
+ return realm;
+ }
+
+ public void setRealm(String realm) {
+ this.realm = realm;
+ }
+
+
+ public Object authenticate(final String username, final String password) {
+ return doAuthenticate(username, password);
+ }
+
+ public Subject doAuthenticate(final String username, final String password) {
+ try {
+ Subject subject = new Subject();
+ LoginContext loginContext = new LoginContext(realm, subject, new CallbackHandler() {
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (int i = 0; i < callbacks.length; i++) {
+ if (callbacks[i] instanceof NameCallback) {
+ ((NameCallback)callbacks[i]).setName(username);
+ } else if (callbacks[i] instanceof PasswordCallback) {
+ ((PasswordCallback)callbacks[i]).setPassword(password.toCharArray());
+ } else {
+ throw new UnsupportedCallbackException(callbacks[i]);
+ }
+ }
+ }
+ });
+ loginContext.login();
+ return subject;
+ } catch (FailedLoginException e) {
+ LOG.log(Level.FINE, "Login failed ", e);
+ return null;
+ } catch (AccountException e) {
+ LOG.log(Level.WARNING, "Account failure ", e);
+ return null;
+ } catch (GeneralSecurityException e) {
+ LOG.log(Level.SEVERE, "General Security Exception ", e);
+ return null;
+ }
+ }
+
+ public boolean authenticate(HttpServletRequest request, HttpServletResponse response) {
+ // Return immediately if the header is missing
+ String authHeader = request.getHeader(HEADER_AUTHORIZATION);
+ if (authHeader != null && authHeader.length() > 0) {
+
+ // Get the authType (Basic, Digest) and authInfo (user/password)
+ // from the header
+ authHeader = authHeader.trim();
+ int blank = authHeader.indexOf(' ');
+ if (blank > 0) {
+ String authType = authHeader.substring(0, blank);
+ String authInfo = authHeader.substring(blank).trim();
+
+
+ if (authType.equalsIgnoreCase(AUTHENTICATION_SCHEME_BASIC)) {
+ try {
+ String srcString = base64Decode(authInfo);
+
+ int i = srcString.indexOf(':');
+ String username = srcString.substring(0, i);
+ String password = srcString.substring(i + 1);
+
+ // authenticate
+ Subject subject = doAuthenticate(username, password);
+ if (subject != null) {
+ return true;
+ }
+
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ // request authentication
+ try {
+ response.setHeader(HEADER_WWW_AUTHENTICATE, AUTHENTICATION_SCHEME_BASIC + " realm=\""
+ + this.realm + "\"");
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ response.setContentLength(0);
+ response.flushBuffer();
+ } catch (IOException ioe) {
+ // failed sending the response ... cannot do anything about it
+ }
+
+ // inform HttpService that authentication failed
+ return false;
+ }
+
+ private static String base64Decode(String srcString) {
+ try {
+ byte[] transformed = Base64Utility.decode(srcString);
+ return new String(transformed, StandardCharsets.ISO_8859_1);
+ } catch (Base64Exception e) {
+ return srcString;
+ }
+ }
+
+}