]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/client_side.cc
Fix logged request size (%http::>st) and other size-related %codes.
[thirdparty/squid.git] / src / client_side.cc
index 350e568802822fc71889cae34273248d7b0ef7bc..772bad9f9a995295c69f61505a1055b75ea55e36 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
@@ -87,6 +87,7 @@
 #include "http.h"
 #include "http/one/RequestParser.h"
 #include "http/one/TeChunkedParser.h"
+#include "http/Stream.h"
 #include "HttpHdrContRange.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"
 #include "parser/Tokenizer.h"
 #include "profiler/Profiler.h"
 #include "rfc1738.h"
+#include "security/NegotiationHistory.h"
 #include "servers/forward.h"
 #include "SquidConfig.h"
 #include "SquidTime.h"
 #include "ssl/ServerBump.h"
 #include "ssl/support.h"
 #endif
-#if USE_SSL_CRTD
-#include "ssl/certificate_db.h"
-#include "ssl/crtd_message.h"
-#endif
 
 // for tvSubUsec() which should be in SquidTime.h
 #include "util.h"
@@ -171,13 +169,6 @@ private:
 
 static void clientListenerConnectionOpened(AnyP::PortCfgPointer &s, const Ipc::FdNoteId portTypeNote, const Subscription::Pointer &sub);
 
-/* our socket-related context */
-
-CBDATA_CLASS_INIT(ClientSocketContext);
-
-/* Local functions */
-static IOCB clientWriteComplete;
-static IOCB clientWriteBodyComplete;
 static IOACB httpAccept;
 #if USE_OPENSSL
 static IOACB httpsAccept;
@@ -194,138 +185,10 @@ static void clientUpdateStatCounters(const LogTags &logType);
 static void clientUpdateHierCounters(HierarchyLogEntry *);
 static bool clientPingHasFinished(ping_data const *aPing);
 void prepareLogWithRequestDetails(HttpRequest *, AccessLogEntry::Pointer &);
-static void ClientSocketContextPushDeferredIfNeeded(ClientSocketContext::Pointer deferredRequest, ConnStateData * conn);
-static void clientUpdateSocketStats(const LogTags &logType, size_t size);
+static void ClientSocketContextPushDeferredIfNeeded(Http::StreamPointer deferredRequest, ConnStateData * conn);
 
 char *skipLeadingSpace(char *aString);
 
-clientStreamNode *
-ClientSocketContext::getTail() const
-{
-    if (http->client_stream.tail)
-        return (clientStreamNode *)http->client_stream.tail->data;
-
-    return NULL;
-}
-
-clientStreamNode *
-ClientSocketContext::getClientReplyContext() const
-{
-    return (clientStreamNode *)http->client_stream.tail->prev->data;
-}
-
-ConnStateData *
-ClientSocketContext::getConn() const
-{
-    return http->getConn();
-}
-
-ClientSocketContext::~ClientSocketContext()
-{
-    clientStreamNode *node = getTail();
-
-    if (node) {
-        ClientSocketContext *streamContext = dynamic_cast<ClientSocketContext *> (node->data.getRaw());
-
-        if (streamContext) {
-            /* We are *always* the tail - prevent recursive free */
-            assert(this == streamContext);
-            node->data = NULL;
-        }
-    }
-
-    httpRequestFree(http);
-}
-
-void
-ClientSocketContext::registerWithConn()
-{
-    assert (!connRegistered_);
-    assert (http);
-    assert (http->getConn() != NULL);
-    connRegistered_ = true;
-    http->getConn()->pipeline.add(ClientSocketContext::Pointer(this));
-}
-
-void
-ClientSocketContext::finished()
-{
-    assert (http);
-    assert (http->getConn() != NULL);
-    ConnStateData *conn = http->getConn();
-
-    /* we can't handle any more stream data - detach */
-    clientStreamDetach(getTail(), http);
-
-    assert(connRegistered_);
-    connRegistered_ = false;
-    assert(conn->pipeline.front() == this); // XXX: still assumes HTTP/1 semantics
-    conn->pipeline.popMe(ClientSocketContext::Pointer(this));
-}
-
-ClientSocketContext::ClientSocketContext(const Comm::ConnectionPointer &aConn, ClientHttpRequest *aReq) :
-    clientConnection(aConn),
-    http(aReq),
-    reply(NULL),
-    writtenToSocket(0),
-    mayUseConnection_ (false),
-    connRegistered_ (false)
-{
-    assert(http != NULL);
-    memset (reqbuf, '\0', sizeof (reqbuf));
-    flags.deferred = 0;
-    flags.parsed_ok = 0;
-    deferredparams.node = NULL;
-    deferredparams.rep = NULL;
-}
-
-void
-ClientSocketContext::writeControlMsg(HttpControlMsg &msg)
-{
-    HttpReply::Pointer rep(msg.reply);
-    Must(rep != NULL);
-
-    // remember the callback
-    cbControlMsgSent = msg.cbSuccess;
-
-    AsyncCall::Pointer call = commCbCall(33, 5, "ClientSocketContext::wroteControlMsg",
-                                         CommIoCbPtrFun(&WroteControlMsg, this));
-
-    getConn()->writeControlMsgAndCall(this, rep.getRaw(), call);
-}
-
-/// called when we wrote the 1xx response
-void
-ClientSocketContext::wroteControlMsg(const Comm::ConnectionPointer &conn, char *, size_t, Comm::Flag errflag, int xerrno)
-{
-    if (errflag == Comm::ERR_CLOSING)
-        return;
-
-    if (errflag == Comm::OK) {
-        ScheduleCallHere(cbControlMsgSent);
-        return;
-    }
-
-    debugs(33, 3, HERE << "1xx writing failed: " << xstrerr(xerrno));
-    // no error notification: see HttpControlMsg.h for rationale and
-    // note that some errors are detected elsewhere (e.g., close handler)
-
-    // close on 1xx errors to be conservative and to simplify the code
-    // (if we do not close, we must notify the source of a failure!)
-    conn->close();
-
-    // XXX: writeControlMsgAndCall() should handle writer-specific writing
-    // results, including errors and then call us with success/failure outcome.
-}
-
-/// wroteControlMsg() wrapper: ClientSocketContext is not an AsyncJob
-void
-ClientSocketContext::WroteControlMsg(const Comm::ConnectionPointer &conn, char *bufnotused, size_t size, Comm::Flag errflag, int xerrno, void *data)
-{
-    ClientSocketContext *context = static_cast<ClientSocketContext*>(data);
-    context->wroteControlMsg(conn, bufnotused, size, errflag, xerrno);
-}
-
 #if USE_IDENT
 static void
 clientIdentDone(const char *ident, void *data)
@@ -499,8 +362,6 @@ prepareLogWithRequestDetails(HttpRequest * request, AccessLogEntry::Pointer &aLo
     aLogEntry->http.method = request->method;
     aLogEntry->http.version = request->http_ver;
     aLogEntry->hier = request->hier;
-    if (request->content_length > 0) // negative when no body or unknown length
-        aLogEntry->http.clientRequestSz.payloadData += request->content_length; // XXX: actually adaptedRequest payload size ??
     aLogEntry->cache.extuser = request->extacl_user.termedBuf();
 
     // Adapted request, if any, inherits and then collects all the stats, but
@@ -537,6 +398,9 @@ ClientHttpRequest::logRequest()
         al->cache.objectSize = loggingEntry()->contentLen(); // payload duplicate ?? with or without TE ?
 
     al->http.clientRequestSz.header = req_sz;
+    // the virgin request is saved to al->request
+    if (al->request && al->request->body_pipe)
+        al->http.clientRequestSz.payloadData = al->request->body_pipe->producedSize();
     al->http.clientReplySz.header = out.headers_sz;
     // XXX: calculate without payload encoding or headers !!
     al->http.clientReplySz.payloadData = out.size - out.headers_sz; // pretend its all un-encoded data for now.
@@ -583,6 +447,7 @@ ClientHttpRequest::logRequest()
     }
 
     if (request) {
+        HTTPMSGUNLOCK(al->adapted_request);
         al->adapted_request = request;
         HTTPMSGLOCK(al->adapted_request);
     }
@@ -720,8 +585,9 @@ void
 ConnStateData::swanSong()
 {
     debugs(33, 2, HERE << clientConnection);
+    checkLogging();
+
     flags.readMore = false;
-    DeregisterRunner(this);
     clientdbEstablished(clientConnection->remote, -1);  /* decrement */
     pipeline.terminateAll(0);
 
@@ -818,134 +684,20 @@ clientIsRequestBodyTooLargeForPolicy(int64_t bodyLength)
     return 0;
 }
 
-void
-ClientSocketContext::deferRecipientForLater(clientStreamNode * node, HttpReply * rep, StoreIOBuffer receivedData)
-{
-    debugs(33, 2, "clientSocketRecipient: Deferring request " << http->uri);
-    assert(flags.deferred == 0);
-    flags.deferred = 1;
-    deferredparams.node = node;
-    deferredparams.rep = rep;
-    deferredparams.queuedBuffer = receivedData;
-    return;
-}
-
-bool
-ClientSocketContext::startOfOutput() const
-{
-    return http->out.size == 0;
-}
-
-size_t
-ClientSocketContext::lengthToSend(Range<int64_t> const &available)
-{
-    /*the size of available range can always fit in a size_t type*/
-    size_t maximum = (size_t)available.size();
-
-    if (!http->request->range)
-        return maximum;
-
-    assert (canPackMoreRanges());
-
-    if (http->range_iter.debt() == -1)
-        return maximum;
-
-    assert (http->range_iter.debt() > 0);
-
-    /* TODO this + the last line could be a range intersection calculation */
-    if (available.start < http->range_iter.currentSpec()->offset)
-        return 0;
-
-    return min(http->range_iter.debt(), (int64_t)maximum);
-}
-
-void
-ClientSocketContext::noteSentBodyBytes(size_t bytes)
-{
-    debugs(33, 7, bytes << " body bytes");
-
-    http->out.offset += bytes;
-
-    if (!http->request->range)
-        return;
-
-    if (http->range_iter.debt() != -1) {
-        http->range_iter.debt(http->range_iter.debt() - bytes);
-        assert (http->range_iter.debt() >= 0);
-    }
-
-    /* debt() always stops at -1, below that is a bug */
-    assert (http->range_iter.debt() >= -1);
-}
-
 bool
 ClientHttpRequest::multipartRangeRequest() const
 {
     return request->multipartRangeRequest();
 }
 
