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;
+        }
+    }
+
+}