-bool
-ClientSocketContext::multipartRangeRequest() const
-{
-    return http->multipartRangeRequest();
-}
-
-void
-ClientSocketContext::sendBody(HttpReply * rep, StoreIOBuffer bodyData)
-{
-    assert(rep == NULL);
-
-    if (!multipartRangeRequest() && !http->request->flags.chunkedReply) {
-        size_t length = lengthToSend(bodyData.range());
-        noteSentBodyBytes (length);
-        AsyncCall::Pointer call = commCbCall(33, 5, "clientWriteBodyComplete",
-                                             CommIoCbPtrFun(clientWriteBodyComplete, this));
-        Comm::Write(clientConnection, bodyData.data, length, call, NULL);
-        return;
-    }
-
-    MemBuf mb;
-    mb.init();
-    if (multipartRangeRequest())
-        packRange(bodyData, &mb);
-    else
-        packChunk(bodyData, mb);
-
-    if (mb.contentSize()) {
-        /* write */
-        AsyncCall::Pointer call = commCbCall(33, 5, "clientWriteComplete",
-                                             CommIoCbPtrFun(clientWriteComplete, this));
-        Comm::Write(clientConnection, &mb, call);
-    }  else
-        writeComplete(clientConnection, NULL, 0, Comm::OK);
-}
-
-/**
- * Packs bodyData into mb using chunked encoding. Packs the last-chunk
- * if bodyData is empty.
- */
 void
-ClientSocketContext::packChunk(const StoreIOBuffer &bodyData, MemBuf &mb)
-{
-    const uint64_t length =
-        static_cast<uint64_t>(lengthToSend(bodyData.range()));
-    noteSentBodyBytes(length);
-
-    mb.appendf("%" PRIX64 "\r\n", length);
-    mb.append(bodyData.data, length);
-    mb.append("\r\n", 2);
-}
-
-/** put terminating boundary for multiparts */
-static void
-clientPackTermBound(String boundary, MemBuf * mb)
+clientPackTermBound(String boundary, MemBuf *mb)
 {
     mb->appendf("\r\n--" SQUIDSTRINGPH "--\r\n", SQUIDSTRINGPRINT(boundary));
-    debugs(33, 6, "clientPackTermBound: buf offset: " << mb->size);
+    debugs(33, 6, "buf offset: " << mb->size);
 }
 
-/** appends a "part" HTTP header (as in a multi-part/range reply) to the buffer */
-static void
+void
 clientPackRangeHdr(const HttpReply * rep, const HttpHdrRangeSpec * spec, String boundary, MemBuf * mb)
 {
     HttpHeader hdr(hoReply);
@@ -953,7 +705,7 @@ clientPackRangeHdr(const HttpReply * rep, const HttpHdrRangeSpec * spec, String
     assert(spec);
 
     /* put boundary */
-    debugs(33, 5, "clientPackRangeHdr: appending boundary: " << boundary);
+    debugs(33, 5, "appending boundary: " << boundary);
     /* rfc2046 requires to _prepend_ boundary with <crlf>! */
     mb->appendf("\r\n--" SQUIDSTRINGPH "\r\n", SQUIDSTRINGPRINT(boundary));
 
@@ -971,90 +723,6 @@ clientPackRangeHdr(const HttpReply * rep, const HttpHdrRangeSpec * spec, String
     mb->append("\r\n", 2);
 }
 
-/**
- * extracts a "range" from *buf and appends them to mb, updating
- * all offsets and such.
- */
-void
-ClientSocketContext::packRange(StoreIOBuffer const &source, MemBuf * mb)
-{
-    HttpHdrRangeIter * i = &http->range_iter;
-    Range<int64_t> available (source.range());
-    char const *buf = source.data;
-
-    while (i->currentSpec() && available.size()) {
-        const size_t copy_sz = lengthToSend(available);
-
-        if (copy_sz) {
-            /*
-             * intersection of "have" and "need" ranges must not be empty
-             */
-            assert(http->out.offset < i->currentSpec()->offset + i->currentSpec()->length);
-            assert(http->out.offset + (int64_t)available.size() > i->currentSpec()->offset);
-
-            /*
-             * put boundary and headers at the beginning of a range in a
-             * multi-range
-             */
-
-            if (http->multipartRangeRequest() && i->debt() == i->currentSpec()->length) {
-                assert(http->memObject());
-                clientPackRangeHdr(
-                    http->memObject()->getReply(),  /* original reply */
-                    i->currentSpec(),       /* current range */
-                    i->boundary,    /* boundary, the same for all */
-                    mb);
-            }
-
-            /*
-             * append content
-             */
-            debugs(33, 3, "clientPackRange: appending " << copy_sz << " bytes");
-
-            noteSentBodyBytes (copy_sz);
-
-            mb->append(buf, copy_sz);
-
-            /*
-             * update offsets
-             */
-            available.start += copy_sz;
-
-            buf += copy_sz;
-
-        }
-
-        if (!canPackMoreRanges()) {
-            debugs(33, 3, "clientPackRange: Returning because !canPackMoreRanges.");
-
-            if (i->debt() == 0)
-                /* put terminating boundary for multiparts */
-                clientPackTermBound(i->boundary, mb);
-
-            return;
-        }
-
-        int64_t nextOffset = getNextRangeOffset();
-
-        assert (nextOffset >= http->out.offset);
-
-        int64_t skip = nextOffset - http->out.offset;
-
-        /* adjust for not to be transmitted bytes */
-        http->out.offset = nextOffset;
-
-        if (available.size() <= (uint64_t)skip)
-            return;
-
-        available.start += skip;
-
-        buf += skip;
-
-        if (copy_sz == 0)
-            return;
-    }
-}
-
 /** returns expected content length for multi-range replies
  * note: assumes that httpHdrRangeCanonize has already been called
  * warning: assumes that HTTP headers for individual ranges at the
@@ -1097,44 +765,6 @@ ClientHttpRequest::mRangeCLen()
     return clen;
 }
 
-/**
- * returns true if If-Range specs match reply, false otherwise
- */
-static int
-clientIfRangeMatch(ClientHttpRequest * http, HttpReply * rep)
-{
-    const TimeOrTag spec = http->request->header.getTimeOrTag(Http::HdrType::IF_RANGE);
-    /* check for parsing falure */
-
-    if (!spec.valid)
-        return 0;
-
-    /* got an ETag? */
-    if (spec.tag.str) {
-        ETag rep_tag = rep->header.getETag(Http::HdrType::ETAG);
-        debugs(33, 3, "clientIfRangeMatch: ETags: " << spec.tag.str << " and " <<
-               (rep_tag.str ? rep_tag.str : "<none>"));
-
-        if (!rep_tag.str)
-            return 0;       /* entity has no etag to compare with! */
-
-        if (spec.tag.weak || rep_tag.weak) {
-            debugs(33, DBG_IMPORTANT, "clientIfRangeMatch: Weak ETags are not allowed in If-Range: " << spec.tag.str << " ? " << rep_tag.str);
-            return 0;       /* must use strong validator for sub-range requests */
-        }
-
-        return etagIsStrongEqual(rep_tag, spec.tag);
-    }
-
-    /* got modification time? */
-    if (spec.time >= 0) {
-        return http->storeEntry()->lastmod <= spec.time;
-    }
-
-    assert(0);          /* should not happen */
-    return 0;
-}
-
 /**
  * generates a "unique" boundary string for multipart responses
  * the caller is responsible for cleaning the string */
@@ -1149,169 +779,6 @@ ClientHttpRequest::rangeBoundaryStr() const
     return b;
 }
 
-/** adds appropriate Range headers if needed */
-void
-ClientSocketContext::buildRangeHeader(HttpReply * rep)
-{
-    HttpHeader *hdr = rep ? &rep->header : 0;
-    const char *range_err = NULL;
-    HttpRequest *request = http->request;
-    assert(request->range);
-    /* check if we still want to do ranges */
-
-    int64_t roffLimit = request->getRangeOffsetLimit();
-
-    if (!rep)
-        range_err = "no [parse-able] reply";
-    else if ((rep->sline.status() != Http::scOkay) && (rep->sline.status() != Http::scPartialContent))
-        range_err = "wrong status code";
-    else if (hdr->has(Http::HdrType::CONTENT_RANGE))
-        range_err = "origin server does ranges";
-    else if (rep->content_length < 0)
-        range_err = "unknown length";
-    else if (rep->content_length != http->memObject()->getReply()->content_length)
-        range_err = "INCONSISTENT length";  /* a bug? */
-
-    /* hits only - upstream CachePeer determines correct behaviour on misses, and client_side_reply determines
-     * hits candidates
-     */
-    else if (http->logType.isTcpHit() && http->request->header.has(Http::HdrType::IF_RANGE) && !clientIfRangeMatch(http, rep))
-        range_err = "If-Range match failed";
-    else if (!http->request->range->canonize(rep))
-        range_err = "canonization failed";
-    else if (http->request->range->isComplex())
-        range_err = "too complex range header";
-    else if (!http->logType.isTcpHit() && http->request->range->offsetLimitExceeded(roffLimit))
-        range_err = "range outside range_offset_limit";
-
-    /* get rid of our range specs on error */
-    if (range_err) {
-        /* XXX We do this here because we need canonisation etc. However, this current
-         * code will lead to incorrect store offset requests - the store will have the
-         * offset data, but we won't be requesting it.
-         * So, we can either re-request, or generate an error
-         */
-        http->request->ignoreRange(range_err);
-    } else {
-        /* XXX: TODO: Review, this unconditional set may be wrong. */
-        rep->sline.set(rep->sline.version, Http::scPartialContent);
-        // web server responded with a valid, but unexpected range.
-        // will (try-to) forward as-is.
-        //TODO: we should cope with multirange request/responses
-        bool replyMatchRequest = rep->content_range != NULL ?
-                                 request->range->contains(rep->content_range->spec) :
-                                 true;
-        const int spec_count = http->request->range->specs.size();
-        int64_t actual_clen = -1;
-
-        debugs(33, 3, "clientBuildRangeHeader: range spec count: " <<
-               spec_count << " virgin clen: " << rep->content_length);
-        assert(spec_count > 0);
-        /* append appropriate header(s) */
-
-        if (spec_count == 1) {
-            if (!replyMatchRequest) {
-                hdr->delById(Http::HdrType::CONTENT_RANGE);
-                hdr->putContRange(rep->content_range);
-                actual_clen = rep->content_length;
-                //http->range_iter.pos = rep->content_range->spec.begin();
-                (*http->range_iter.pos)->offset = rep->content_range->spec.offset;
-                (*http->range_iter.pos)->length = rep->content_range->spec.length;
-
-            } else {
-                HttpHdrRange::iterator pos = http->request->range->begin();
-                assert(*pos);
-                /* append Content-Range */
-
-                if (!hdr->has(Http::HdrType::CONTENT_RANGE)) {
-                    /* No content range, so this was a full object we are
-                     * sending parts of.
-                     */
-                    httpHeaderAddContRange(hdr, **pos, rep->content_length);
-                }
-
-                /* set new Content-Length to the actual number of bytes
-                 * transmitted in the message-body */
-                actual_clen = (*pos)->length;
-            }
-        } else {
-            /* multipart! */
-            /* generate boundary string */
-            http->range_iter.boundary = http->rangeBoundaryStr();
-            /* delete old Content-Type, add ours */
-            hdr->delById(Http::HdrType::CONTENT_TYPE);
-            httpHeaderPutStrf(hdr, Http::HdrType::CONTENT_TYPE,
-                              "multipart/byteranges; boundary=\"" SQUIDSTRINGPH "\"",
-                              SQUIDSTRINGPRINT(http->range_iter.boundary));
-            /* Content-Length is not required in multipart responses
-             * but it is always nice to have one */
-            actual_clen = http->mRangeCLen();
-            /* http->out needs to start where we want data at */
-            http->out.offset = http->range_iter.currentSpec()->offset;
-        }
-
-        /* replace Content-Length header */
-        assert(actual_clen >= 0);
-
-        hdr->delById(Http::HdrType::CONTENT_LENGTH);
-
-        hdr->putInt64(Http::HdrType::CONTENT_LENGTH, actual_clen);
-
-        debugs(33, 3, "clientBuildRangeHeader: actual content length: " << actual_clen);
-
-        /* And start the range iter off */
-        http->range_iter.updateSpec();
-    }
-}
-
-void
-ClientSocketContext::prepareReply(HttpReply * rep)
-{
-    reply = rep;
-
-    if (http->request->range)
-        buildRangeHeader(rep);
-}
-
-void
-ClientSocketContext::sendStartOfMessage(HttpReply * rep, StoreIOBuffer bodyData)
-{
-    prepareReply(rep);
-    assert (rep);
-    MemBuf *mb = rep->pack();
-
-    // dump now, so we dont output any body.
-    debugs(11, 2, "HTTP Client " << clientConnection);
-    debugs(11, 2, "HTTP Client REPLY:\n---------\n" << mb->buf << "\n----------");
-
-    /* Save length of headers for persistent conn checks */
-    http->out.headers_sz = mb->contentSize();
-#if HEADERS_LOG
-
-    headersLog(0, 0, http->request->method, rep);
-#endif
-
-    if (bodyData.data && bodyData.length) {
-        if (multipartRangeRequest())
-            packRange(bodyData, mb);
-        else if (http->request->flags.chunkedReply) {
-            packChunk(bodyData, *mb);
-        } else {
-            size_t length = lengthToSend(bodyData.range());
-            noteSentBodyBytes (length);
-
-            mb->append(bodyData.data, length);
-        }
-    }
-
-    /* write */
-    debugs(33,7, HERE << "sendStartOfMessage schedules clientWriteComplete");
-    AsyncCall::Pointer call = commCbCall(33, 5, "clientWriteComplete",
-                                         CommIoCbPtrFun(clientWriteComplete, this));
-    Comm::Write(clientConnection, mb, call);
-    delete mb;
-}
-
 /**
  * Write a chunk of data to a client socket. If the reply is present,
  * send the reply headers down the wire too, and clean them up when
@@ -1339,7 +806,7 @@ clientSocketRecipient(clientStreamNode * node, ClientHttpRequest * http,
      */
     assert(cbdataReferenceValid(node));
     assert(node->node.next == NULL);
-    ClientSocketContext::Pointer context = dynamic_cast<ClientSocketContext *>(node->data.getRaw());
+    Http::StreamPointer context = dynamic_cast<Http::Stream *>(node->data.getRaw());
     assert(context != NULL);
 
     /* TODO: check offset is what we asked for */
@@ -1372,20 +839,13 @@ clientSocketDetach(clientStreamNode * node, ClientHttpRequest * http)
     /* Set null by ContextFree */
     assert(node->node.next == NULL);
     /* this is the assert discussed above */
-    assert(NULL == dynamic_cast<ClientSocketContext *>(node->data.getRaw()));
+    assert(NULL == dynamic_cast<Http::Stream *>(node->data.getRaw()));
     /* We are only called when the client socket shutsdown.
      * Tell the prev pipeline member we're finished
      */
     clientStreamDetach(node, http);
 }
 
-static void
-clientWriteBodyComplete(const Comm::ConnectionPointer &conn, char *, size_t size, Comm::Flag errflag, int xerrno, void *data)
-{
-    debugs(33,7, "schedule clientWriteComplete");
-    clientWriteComplete(conn, NULL, size, errflag, xerrno, data);
-}
-
 void
 ConnStateData::readNextRequest()
 {
@@ -1405,7 +865,7 @@ ConnStateData::readNextRequest()
 }
 
 static void
-ClientSocketContextPushDeferredIfNeeded(ClientSocketContext::Pointer deferredRequest, ConnStateData * conn)
+ClientSocketContextPushDeferredIfNeeded(Http::StreamPointer deferredRequest, ConnStateData * conn)
 {
     debugs(33, 2, HERE << conn->clientConnection << " Sending next");
 
@@ -1494,7 +954,7 @@ ConnStateData::kick()
      * then look at processing it. If not, simply kickstart
      * another read.
      */
-    ClientSocketContext::Pointer deferredRequest = pipeline.front();
+    Http::StreamPointer deferredRequest = pipeline.front();
     if (deferredRequest != nullptr) {
         debugs(33, 3, clientConnection << ": calling PushDeferredIfNeeded");
         ClientSocketContextPushDeferredIfNeeded(deferredRequest, this);
@@ -1507,201 +967,6 @@ ConnStateData::kick()
     }
 }
 
-void
-clientUpdateSocketStats(const LogTags &logType, size_t size)
-{
-    if (size == 0)
-        return;
-
-    statCounter.client_http.kbytes_out += size;
-
-    if (logType.isTcpHit())
-        statCounter.client_http.hit_kbytes_out += size;
-}
-
-/**
- * increments iterator "i"
- * used by clientPackMoreRanges
- *
- \retval true    there is still data available to pack more ranges
- \retval false
- */
-bool
-ClientSocketContext::canPackMoreRanges() const
-{
-    /** first update iterator "i" if needed */
-
-    if (!http->range_iter.debt()) {
-        debugs(33, 5, HERE << "At end of current range spec for " << clientConnection);
-
-        if (http->range_iter.pos != http->range_iter.end)
-            ++http->range_iter.pos;
-
-        http->range_iter.updateSpec();
-    }
-
-    assert(!http->range_iter.debt() == !http->range_iter.currentSpec());
-
-    /* paranoid sync condition */
-    /* continue condition: need_more_data */
-    debugs(33, 5, "ClientSocketContext::canPackMoreRanges: returning " << (http->range_iter.currentSpec() ? true : false));
-    return http->range_iter.currentSpec() ? true : false;
-}
-
-int64_t
-ClientSocketContext::getNextRangeOffset() const
-{
-    debugs (33, 5, "range: " << http->request->range <<
-            "; http offset " << http->out.offset <<
-            "; reply " << reply);
-
-    // XXX: This method is called from many places, including pullData() which
-    // may be called before prepareReply() [on some Squid-generated errors].
-    // Hence, we may not even know yet whether we should honor/do ranges.
-
-    if (http->request->range) {
-        /* offset in range specs does not count the prefix of an http msg */
-        /* check: reply was parsed and range iterator was initialized */
-        assert(http->range_iter.valid);
-        /* filter out data according to range specs */
-        assert (canPackMoreRanges());
-        {
-            int64_t start;      /* offset of still missing data */
-            assert(http->range_iter.currentSpec());
-            start = http->range_iter.currentSpec()->offset + http->range_iter.currentSpec()->length - http->range_iter.debt();
-            debugs(33, 3, "clientPackMoreRanges: in:  offset: " << http->out.offset);
-            debugs(33, 3, "clientPackMoreRanges: out:"
-                   " start: " << start <<
-                   " spec[" << http->range_iter.pos - http->request->range->begin() << "]:" <<
-                   " [" << http->range_iter.currentSpec()->offset <<
-                   ", " << http->range_iter.currentSpec()->offset + http->range_iter.currentSpec()->length << "),"
-                   " len: " << http->range_iter.currentSpec()->length <<
-                   " debt: " << http->range_iter.debt());
-            if (http->range_iter.currentSpec()->length != -1)
-                assert(http->out.offset <= start);  /* we did not miss it */
-
-            return start;
-        }
-
-    } else if (reply && reply->content_range) {
-        /* request does not have ranges, but reply does */
-        /** \todo FIXME: should use range_iter_pos on reply, as soon as reply->content_range
-         *        becomes HttpHdrRange rather than HttpHdrRangeSpec.
-         */
-        return http->out.offset + reply->content_range->spec.offset;
-    }
-
-    return http->out.offset;
-}
-
-void
-ClientSocketContext::pullData()
-{
-    debugs(33, 5, reply << " written " << http->out.size << " into " << clientConnection);
-
-    /* More data will be coming from the stream. */
-    StoreIOBuffer readBuffer;
-    /* XXX: Next requested byte in the range sequence */
-    /* XXX: length = getmaximumrangelenfgth */
-    readBuffer.offset = getNextRangeOffset();
-    readBuffer.length = HTTP_REQBUF_SZ;
-    readBuffer.data = reqbuf;
-    /* we may note we have reached the end of the wanted ranges */
-    clientStreamRead(getTail(), http, readBuffer);
-}
-
-/** Adapt stream status to account for Range cases
- *
- */
-clientStream_status_t
-ClientSocketContext::socketState()
-{
-    switch (clientStreamStatus(getTail(), http)) {
-
-    case STREAM_NONE:
-        /* check for range support ending */
-
-        if (http->request->range) {
-            /* check: reply was parsed and range iterator was initialized */
-            assert(http->range_iter.valid);
-            /* filter out data according to range specs */
-
-            if (!canPackMoreRanges()) {
-                debugs(33, 5, HERE << "Range request at end of returnable " <<
-                       "range sequence on " << clientConnection);
-                // we got everything we wanted from the store
-                return STREAM_COMPLETE;
-            }
-        } else if (reply && reply->content_range) {
-            /* reply has content-range, but Squid is not managing ranges */
-            const int64_t &bytesSent = http->out.offset;
-            const int64_t &bytesExpected = reply->content_range->spec.length;
-
-            debugs(33, 7, HERE << "body bytes sent vs. expected: " <<
-                   bytesSent << " ? " << bytesExpected << " (+" <<
-                   reply->content_range->spec.offset << ")");
-
-            // did we get at least what we expected, based on range specs?
-
-            if (bytesSent == bytesExpected) // got everything
-                return STREAM_COMPLETE;
-
-            if (bytesSent > bytesExpected) // Error: Sent more than expected
-                return STREAM_UNPLANNED_COMPLETE;
-        }
-
-        return STREAM_NONE;
-
-    case STREAM_COMPLETE:
-        return STREAM_COMPLETE;
-
-    case STREAM_UNPLANNED_COMPLETE:
-        return STREAM_UNPLANNED_COMPLETE;
-
-    case STREAM_FAILED:
-        return STREAM_FAILED;
-    }
-
-    fatal ("unreachable code\n");
-    return STREAM_NONE;
-}
-
-/**
- * A write has just completed to the client, or we have just realised there is
- * no more data to send.
- */
-void
-clientWriteComplete(const Comm::ConnectionPointer &conn, char *bufnotused, size_t size, Comm::Flag errflag, int, void *data)
-{
-    ClientSocketContext *context = (ClientSocketContext *)data;
-    context->writeComplete(conn, bufnotused, size, errflag);
-}
-
-/// remembers the abnormal connection termination for logging purposes
-void
-ClientSocketContext::noteIoError(const int xerrno)
-{
-    if (http) {
-        http->logType.err.timedout = (xerrno == ETIMEDOUT);
-        // aborted even if xerrno is zero (which means read abort/eof)
-        http->logType.err.aborted = (xerrno != ETIMEDOUT);
-    }
-}
-
-void
-ClientSocketContext::doClose()
-{
-    clientConnection->close();
-}
-
-/// called when we encounter a response-related error
-void
-ClientSocketContext::initiateClose(const char *reason)
-{
-    debugs(33, 4, clientConnection << " because " << reason);
-    http->getConn()->stopSending(reason); // closes ASAP
-}
-
 void
 ConnStateData::stopSending(const char *error)
 {
@@ -1727,63 +992,28 @@ ConnStateData::stopSending(const char *error)
 }
 
 void
-ClientSocketContext::writeComplete(const Comm::ConnectionPointer &conn, char *, size_t size, Comm::Flag errflag)
+ConnStateData::afterClientWrite(size_t size)
 {
-    const StoreEntry *entry = http->storeEntry();
-    http->out.size += size;
-    debugs(33, 5, HERE << conn << ", sz " << size <<
-           ", err " << errflag << ", off " << http->out.size << ", len " <<
-           (entry ? entry->objectLen() : 0));
-    clientUpdateSocketStats(http->logType, size);
-
-    /* Bail out quickly on Comm::ERR_CLOSING - close handlers will tidy up */
-
-    if (errflag == Comm::ERR_CLOSING || !Comm::IsConnOpen(conn))
+    if (pipeline.empty())
         return;
 
-    if (errflag || clientHttpRequestStatus(conn->fd, http)) {
-        initiateClose("failure or true request status");
-        /* Do we leak here ? */
-        return;
-    }
-
-    switch (socketState()) {
-
-    case STREAM_NONE:
-        pullData();
-        break;
-
-    case STREAM_COMPLETE: {
-        debugs(33, 5, conn << " Stream complete, keepalive is " << http->request->flags.proxyKeepalive);
-        ConnStateData *c = http->getConn();
-        if (!http->request->flags.proxyKeepalive)
-            clientConnection->close();
-        finished();
-        c->kick();
-    }
-    return;
-
-    case STREAM_UNPLANNED_COMPLETE:
-        initiateClose("STREAM_UNPLANNED_COMPLETE");
-        return;
-
-    case STREAM_FAILED:
-        initiateClose("STREAM_FAILED");
-        return;
-
-    default:
-        fatal("Hit unreachable code in clientWriteComplete\n");
+    auto ctx = pipeline.front();
+    if (size) {
+        statCounter.client_http.kbytes_out += size;
+        if (ctx->http->logType.isTcpHit())
+            statCounter.client_http.hit_kbytes_out += size;
     }
+    ctx->writeComplete(size);
 }
 
-ClientSocketContext *
+Http::Stream *
 ConnStateData::abortRequestParsing(const char *const uri)
 {
     ClientHttpRequest *http = new ClientHttpRequest(this);
     http->req_sz = inBuf.length();
     http->uri = xstrdup(uri);
     setLogUri (http, uri);
-    ClientSocketContext *context = new ClientSocketContext(clientConnection, http);
+    auto *context = new Http::Stream(clientConnection, http);
     StoreIOBuffer tempBuffer;
     tempBuffer.data = context->reqbuf;
     tempBuffer.length = HTTP_REQBUF_SZ;
@@ -1813,10 +1043,6 @@ ConnStateData::endingShutdown()
     // swanSong() in the close handler will cleanup.
     if (Comm::IsConnOpen(clientConnection))
         clientConnection->close();
-
-    // deregister now to ensure finalShutdown() does not kill us prematurely.
-    // fd_table purge will cleanup if close handler was not fast enough.
-    DeregisterRunner(this);
 }
 
 char *
@@ -1991,7 +1217,8 @@ prepareAcceleratedURL(ConnStateData * conn, ClientHttpRequest *http, const Http1
         } // else nothing to alter port-wise.
         const int url_sz = hp->requestUri().length() + 32 + Config.appendDomainLen + strlen(host);
         http->uri = (char *)xcalloc(url_sz, 1);
-        snprintf(http->uri, url_sz, "%s://%s" SQUIDSBUFPH, AnyP::UriScheme(conn->transferProtocol.protocol).c_str(), host, SQUIDSBUFPRINT(url));
+        const SBuf &scheme = AnyP::UriScheme(conn->transferProtocol.protocol).image();
+        snprintf(http->uri, url_sz, SQUIDSBUFPH "://%s" SQUIDSBUFPH, SQUIDSBUFPRINT(scheme), host, SQUIDSBUFPRINT(url));
         debugs(33, 5, "ACCEL VHOST REWRITE: " << http->uri);
     } else if (conn->port->defaultsite /* && !vhost */) {
         debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: defaultsite=" << conn->port->defaultsite << " + vport=" << vport);
@@ -2003,8 +1230,9 @@ prepareAcceleratedURL(ConnStateData * conn, ClientHttpRequest *http, const Http1
         if (vport > 0) {
             snprintf(vportStr, sizeof(vportStr),":%d",vport);
         }
-        snprintf(http->uri, url_sz, "%s://%s%s" SQUIDSBUFPH,
-                 AnyP::UriScheme(conn->transferProtocol.protocol).c_str(), conn->port->defaultsite, vportStr, SQUIDSBUFPRINT(url));
+        const SBuf &scheme = AnyP::UriScheme(conn->transferProtocol.protocol).image();
+        snprintf(http->uri, url_sz, SQUIDSBUFPH "://%s%s" SQUIDSBUFPH,
+                 SQUIDSBUFPRINT(scheme), conn->port->defaultsite, vportStr, SQUIDSBUFPRINT(url));
         debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: " << http->uri);
     } else if (vport > 0 /* && (!vhost || no Host:) */) {
         debugs(33, 5, "ACCEL VPORT REWRITE: *_port IP + vport=" << vport);
@@ -2012,9 +1240,9 @@ prepareAcceleratedURL(ConnStateData * conn, ClientHttpRequest *http, const Http1
         const int url_sz = hp->requestUri().length() + 32 + Config.appendDomainLen;
         http->uri = (char *)xcalloc(url_sz, 1);
         http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN);
-        snprintf(http->uri, url_sz, "%s://%s:%d" SQUIDSBUFPH,
-                 AnyP::UriScheme(conn->transferProtocol.protocol).c_str(),
-                 ipbuf, vport, SQUIDSBUFPRINT(url));
+        const SBuf &scheme = AnyP::UriScheme(conn->transferProtocol.protocol).image();
+        snprintf(http->uri, url_sz, SQUIDSBUFPH "://%s:%d" SQUIDSBUFPH,
+                 SQUIDSBUFPRINT(scheme), ipbuf, vport, SQUIDSBUFPRINT(url));
         debugs(33, 5, "ACCEL VPORT REWRITE: " << http->uri);
     }
 }
@@ -2032,8 +1260,9 @@ prepareTransparentURL(ConnStateData * conn, ClientHttpRequest *http, const Http1
         const int url_sz = hp->requestUri().length() + 32 + Config.appendDomainLen +
                            strlen(host);
         http->uri = (char *)xcalloc(url_sz, 1);
-        snprintf(http->uri, url_sz, "%s://%s" SQUIDSBUFPH,
-                 AnyP::UriScheme(conn->transferProtocol.protocol).c_str(), host, SQUIDSBUFPRINT(hp->requestUri()));
+        const SBuf &scheme = AnyP::UriScheme(conn->transferProtocol.protocol).image();
+        snprintf(http->uri, url_sz, SQUIDSBUFPH "://%s" SQUIDSBUFPH,
+                 SQUIDSBUFPRINT(scheme), host, SQUIDSBUFPRINT(hp->requestUri()));
         debugs(33, 5, "TRANSPARENT HOST REWRITE: " << http->uri);
     } else {
         /* Put the local socket IP address as the hostname.  */
@@ -2041,8 +1270,9 @@ prepareTransparentURL(ConnStateData * conn, ClientHttpRequest *http, const Http1
         http->uri = (char *)xcalloc(url_sz, 1);
         static char ipbuf[MAX_IPSTRLEN];
         http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN);
-        snprintf(http->uri, url_sz, "%s://%s:%d" SQUIDSBUFPH,
-                 AnyP::UriScheme(http->getConn()->transferProtocol.protocol).c_str(),
+        const SBuf &scheme = AnyP::UriScheme(http->getConn()->transferProtocol.protocol).image();
+        snprintf(http->uri, url_sz, SQUIDSBUFPH "://%s:%d" SQUIDSBUFPH,
+                 SQUIDSBUFPRINT(scheme),
                  ipbuf, http->getConn()->clientConnection->local.port(), SQUIDSBUFPRINT(hp->requestUri()));
         debugs(33, 5, "TRANSPARENT REWRITE: " << http->uri);
     }
@@ -2059,9 +1289,9 @@ prepareTransparentURL(ConnStateData * conn, ClientHttpRequest *http, const Http1
  *          parsing failure
  *  \param[out] http_ver will be set as a side-effect of the parsing
  *  \return NULL on incomplete requests,
- *          a ClientSocketContext structure on success or failure.
+ *          a Http::Stream on success or failure.
  */
-ClientSocketContext *
+Http::Stream *
 parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
 {
     /* Attempt to parse the first line; this will define where the method, url, version and header begin */
@@ -2079,10 +1309,14 @@ parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
         }
 
         if (!parsedOk) {
-            if (hp->parseStatusCode == Http::scRequestHeaderFieldsTooLarge || hp->parseStatusCode == Http::scUriTooLong)
-                return csd->abortRequestParsing("error:request-too-large");
-
-            return csd->abortRequestParsing("error:invalid-request");
+            const bool tooBig =
+                hp->parseStatusCode == Http::scRequestHeaderFieldsTooLarge ||
+                hp->parseStatusCode == Http::scUriTooLong;
+            auto result = csd->abortRequestParsing(
+                              tooBig ? "error:request-too-large" : "error:invalid-request");
+            // assume that remaining leftovers belong to this bad request
+            csd->consumeInput(csd->inBuf.length());
+            return result;
         }
     }
 
@@ -2129,7 +1363,7 @@ parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
     ClientHttpRequest *http = new ClientHttpRequest(csd);
 
     http->req_sz = hp->messageHeaderSize();
-    ClientSocketContext *result = new ClientSocketContext(csd->clientConnection, http);
+    Http::Stream *result = new Http::Stream(csd->clientConnection, http);
 
     StoreIOBuffer tempBuffer;
     tempBuffer.data = result->reqbuf;
@@ -2240,7 +1474,7 @@ ConnStateData::quitAfterError(HttpRequest *request)
 }
 
 #if USE_OPENSSL
-bool ConnStateData::serveDelayedError(ClientSocketContext *context)
+bool ConnStateData::serveDelayedError(Http::Stream *context)
 {
     ClientHttpRequest *http = context->http;
 
@@ -2275,16 +1509,16 @@ bool ConnStateData::serveDelayedError(ClientSocketContext *context)
     // In bump-server-first mode, we have not necessarily seen the intended
     // server name at certificate-peeking time. Check for domain mismatch now,
     // when we can extract the intended name from the bumped HTTP request.
-    if (X509 *srvCert = sslServerBump->serverCert.get()) {
+    if (const Security::CertPointer &srvCert = sslServerBump->serverCert) {
         HttpRequest *request = http->request;
-        if (!Ssl::checkX509ServerValidity(srvCert, request->url.host())) {
+        if (!Ssl::checkX509ServerValidity(srvCert.get(), request->url.host())) {
             debugs(33, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate " <<
                    "does not match domainname " << request->url.host());
 
             bool allowDomainMismatch = false;
             if (Config.ssl_client.cert_error) {
                 ACLFilledChecklist check(Config.ssl_client.cert_error, request, dash_str);
-                check.sslErrors = new Ssl::CertErrors(Ssl::CertError(SQUID_X509_V_ERR_DOMAIN_MISMATCH, srvCert));
+                check.sslErrors = new Security::CertErrors(Security::CertError(SQUID_X509_V_ERR_DOMAIN_MISMATCH, srvCert));
                 allowDomainMismatch = (check.fastCheck() == ACCESS_ALLOWED);
                 delete check.sslErrors;
                 check.sslErrors = NULL;
@@ -2306,7 +1540,7 @@ bool ConnStateData::serveDelayedError(ClientSocketContext *context)
                 err->src_addr = clientConnection->remote;
                 Ssl::ErrorDetail *errDetail = new Ssl::ErrorDetail(
                     SQUID_X509_V_ERR_DOMAIN_MISMATCH,
-                    srvCert, NULL);
+                    srvCert.get(), nullptr);
                 err->detail = errDetail;
                 // Save the original request for logging purposes.
                 if (!context->http->al->request) {
@@ -2330,7 +1564,7 @@ bool ConnStateData::serveDelayedError(ClientSocketContext *context)
  * or false otherwise
  */
 bool
-clientTunnelOnError(ConnStateData *conn, ClientSocketContext *context, HttpRequest *request, const HttpRequestMethod& method, err_type requestError, Http::StatusCode errStatusCode, const char *requestErrorBytes)
+clientTunnelOnError(ConnStateData *conn, Http::Stream *context, HttpRequest *request, const HttpRequestMethod& method, err_type requestError, Http::StatusCode errStatusCode, const char *requestErrorBytes)
 {
     if (conn->port->flags.isIntercepted() &&
             Config.accessList.on_unsupported_protocol && conn->pipeline.nrequests <= 1) {
@@ -2347,11 +1581,10 @@ clientTunnelOnError(ConnStateData *conn, ClientSocketContext *context, HttpReque
                 // The below may leak client streams BodyPipe objects. BUT, we need
                 // to check if client-streams detatch is safe to do here (finished() will detatch).
                 assert(conn->pipeline.front() == context); // XXX: still assumes HTTP/1 semantics
-                conn->pipeline.popMe(ClientSocketContextPointer(context));
+                conn->pipeline.popMe(Http::StreamPointer(context));
             }
             Comm::SetSelect(conn->clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
-            conn->fakeAConnectRequest("unknown-protocol", conn->preservedClientData);
-            return true;
+            return conn->fakeAConnectRequest("unknown-protocol", conn->preservedClientData);
         } else {
             debugs(33, 3, "Continue with returning the error: " << requestError);
         }
@@ -2388,7 +1621,7 @@ clientProcessRequestFinished(ConnStateData *conn, const HttpRequest::Pointer &re
 }
 
 void
-clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp, ClientSocketContext *context)
+clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp, Http::Stream *context)
 {
     ClientHttpRequest *http = context->http;
     bool chunked = false;
@@ -2416,6 +1649,8 @@ clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp,
     // TODO: decouple http->flags.accel from request->flags.sslBumped
     request->flags.noDirect = (request->flags.accelerated && !request->flags.sslBumped) ?
                               !conn->port->allow_direct : 0;
+    request->sources |= isFtp ? HttpMsg::srcFtp :
+                        ((request->flags.sslBumped || conn->port->transport.protocol == AnyP::PROTO_HTTPS) ? HttpMsg::srcHttps : HttpMsg::srcHttp);
 #if USE_AUTH
     if (request->flags.sslBumped) {
         if (conn->getAuth() != NULL)
@@ -2448,7 +1683,7 @@ clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp,
             http->flags.internal = true;
         } else if (Config.onoff.global_internal_static && internalStaticCheck(request->url.path())) {
             debugs(33, 2, "internal URL found: " << request->url.getScheme() << "://" << request->url.authority(true) << " (global_internal_static on)");
-            request->url.setScheme(AnyP::PROTO_HTTP);
+            request->url.setScheme(AnyP::PROTO_HTTP, "http");
             request->url.host(internalHostname());
             request->url.port(getMyPort());
             http->flags.internal = true;
@@ -2912,10 +2147,6 @@ ConnStateData::clientParseRequests()
     // On errors, bodyPipe may become nil, but readMore will be cleared
     while (!inBuf.isEmpty() && !bodyPipe && flags.readMore) {
 
-        /* Don't try to parse if the buffer is empty */
-        if (inBuf.isEmpty())
-            break;
-
         /* Limit the number of concurrent requests */
         if (concurrentRequestQueueFilled())
             break;
@@ -2924,7 +2155,7 @@ ConnStateData::clientParseRequests()
         if (needProxyProtocolHeader_ && !parseProxyProtocolHeader())
             break;
 
-        if (ClientSocketContext *context = parseOneRequest()) {
+        if (Http::Stream *context = parseOneRequest()) {
             debugs(33, 5, clientConnection << ": done parsing a request");
 
             AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout",
@@ -2956,6 +2187,13 @@ ConnStateData::clientParseRequests()
 void
 ConnStateData::afterClientRead()
 {
+#if USE_OPENSSL
+    if (parsingTlsHandshake) {
+        parseTlsHandshake();
+        return;
+    }
+#endif
+
     /* Process next request */
     if (pipeline.empty())
         fd_note(clientConnection->fd, "Reading next request");
@@ -3095,7 +2333,7 @@ ConnStateData::abortChunkedRequestBody(const err_type error)
     // but if we fail when the server connection is used already, the server may send
     // us its response too, causing various assertions. How to prevent that?
 #if WE_KNOW_HOW_TO_SEND_ERRORS
-    ClientSocketContext::Pointer context = pipeline.front();
+    Http::StreamPointer context = pipeline.front();
     if (context != NULL && !context->http->out.offset) { // output nothing yet
         clientStreamNode *node = context->getClientReplyContext();
         clientReplyContext *repContext = dynamic_cast<clientReplyContext*>(node->data.getRaw());
@@ -3144,7 +2382,7 @@ ConnStateData::requestTimeout(const CommTimeoutCbParams &io)
                 receivedFirstByte();
                 return;
             }
-        } else if (fd_table[io.conn->fd].ssl == NULL)
+        } else if (!fd_table[io.conn->fd].ssl)
 #endif
         {
             const HttpRequestMethod method;
@@ -3188,6 +2426,7 @@ ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) :
     needProxyProtocolHeader_(false),
 #if USE_OPENSSL
     switchedToHttps_(false),
+    parsingTlsHandshake(false),
     sslServerBump(NULL),
     signAlgorithm(Ssl::algSignTrusted),
 #endif
@@ -3210,7 +2449,7 @@ ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) :
 
     // register to receive notice of Squid signal events
     // which may affect long persisting client connections
-    RegisterRunner(this);
+    registerRunner();
 }
 
 void
@@ -3223,8 +2462,10 @@ ConnStateData::start()
             (transparent() || port->disable_pmtu_discovery == DISABLE_PMTU_ALWAYS)) {
 #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
         int i = IP_PMTUDISC_DONT;
-        if (setsockopt(clientConnection->fd, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0)
-            debugs(33, 2, "WARNING: Path MTU discovery disabling failed on " << clientConnection << " : " << xstrerror());
+        if (setsockopt(clientConnection->fd, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0) {
+            int xerrno = errno;
+            debugs(33, 2, "WARNING: Path MTU discovery disabling failed on " << clientConnection << " : " << xstrerr(xerrno));
+        }
 #else
         static bool reported = false;
 
@@ -3331,23 +2572,23 @@ httpAccept(const CommAcceptCbParams &params)
     ++incoming_sockets_accepted;
 
     // Socket is ready, setup the connection manager to start using it
-    ConnStateData *connState = Http::NewServer(xact);
-    AsyncJob::Start(connState); // usually async-calls readSomeData()
+    auto *srv = Http::NewServer(xact);
+    AsyncJob::Start(srv); // usually async-calls readSomeData()
 }
 
 #if USE_OPENSSL
 
 /** Create SSL connection structure and update fd_table */
-static Security::SessionPointer
+static bool
 httpsCreate(const Comm::ConnectionPointer &conn, Security::ContextPtr sslContext)
 {
-    if (auto ssl = Ssl::CreateServer(sslContext, conn->fd, "client https start")) {
+    if (Ssl::CreateServer(sslContext, conn, "client https start")) {
         debugs(33, 5, "will negotate SSL on " << conn);
-        return ssl;
+        return true;
     }
 
     conn->close();
-    return nullptr;
+    return false;
 }
 
 /**
@@ -3360,7 +2601,7 @@ static int
 Squid_SSL_accept(ConnStateData *conn, PF *callback)
 {
     int fd = conn->clientConnection->fd;
-    auto ssl = fd_table[fd].ssl;
+    auto ssl = fd_table[fd].ssl.get();
     int ret;
 
     errno = 0;
@@ -3371,11 +2612,11 @@ Squid_SSL_accept(ConnStateData *conn, PF *callback)
         switch (ssl_error) {
 
         case SSL_ERROR_WANT_READ:
-            Comm::SetSelect(fd, COMM_SELECT_READ, callback, conn, 0);
+            Comm::SetSelect(fd, COMM_SELECT_READ, callback, (callback != NULL ? conn : NULL), 0);
             return 0;
 
         case SSL_ERROR_WANT_WRITE:
-            Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, conn, 0);
+            Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, (callback != NULL ? conn : NULL), 0);
             return 0;
 
         case SSL_ERROR_SYSCALL:
@@ -3409,7 +2650,7 @@ clientNegotiateSSL(int fd, void *data)
 {
     ConnStateData *conn = (ConnStateData *)data;
     X509 *client_cert;
-    auto ssl = fd_table[fd].ssl;
+    auto ssl = fd_table[fd].ssl.get();
 
     int ret;
     if ((ret = Squid_SSL_accept(conn, clientNegotiateSSL)) <= 0) {
@@ -3418,11 +2659,11 @@ clientNegotiateSSL(int fd, void *data)
         return;
     }
 
-    if (SSL_session_reused(ssl)) {
+    if (Security::SessionIsResumed(fd_table[fd].ssl)) {
         debugs(83, 2, "clientNegotiateSSL: Session " << SSL_get_session(ssl) <<
                " reused on FD " << fd << " (" << fd_table[fd].ipaddr << ":" << (int)fd_table[fd].remote_port << ")");
     } else {
-        if (do_debug(83, 4)) {
+        if (Debug::Enabled(83, 4)) {
             /* Write out the SSL session details.. actually the call below, but
              * OpenSSL headers do strange typecasts confusing GCC.. */
             /* PEM_write_SSL_SESSION(debug_log, SSL_get_session(ssl)); */
@@ -3455,8 +2696,8 @@ clientNegotiateSSL(int fd, void *data)
                ")");
     }
 
-    debugs(83, 3, "clientNegotiateSSL: FD " << fd << " negotiated cipher " <<
-           SSL_get_cipher(ssl));
+    // Connection established. Retrieve TLS connection parameters for logging.
+    conn->clientConnection->tlsNegotiations()->retrieveNegotiatedInfo(ssl);
 
     client_cert = SSL_get_peer_certificate(ssl);
 
@@ -3493,11 +2734,10 @@ clientNegotiateSSL(int fd, void *data)
 static void
 httpsEstablish(ConnStateData *connState, Security::ContextPtr sslContext)
 {
-    Security::SessionPointer ssl = nullptr;
     assert(connState);
     const Comm::ConnectionPointer &details = connState->clientConnection;
 
-    if (!sslContext || !(ssl = httpsCreate(details, sslContext)))
+    if (!sslContext || !httpsCreate(details, sslContext))
         return;
 
     typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
@@ -3531,7 +2771,8 @@ httpsSslBumpAccessCheckDone(allow_t answer, void *data)
         debugs(33, 2, HERE << "sslBump not needed for " << connState->clientConnection);
         connState->sslBumpMode = Ssl::bumpNone;
     }
-    connState->fakeAConnectRequest("ssl-bump", connState->inBuf);
+    if (!connState->fakeAConnectRequest("ssl-bump", connState->inBuf))
+        connState->clientConnection->close();
 }
 
 /** handle a new HTTPS connection */
@@ -3555,12 +2796,11 @@ httpsAccept(const CommAcceptCbParams &params)
     if (s->tcp_keepalive.enabled) {
         commSetTcpKeepalive(params.conn->fd, s->tcp_keepalive.idle, s->tcp_keepalive.interval, s->tcp_keepalive.timeout);
     }
-
     ++incoming_sockets_accepted;
 
     // Socket is ready, setup the connection manager to start using it
-    ConnStateData *connState = Https::NewServer(xact);
-    AsyncJob::Start(connState); // usually async-calls postHttpsAccept()
+    auto *srv = Https::NewServer(xact);
+    AsyncJob::Start(srv); // usually async-calls postHttpsAccept()
 }
 
 void
@@ -3586,11 +2826,19 @@ ConnStateData::postHttpsAccept()
         ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, request, NULL);
         acl_checklist->src_addr = clientConnection->remote;
         acl_checklist->my_addr = port->s;
+        // Build a local AccessLogEntry to allow requiresAle() acls work
+        acl_checklist->al = new AccessLogEntry;
+        acl_checklist->al->cache.start_time = current_time;
+        acl_checklist->al->tcpClient = clientConnection;
+        acl_checklist->al->cache.port = port;
+        acl_checklist->al->cache.caddr = log_addr;
+        HTTPMSGUNLOCK(acl_checklist->al->request);
+        acl_checklist->al->request = request;
+        HTTPMSGLOCK(acl_checklist->al->request);
         acl_checklist->nonBlockingCheck(httpsSslBumpAccessCheckDone, this);
         return;
     } else {
-        Security::ContextPtr sslContext = port->staticSslContext.get();
-        httpsEstablish(this, sslContext);
+        httpsEstablish(this, port->secure.staticContext.get());
     }
 }
 
@@ -3624,10 +2872,13 @@ ConnStateData::sslCrtdHandleReply(const Helper::Reply &reply)
                 debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " was successfully recieved from ssl_crtd");
                 if (sslServerBump && (sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare)) {
                     doPeekAndSpliceStep();
-                    auto ssl = fd_table[clientConnection->fd].ssl;
+                    auto ssl = fd_table[clientConnection->fd].ssl.get();
                     bool ret = Ssl::configureSSLUsingPkeyAndCertFromMemory(ssl, reply_message.getBody().c_str(), *port);
                     if (!ret)
                         debugs(33, 5, "Failed to set certificates to ssl object for PeekAndSplice mode");
+
+                    SSL_CTX *sslContext = SSL_get_SSL_CTX(ssl);
+                    Ssl::configureUnconfiguredSslContext(sslContext, signAlgorithm, *port);
                 } else {
                     auto ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port);
                     getSslContextDone(ctx, true);
@@ -3664,7 +2915,7 @@ void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &cer
 
         ACLFilledChecklist checklist(NULL, sslServerBump->request.getRaw(),
                                      clientConnection != NULL ? clientConnection->rfc931 : dash_str);
-        checklist.sslErrors = cbdataReference(sslServerBump->sslErrors);
+        checklist.sslErrors = cbdataReference(sslServerBump->sslErrors());
 
         for (sslproxy_cert_adapt *ca = Config.ssl_client.cert_adapt; ca != NULL; ca = ca->next) {
             // If the algorithm already set, then ignore it.
@@ -3730,10 +2981,14 @@ void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &cer
 void
 ConnStateData::getSslContextStart()
 {
-    // XXX starting SSL with a pipeline of requests still waiting for non-SSL replies?
-    assert(pipeline.count() < 2); // the CONNECT is okay for now. Anything else is a bug.
-    pipeline.terminateAll(0);
-    /* careful: terminateAll(0) above frees request, host, etc. */
+    // If we are called, then CONNECT has succeeded. Finalize it.
+    if (auto xact = pipeline.front()) {
+        if (xact->http && xact->http->request && xact->http->request->method == Http::METHOD_CONNECT)
+            xact->finished();
+        // cannot proceed with encryption if requests wait for plain responses
+        Must(pipeline.empty());
+    }
+    /* careful: finished() above frees request, host, etc. */
 
     if (port->generateHostCertificates) {
         Ssl::CertificateProperties certProperties;
@@ -3746,7 +3001,7 @@ ConnStateData::getSslContextStart()
             debugs(33, 5, "Finding SSL certificate for " << sslBumpCertKey << " in cache");
             Ssl::LocalContextStorage * ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s);
             Security::ContextPtr dynCtx = nullptr;
-            Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache ? ssl_ctx_cache->get(sslBumpCertKey.termedBuf()) : NULL;
+            Security::ContextPointer *cachedCtx = ssl_ctx_cache ? ssl_ctx_cache->get(sslBumpCertKey.termedBuf()) : nullptr;
             if (cachedCtx && (dynCtx = cachedCtx->get())) {
                 debugs(33, 5, "SSL certificate for " << sslBumpCertKey << " found in cache");
                 if (Ssl::verifySslCertificate(dynCtx, certProperties)) {
@@ -3784,9 +3039,12 @@ ConnStateData::getSslContextStart()
         debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName);
         if (sslServerBump && (sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare)) {
             doPeekAndSpliceStep();
-            auto ssl = fd_table[clientConnection->fd].ssl;
+            auto ssl = fd_table[clientConnection->fd].ssl.get();
             if (!Ssl::configureSSL(ssl, certProperties, *port))
                 debugs(33, 5, "Failed to set certificates to ssl object for PeekAndSplice mode");
+
+            SSL_CTX *sslContext = SSL_get_SSL_CTX(ssl);
+            Ssl::configureUnconfiguredSslContext(sslContext, certProperties.signAlgorithm, *port);
         } else {
             auto dynCtx = Ssl::generateSslContext(certProperties, *port);
             getSslContextDone(dynCtx, true);
@@ -3802,24 +3060,17 @@ ConnStateData::getSslContextDone(Security::ContextPtr sslContext, bool isNew)
     // Try to add generated ssl context to storage.
     if (port->generateHostCertificates && isNew) {
 
-        if (signAlgorithm == Ssl::algSignTrusted) {
-            // Add signing certificate to the certificates chain
-            X509 *cert = port->signingCert.get();
-            if (SSL_CTX_add_extra_chain_cert(sslContext, cert)) {
-                // increase the certificate lock
-                CRYPTO_add(&(cert->references),1,CRYPTO_LOCK_X509);
-            } else {
-                const int ssl_error = ERR_get_error();
-                debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain: " << ERR_error_string(ssl_error, NULL));
-            }
-            Ssl::addChainToSslContext(sslContext, port->certsToChain.get());
+        if (sslContext && (signAlgorithm == Ssl::algSignTrusted)) {
+            Ssl::chainCertificatesToSSLContext(sslContext, *port);
+        } else if (signAlgorithm == Ssl::algSignTrusted) {
+            debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain because SSL context chain is invalid!");
         }
         //else it is self-signed or untrusted do not attrach any certificate
 
         Ssl::LocalContextStorage *ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s);
         assert(sslBumpCertKey.size() > 0 && sslBumpCertKey[0] != '\0');
         if (sslContext) {
-            if (!ssl_ctx_cache || !ssl_ctx_cache->add(sslBumpCertKey.termedBuf(), new Ssl::SSL_CTX_Pointer(sslContext))) {
+            if (!ssl_ctx_cache || !ssl_ctx_cache->add(sslBumpCertKey.termedBuf(), new Security::ContextPointer(sslContext))) {
                 // If it is not in storage delete after using. Else storage deleted it.
                 fd_table[clientConnection->fd].dynamicSslContext = sslContext;
             }
@@ -3830,13 +3081,13 @@ ConnStateData::getSslContextDone(Security::ContextPtr sslContext, bool isNew)
 
     // If generated ssl context = NULL, try to use static ssl context.
     if (!sslContext) {
-        if (!port->staticSslContext) {
-            debugs(83, DBG_IMPORTANT, "Closing SSL " << clientConnection->remote << " as lacking SSL context");
+        if (!port->secure.staticContext) {
+            debugs(83, DBG_IMPORTANT, "Closing " << clientConnection->remote << " as lacking TLS context");
             clientConnection->close();
             return;
         } else {
-            debugs(33, 5, HERE << "Using static ssl context.");
-            sslContext = port->staticSslContext.get();
+            debugs(33, 5, "Using static TLS context.");
+            sslContext = port->secure.staticContext.get();
         }
     }
 
@@ -3851,10 +3102,14 @@ ConnStateData::getSslContextDone(Security::ContextPtr sslContext, bool isNew)
                                      this, ConnStateData::requestTimeout);
     commSetConnTimeout(clientConnection, Config.Timeout.request, timeoutCall);
 
-    // Disable the client read handler until CachePeer selection is complete
-    Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
-    Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientNegotiateSSL, this, 0);
     switchedToHttps_ = true;
+
+    auto ssl = fd_table[clientConnection->fd].ssl.get();
+    BIO *b = SSL_get_rbio(ssl);
+    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
+    bio->setReadBufData(inBuf);
+    inBuf.clear();
+    clientNegotiateSSL(clientConnection->fd, this);
 }
 
 void
@@ -3879,19 +3134,67 @@ ConnStateData::switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode)
     if (bumpServerMode == Ssl::bumpServerFirst && !sslServerBump) {
         request->flags.sslPeek = true;
         sslServerBump = new Ssl::ServerBump(request);
-
-        // will call httpsPeeked() with certificate and connection, eventually
-        FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw());
-        return;
     } else if (bumpServerMode == Ssl::bumpPeek || bumpServerMode == Ssl::bumpStare) {
         request->flags.sslPeek = true;
         sslServerBump = new Ssl::ServerBump(request, NULL, bumpServerMode);
-        startPeekAndSplice();
-        return;
     }
 
-    // otherwise, use sslConnectHostOrIp
-    getSslContextStart();
+    // commSetConnTimeout() was called for this request before we switched.
+    // Fix timeout to request_start_timeout
+    typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
+    AsyncCall::Pointer timeoutCall =  JobCallback(33, 5,
+                                      TimeoutDialer, this, ConnStateData::requestTimeout);
+    commSetConnTimeout(clientConnection, Config.Timeout.request_start_timeout, timeoutCall);
+    // Also reset receivedFirstByte_ flag to allow this timeout work in the case we have
+    // a bumbed "connect" request on non transparent port.
+    receivedFirstByte_ = false;
+    // Get more data to peek at Tls
+    parsingTlsHandshake = true;
+    readSomeData();
+}
+
+void
+ConnStateData::parseTlsHandshake()
+{
+    Must(parsingTlsHandshake);
+
+    assert(!inBuf.isEmpty());
+    receivedFirstByte();
+    fd_note(clientConnection->fd, "Parsing TLS handshake");
+
+    bool unsupportedProtocol = false;
+    try {
+        if (!tlsParser.parseHello(inBuf)) {
+            // need more data to finish parsing
+            readSomeData();
+            return;
+        }
+    }
+    catch (const std::exception &ex) {
+        debugs(83, 2, "error on FD " << clientConnection->fd << ": " << ex.what());
+        unsupportedProtocol = true;
+    }
+
+    parsingTlsHandshake = false;
+
+    // Even if the parser failed, each TLS detail should either be set
+    // correctly or still be "unknown"; copying unknown detail is a no-op.
+    clientConnection->tlsNegotiations()->retrieveParsedInfo(tlsParser.details);
+
+    // We should disable read/write handlers
+    Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
+    Comm::SetSelect(clientConnection->fd, COMM_SELECT_WRITE, NULL, NULL, 0);
+
+    if (!sslServerBump) { // BumpClientFirst mode does not use this member
+        getSslContextStart();
+        return;
+    } else if (sslServerBump->act.step1 == Ssl::bumpServerFirst) {
+        // will call httpsPeeked() with certificate and connection, eventually
+        FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw());
+    } else {
+        Must(sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare);
+        startPeekAndSplice(unsupportedProtocol);
+    }
 }
 
 bool
@@ -3904,84 +3207,30 @@ ConnStateData::spliceOnError(const err_type err)
         checklist.conn(this);
         allow_t answer = checklist.fastCheck();
         if (answer == ACCESS_ALLOWED && answer.kind == 1) {
-            splice();
-            return true;
+            return splice();
         }
     }
     return false;
 }
 
-/** negotiate an SSL connection */
-static void
-clientPeekAndSpliceSSL(int fd, void *data)
+void
+ConnStateData::startPeekAndSplice(const bool unsupportedProtocol)
 {
-    ConnStateData *conn = (ConnStateData *)data;
-    auto ssl = fd_table[fd].ssl;
-
-    debugs(83, 5, "Start peek and splice on FD " << fd);
-
-    int ret = 0;
-    if ((ret = Squid_SSL_accept(conn, clientPeekAndSpliceSSL)) < 0)
-        debugs(83, 2, "SSL_accept failed.");
-
-    BIO *b = SSL_get_rbio(ssl);
-    assert(b);
-    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
-    if (ret < 0) {
-        const err_type err = bio->noSslClient() ? ERR_PROTOCOL_UNKNOWN : ERR_SECURE_ACCEPT_FAIL;
-        if (!conn->spliceOnError(err))
-            conn->clientConnection->close();
+    if (unsupportedProtocol) {
+        if (!spliceOnError(ERR_PROTOCOL_UNKNOWN))
+            clientConnection->close();
         return;
     }
 
-    if (bio->rBufData().contentSize() > 0)
-        conn->receivedFirstByte();
-
-    if (bio->gotHello()) {
-        if (conn->serverBump()) {
-            Ssl::Bio::sslFeatures const &features = bio->getFeatures();
-            if (!features.serverName.isEmpty()) {
-                conn->serverBump()->clientSni = features.serverName;
-                conn->resetSslCommonName(features.serverName.c_str());
-            }
+    if (serverBump()) {
+        Security::TlsDetails::Pointer const &details = tlsParser.details;
+        if (details && !details->serverName.isEmpty()) {
+            serverBump()->clientSni = details->serverName;
+            resetSslCommonName(details->serverName.c_str());
         }
-
-        debugs(83, 5, "I got hello. Start forwarding the request!!! ");
-        Comm::SetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
-        Comm::SetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0);
-        conn->startPeekAndSpliceDone();
-        return;
     }
-}
-
-void ConnStateData::startPeekAndSplice()
-{
-    // will call httpsPeeked() with certificate and connection, eventually
-    auto unConfiguredCTX = Ssl::createSSLContext(port->signingCert, port->signPkey, *port);
-    fd_table[clientConnection->fd].dynamicSslContext = unConfiguredCTX;
-
-    if (!httpsCreate(clientConnection, unConfiguredCTX))
-        return;
-
-    // commSetConnTimeout() was called for this request before we switched.
-    // Fix timeout to request_start_timeout
-    typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
-    AsyncCall::Pointer timeoutCall =  JobCallback(33, 5,
-                                      TimeoutDialer, this, ConnStateData::requestTimeout);
-    commSetConnTimeout(clientConnection, Config.Timeout.request_start_timeout, timeoutCall);
-    // Also reset receivedFirstByte_ flag to allow this timeout work in the case we have
-    // a bumbed "connect" request on non transparent port.
-    receivedFirstByte_ = false;
-
-    // Disable the client read handler until CachePeer selection is complete
-    Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
-    Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientPeekAndSpliceSSL, this, 0);
-    switchedToHttps_ = true;
 
-    auto ssl = fd_table[clientConnection->fd].ssl;
-    BIO *b = SSL_get_rbio(ssl);
-    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
-    bio->hold(true);
+    startPeekAndSpliceDone();
 }
 
 void httpsSslBumpStep2AccessCheckDone(allow_t answer, void *data)
@@ -4007,41 +3256,35 @@ void httpsSslBumpStep2AccessCheckDone(allow_t answer, void *data)
         connState->clientConnection->close();
     } else if (bumpAction != Ssl::bumpSplice) {
         connState->startPeekAndSpliceDone();
-    } else
-        connState->splice();
+    } else if (!connState->splice())
+        connState->clientConnection->close();
 }
 
-void
+bool
 ConnStateData::splice()
 {
-    //Normally we can splice here, because we just got client hello message
-    auto ssl = fd_table[clientConnection->fd].ssl;
-    BIO *b = SSL_get_rbio(ssl);
-    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
-    MemBuf const &rbuf = bio->rBufData();
-    debugs(83,5, "Bio for  " << clientConnection << " read " << rbuf.contentSize() << " helo bytes");
-    // Do splice:
-    fd_table[clientConnection->fd].read_method = &default_read_method;
-    fd_table[clientConnection->fd].write_method = &default_write_method;
+    // normally we can splice here, because we just got client hello message
+
+    if (fd_table[clientConnection->fd].ssl.get()) {
+        // Restore default read methods
+        fd_table[clientConnection->fd].read_method = &default_read_method;
+        fd_table[clientConnection->fd].write_method = &default_write_method;
+    }
 
     if (transparent()) {
         // set the current protocol to something sensible (was "HTTPS" for the bumping process)
         // we are sending a faked-up HTTP/1.1 message wrapper, so go with that.
         transferProtocol = Http::ProtocolVersion();
-        // XXX: copy from MemBuf reallocates, not a regression since old code did too
-        SBuf temp;
-        temp.append(rbuf.content(), rbuf.contentSize());
-        fakeAConnectRequest("intercepted TLS spliced", temp);
+        return fakeAConnectRequest("intercepted TLS spliced", inBuf);
     } else {
         // XXX: assuming that there was an HTTP/1.1 CONNECT to begin with...
 
         // reset the current protocol to HTTP/1.1 (was "HTTPS" for the bumping process)
         transferProtocol = Http::ProtocolVersion();
-        // inBuf still has the "CONNECT ..." request data, reset it to SSL hello message
-        inBuf.append(rbuf.content(), rbuf.contentSize());
-        ClientSocketContext::Pointer context = pipeline.front();
+        Http::StreamPointer context = pipeline.front();
         ClientHttpRequest *http = context->http;
         tunnelStart(http);
+        return true;
     }
 }
 
@@ -4050,11 +3293,15 @@ ConnStateData::startPeekAndSpliceDone()
 {
     // This is the Step2 of the SSL bumping
     assert(sslServerBump);
+    Http::StreamPointer context = pipeline.front();
+    ClientHttpRequest *http = context ? context->http : NULL;
+
     if (sslServerBump->step == Ssl::bumpStep1) {
         sslServerBump->step = Ssl::bumpStep2;
         // Run a accessList check to check if want to splice or continue bumping
 
         ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, sslServerBump->request.getRaw(), NULL);
+        acl_checklist->al = http ? http->al : NULL;
         //acl_checklist->src_addr = params.conn->remote;
         //acl_checklist->my_addr = s->s;
         acl_checklist->banAction(allow_t(ACCESS_ALLOWED, Ssl::bumpNone));
@@ -4064,13 +3311,46 @@ ConnStateData::startPeekAndSpliceDone()
         return;
     }
 
-    FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw());
+    // will call httpsPeeked() with certificate and connection, eventually
+    auto unConfiguredCTX = Ssl::createSSLContext(port->signingCert, port->signPkey, *port);
+    fd_table[clientConnection->fd].dynamicSslContext = unConfiguredCTX;
+
+    if (!httpsCreate(clientConnection, unConfiguredCTX))
+        return;
+
+    switchedToHttps_ = true;
+
+    auto ssl = fd_table[clientConnection->fd].ssl.get();
+    BIO *b = SSL_get_rbio(ssl);
+    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
+    bio->setReadBufData(inBuf);
+    bio->hold(true);
+
+    // Here squid should have all of the client hello message so the
+    // Squid_SSL_accept should return 0;
+    // This block exist only to force openSSL parse client hello and detect
+    // ERR_SECURE_ACCEPT_FAIL error, which should be checked and splice if required.
+    int ret = 0;
+    if ((ret = Squid_SSL_accept(this, NULL)) < 0) {
+        debugs(83, 2, "SSL_accept failed.");
+        const err_type err = ERR_SECURE_ACCEPT_FAIL;
+        if (!spliceOnError(err))
+            clientConnection->close();
+        return;
+    }
+
+    // We need to reset inBuf here, to be used by incoming requests in the case
+    // of SSL bump
+    inBuf.clear();
+
+    debugs(83, 5, "Peek and splice at step2 done. Start forwarding the request!!! ");
+    FwdState::Start(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw(), http ? http->al : NULL);
 }
 
 void
 ConnStateData::doPeekAndSpliceStep()
 {
-    auto ssl = fd_table[clientConnection->fd].ssl;
+    auto ssl = fd_table[clientConnection->fd].ssl.get();
     BIO *b = SSL_get_rbio(ssl);
     assert(b);
     Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
@@ -4104,7 +3384,7 @@ ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection)
 
 #endif /* USE_OPENSSL */
 
-void
+bool
 ConnStateData::fakeAConnectRequest(const char *reason, const SBuf &payload)
 {
     // fake a CONNECT request to force connState to tunnel
@@ -4135,8 +3415,9 @@ ConnStateData::fakeAConnectRequest(const char *reason, const SBuf &payload)
 
     if (!ret) {
         debugs(33, 2, "Failed to start fake CONNECT request for " << reason << " connection: " << clientConnection);
-        clientConnection->close();
+        return false;
     }
+    return true;
 }
 
 /// check FD after clientHttp[s]ConnectionOpened, adjust HttpSockets as needed
@@ -4172,7 +3453,7 @@ static void
 clientHttpConnectionsOpen(void)
 {
     for (AnyP::PortCfgPointer s = HttpPortList; s != NULL; s = s->next) {
-        const char *scheme = AnyP::UriScheme(s->transport.protocol).c_str();
+        const SBuf &scheme = AnyP::UriScheme(s->transport.protocol).image();
 
         if (MAXTCPLISTENPORTS == NHttpSockets) {
             debugs(1, DBG_IMPORTANT, "WARNING: You have too many '" << scheme << "_port' lines.");
@@ -4186,7 +3467,7 @@ clientHttpConnectionsOpen(void)
                 debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << scheme << "_port " << s->s);
                 s->flags.tunnelSslBumping = false;
             }
-            if (!s->staticSslContext && !s->generateHostCertificates) {
+            if (!s->secure.staticContext && !s->generateHostCertificates) {
                 debugs(1, DBG_IMPORTANT, "Will not bump SSL at " << scheme << "_port " << s->s << " due to TLS initialization failure.");
                 s->flags.tunnelSslBumping = false;
                 if (s->transport.protocol == AnyP::PROTO_HTTP)
@@ -4199,7 +3480,7 @@ clientHttpConnectionsOpen(void)
             }
         }
 
-        if (s->secure.encryptTransport && !s->staticSslContext) {
+        if (s->secure.encryptTransport && !s->secure.staticContext) {
             debugs(1, DBG_CRITICAL, "ERROR: Ignoring " << scheme << "_port " << s->s << " due to TLS context initialization failure.");
             continue;
         }
@@ -4325,7 +3606,7 @@ clientConnectionsClose()
 int
 varyEvaluateMatch(StoreEntry * entry, HttpRequest * request)
 {
-    const char *vary = request->vary_headers;
+    SBuf vary(request->vary_headers);
     int has_vary = entry->getReply()->header.has(Http::HdrType::VARY);
 #if X_ACCELERATOR_VARY
 
@@ -4333,12 +3614,12 @@ varyEvaluateMatch(StoreEntry * entry, HttpRequest * request)
         entry->getReply()->header.has(Http::HdrType::HDR_X_ACCELERATOR_VARY);
 #endif
 
-    if (!has_vary || !entry->mem_obj->vary_headers) {
-        if (vary) {
+    if (!has_vary || entry->mem_obj->vary_headers.isEmpty()) {
+        if (!vary.isEmpty()) {
             /* Oops... something odd is going on here.. */
             debugs(33, DBG_IMPORTANT, "varyEvaluateMatch: Oops. Not a Vary object on second attempt, '" <<
                    entry->mem_obj->urlXXX() << "' '" << vary << "'");
-            safe_free(request->vary_headers);
+            request->vary_headers.clear();
             return VARY_CANCEL;
         }
 
@@ -4352,8 +3633,8 @@ varyEvaluateMatch(StoreEntry * entry, HttpRequest * request)
          */
         vary = httpMakeVaryMark(request, entry->getReply());
 
-        if (vary) {
-            request->vary_headers = xstrdup(vary);
+        if (!vary.isEmpty()) {
+            request->vary_headers = vary;
             return VARY_OTHER;
         } else {
             /* Ouch.. we cannot handle this kind of variance */
@@ -4361,18 +3642,18 @@ varyEvaluateMatch(StoreEntry * entry, HttpRequest * request)
             return VARY_CANCEL;
         }
     } else {
-        if (!vary) {
+        if (vary.isEmpty()) {
             vary = httpMakeVaryMark(request, entry->getReply());
 
-            if (vary)
-                request->vary_headers = xstrdup(vary);
+            if (!vary.isEmpty())
+                request->vary_headers = vary;
         }
 
-        if (!vary) {
+        if (vary.isEmpty()) {
             /* Ouch.. we cannot handle this kind of variance */
             /* XXX This cannot really happen, but just to be complete */
             return VARY_CANCEL;
-        } else if (strcmp(vary, entry->mem_obj->vary_headers) == 0) {
+        } else if (vary.cmp(entry->mem_obj->vary_headers) == 0) {
             return VARY_MATCH;
         } else {
             /* Oops.. we have already been here and still haven't
@@ -4487,7 +3768,7 @@ ConnStateData::finishDechunkingRequest(bool withSuccess)
         Must(!bodyPipe); // we rely on it being nil after we are done with body
         if (withSuccess) {
             Must(myPipe->bodySizeKnown());
-            ClientSocketContext::Pointer context = pipeline.front();
+            Http::StreamPointer context = pipeline.front();
             if (context != NULL && context->http && context->http->request)
                 context->http->request->setContentLength(myPipe->bodySize());
         }
@@ -4506,8 +3787,17 @@ ConnStateData::sendControlMsg(HttpControlMsg msg)
         return;
     }
 
+    // HTTP/1 1xx status messages are only valid when there is a transaction to trigger them
     if (!pipeline.empty()) {
-        pipeline.front()->writeControlMsg(msg); // will call msg.cbSuccess
+        HttpReply::Pointer rep(msg.reply);
+        Must(rep);
+        // remember the callback
+        cbControlMsgSent = msg.cbSuccess;
+
+        typedef CommCbMemFunT<HttpControlMsgSink, CommIoCbParams> Dialer;
+        AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, HttpControlMsgSink::wroteControlMsg);
+
+        writeControlMsgAndCall(rep.getRaw(), call);
         return;
     }
 
@@ -4619,7 +3909,7 @@ ConnStateData::handleIdleClientPinnedTlsRead()
     // renegotiations. We should close the connection except for the last case.
 
     Must(pinning.serverConnection != nullptr);
-    SSL *ssl = fd_table[pinning.serverConnection->fd].ssl;
+    auto ssl = fd_table[pinning.serverConnection->fd].ssl.get();
     if (!ssl)
         return false;
 
@@ -4746,3 +4036,36 @@ ConnStateData::unpinConnection(const bool andClose)
      * connection has gone away */
 }
 
+void
+ConnStateData::checkLogging()
+{
+    // if we are parsing request body, its request is responsible for logging
+    if (bodyPipe)
+        return;
+
+    // a request currently using this connection is responsible for logging
+    if (!pipeline.empty() && pipeline.back()->mayUseConnection())
+        return;
+
+    /* Either we are waiting for the very first transaction, or
+     * we are done with the Nth transaction and are waiting for N+1st.
+     * XXX: We assume that if anything was added to inBuf, then it could
+     * only be consumed by actions already covered by the above checks.
+     */
+
+    // do not log connections that closed after a transaction (it is normal)
+    // TODO: access_log needs ACLs to match received-no-bytes connections
+    // XXX: TLS may return here even though we got no transactions yet
+    // XXX: PROXY protocol may return here even though we got no
+    // transactions yet
+    if (receivedFirstByte_ && inBuf.isEmpty())
+        return;
+
+    /* Create a temporary ClientHttpRequest object. Its destructor will log. */
+    ClientHttpRequest http(this);
+    http.req_sz = inBuf.length();
+    char const *uri = "error:transaction-end-before-headers";
+    http.uri = xstrdup(uri);
+    setLogUri(&http, uri);
+}
+