]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/client_side_request.cc
transaction_initiator ACL for detecting various unusual transactions
[thirdparty/squid.git] / src / client_side_request.cc
index d156d30748ead34f4b8a500213ef0e536606bba6..d4628cbcc2f09cd4bc914b3a79ae1098498f827e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
@@ -37,6 +37,7 @@
 #include "helper.h"
 #include "helper/Reply.h"
 #include "http.h"
+#include "http/Stream.h"
 #include "HttpHdrCc.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
@@ -168,16 +169,18 @@ ClientHttpRequest::ClientHttpRequest(ConnStateData * aConn) :
     setConn(aConn);
     al = new AccessLogEntry;
     al->cache.start_time = current_time;
-    al->tcpClient = clientConnection = aConn->clientConnection;
-    al->cache.port = aConn->port;
-    al->cache.caddr = aConn->log_addr;
+    if (aConn) {
+        al->tcpClient = clientConnection = aConn->clientConnection;
+        al->cache.port = aConn->port;
+        al->cache.caddr = aConn->log_addr;
 
 #if USE_OPENSSL
-    if (aConn->clientConnection != NULL && aConn->clientConnection->isOpen()) {
-        if (SSL *ssl = fd_table[aConn->clientConnection->fd].ssl)
-            al->cache.sslClientCert.reset(SSL_get_peer_certificate(ssl));
-    }
+        if (aConn->clientConnection != NULL && aConn->clientConnection->isOpen()) {
+            if (auto ssl = fd_table[aConn->clientConnection->fd].ssl.get())
+                al->cache.sslClientCert.resetWithoutLocking(SSL_get_peer_certificate(ssl));
+        }
 #endif
+    }
     dlinkAdd(this, &active, &ClientActiveRequests);
 }
 
@@ -190,7 +193,7 @@ ClientHttpRequest::onlyIfCached()const
 {
     assert(request);
     return request->cache_control &&
-           request->cache_control->onlyIfCached();
+           request->cache_control->hasOnlyIfCached();
 }
 
 /**
@@ -274,7 +277,6 @@ ClientHttpRequest::~ClientHttpRequest()
     /* the ICP check here was erroneous
      * - StoreEntry::releaseRequest was always called if entry was valid
      */
-    assert(logType < LOG_TYPE_MAX);
 
     logRequest();
 
@@ -318,7 +320,7 @@ ClientHttpRequest::~ClientHttpRequest()
 int
 clientBeginRequest(const HttpRequestMethod& method, char const *url, CSCB * streamcallback,
                    CSD * streamdetach, ClientStreamData streamdata, HttpHeader const *header,
-                   char *tailbuf, size_t taillen)
+                   char *tailbuf, size_t taillen, const MasterXaction::Pointer &mx)
 {
     size_t url_sz;
     ClientHttpRequest *http = new ClientHttpRequest(NULL);
@@ -344,7 +346,7 @@ clientBeginRequest(const HttpRequestMethod& method, char const *url, CSCB * stre
     http->uri = (char *)xcalloc(url_sz, 1);
     strcpy(http->uri, url);
 
-    if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, method)) == NULL) {
+    if ((request = HttpRequest::FromUrl(http->uri, mx, method)) == NULL) {
         debugs(85, 5, "Invalid URL: " << http->uri);
         return -1;
     }
@@ -355,7 +357,7 @@ clientBeginRequest(const HttpRequestMethod& method, char const *url, CSCB * stre
      * correctness.
      */
     if (header)
-        request->header.update(header, NULL);
+        request->header.update(header);
 
     http->log_uri = xstrdup(urlCanonicalClean(request));
 
@@ -366,8 +368,6 @@ clientBeginRequest(const HttpRequestMethod& method, char const *url, CSCB * stre
      */
     request->flags.accelerated = http->flags.accel;
 
-    request->flags.internalClient = true;
-
     /* this is an internally created
      * request, not subject to acceleration
      * target overrides */
@@ -560,7 +560,7 @@ ClientRequestContext::hostHeaderVerifyFailed(const char *A, const char *B)
     // NP: we do not yet handle CONNECT tunnels well, so ignore for them
     if (!Config.onoff.hostStrictVerify && http->request->method != Http::METHOD_CONNECT) {
         debugs(85, 3, "SECURITY ALERT: Host header forgery detected on " << http->getConn()->clientConnection <<
-               " (" << A << " does not match " << B << ") on URL: " << urlCanonical(http->request));
+               " (" << A << " does not match " << B << ") on URL: " << http->request->effectiveRequestUri());
 
         // NP: it is tempting to use 'flags.noCache' but that is all about READing cache data.
         // The problems here are about WRITE for new cache content, which means flags.cachable
@@ -574,8 +574,9 @@ ClientRequestContext::hostHeaderVerifyFailed(const char *A, const char *B)
 
     debugs(85, DBG_IMPORTANT, "SECURITY ALERT: Host header forgery detected on " <<
            http->getConn()->clientConnection << " (" << A << " does not match " << B << ")");
-    debugs(85, DBG_IMPORTANT, "SECURITY ALERT: By user agent: " << http->request->header.getStr(HDR_USER_AGENT));
-    debugs(85, DBG_IMPORTANT, "SECURITY ALERT: on URL: " << urlCanonical(http->request));
+    if (const char *ua = http->request->header.getStr(Http::HdrType::USER_AGENT))
+        debugs(85, DBG_IMPORTANT, "SECURITY ALERT: By user agent: " << ua);
+    debugs(85, DBG_IMPORTANT, "SECURITY ALERT: on URL: " << http->request->effectiveRequestUri());
 
     // IP address validation for Host: failed. reject the connection.
     clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->prev->data;
@@ -600,7 +601,7 @@ void
 ClientRequestContext::hostHeaderVerify()
 {
     // Require a Host: header.
-    const char *host = http->request->header.getStr(HDR_HOST);
+    const char *host = http->request->header.getStr(Http::HdrType::HOST);
 
     if (!host) {
         // TODO: dump out the HTTP/1.1 error about missing host header.
@@ -697,14 +698,14 @@ ClientRequestContext::clientAccessCheck()
 #if FOLLOW_X_FORWARDED_FOR
     if (!http->request->flags.doneFollowXff() &&
             Config.accessList.followXFF &&
-            http->request->header.has(HDR_X_FORWARDED_FOR)) {
+            http->request->header.has(Http::HdrType::X_FORWARDED_FOR)) {
 
         /* we always trust the direct client address for actual use */
         http->request->indirect_client_addr = http->request->client_addr;
         http->request->indirect_client_addr.port(0);
 
         /* setup the XFF iterator for processing */
-        http->request->x_forwarded_for_iterator = http->request->header.getList(HDR_X_FORWARDED_FOR);
+        http->request->x_forwarded_for_iterator = http->request->header.getList(Http::HdrType::X_FORWARDED_FOR);
 
         /* begin by checking to see if we trust direct client enough to walk XFF */
         acl_checklist = clientAclChecklistCreate(Config.accessList.followXFF, http);
@@ -834,10 +835,8 @@ ClientRequestContext::clientAccessCheckDone(const allow_t &answer)
     }
 
     /* ACCESS_ALLOWED continues here ... */
-    safe_free(http->uri);
-
-    http->uri = xstrdup(urlCanonical(http->request));
-
+    xfree(http->uri);
+    http->uri = SBufToCstring(http->request->effectiveRequestUri());
     http->doCallouts();
 }
 
@@ -854,7 +853,7 @@ ClientHttpRequest::noteAdaptationAclCheckDone(Adaptation::ServiceGroupPointer g)
             ih->rfc931 = getConn()->clientConnection->rfc931;
 #if USE_OPENSSL
             if (getConn()->clientConnection->isOpen()) {
-                ih->ssluser = sslGetUserEmail(fd_table[getConn()->clientConnection->fd].ssl);
+                ih->ssluser = sslGetUserEmail(fd_table[getConn()->clientConnection->fd].ssl.get());
             }
 #endif
         }
@@ -884,8 +883,7 @@ clientRedirectAccessCheckDone(allow_t answer, void *data)
     if (answer == ACCESS_ALLOWED)
         redirectStart(http, clientRedirectDoneWrapper, context);
     else {
-        Helper::Reply nilReply;
-        nilReply.result = Helper::Error;
+        Helper::Reply const nilReply(Helper::Error);
         context->clientRedirectDone(nilReply);
     }
 }
@@ -894,7 +892,7 @@ void
 ClientRequestContext::clientRedirectStart()
 {
     debugs(33, 5, HERE << "'" << http->uri << "'");
-    (void)SyncNotes(*http->al, *http->request);
+    http->al->syncNotes(http->request);
     if (Config.accessList.redirector) {
         acl_checklist = clientAclChecklistCreate(Config.accessList.redirector, http);
         acl_checklist->nonBlockingCheck(clientRedirectAccessCheckDone, this);
@@ -917,8 +915,7 @@ clientStoreIdAccessCheckDone(allow_t answer, void *data)
         storeIdStart(http, clientStoreIdDoneWrapper, context);
     else {
         debugs(85, 3, "access denied expected ERR reply handling: " << answer);
-        Helper::Reply nilReply;
-        nilReply.result = Helper::Error;
+        Helper::Reply const nilReply(Helper::Error);
         context->clientStoreIdDone(nilReply);
     }
 }
@@ -1019,19 +1016,19 @@ clientCheckPinning(ClientHttpRequest * http)
      * is already pinned if it was pinned earlier due to proxy auth
      */
     if (!request->flags.connectionAuth) {
-        if (req_hdr->has(HDR_AUTHORIZATION) || req_hdr->has(HDR_PROXY_AUTHORIZATION)) {
+        if (req_hdr->has(Http::HdrType::AUTHORIZATION) || req_hdr->has(Http::HdrType::PROXY_AUTHORIZATION)) {
             HttpHeaderPos pos = HttpHeaderInitPos;
             HttpHeaderEntry *e;
             int may_pin = 0;
             while ((e = req_hdr->getEntry(&pos))) {
-                if (e->id == HDR_AUTHORIZATION || e->id == HDR_PROXY_AUTHORIZATION) {
+                if (e->id == Http::HdrType::AUTHORIZATION || e->id == Http::HdrType::PROXY_AUTHORIZATION) {
                     const char *value = e->value.rawBuf();
                     if (strncasecmp(value, "NTLM ", 5) == 0
                             ||
                             strncasecmp(value, "Negotiate ", 10) == 0
                             ||
                             strncasecmp(value, "Kerberos ", 9) == 0) {
-                        if (e->id == HDR_AUTHORIZATION) {
+                        if (e->id == Http::HdrType::AUTHORIZATION) {
                             request->flags.connectionAuth = true;
                             may_pin = 1;
                         } else {
@@ -1055,10 +1052,9 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
     HttpRequest *request = http->request;
     HttpHeader *req_hdr = &request->header;
     bool no_cache = false;
-    const char *str;
 
     request->imslen = -1;
-    request->ims = req_hdr->getTime(HDR_IF_MODIFIED_SINCE);
+    request->ims = req_hdr->getTime(Http::HdrType::IF_MODIFIED_SINCE);
 
     if (request->ims > 0)
         request->flags.ims = true;
@@ -1069,30 +1065,8 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
                 no_cache=true;
 
             // RFC 2616: treat Pragma:no-cache as if it was Cache-Control:no-cache when Cache-Control is missing
-        } else if (req_hdr->has(HDR_PRAGMA))
-            no_cache = req_hdr->hasListMember(HDR_PRAGMA,"no-cache",',');
-
-        /*
-        * Work around for supporting the Reload button in IE browsers when Squid
-        * is used as an accelerator or transparent proxy, by turning accelerated
-        * IMS request to no-cache requests. Now knows about IE 5.5 fix (is
-        * actually only fixed in SP1, but we can't tell whether we are talking to
-        * SP1 or not so all 5.5 versions are treated 'normally').
-        */
-        if (Config.onoff.ie_refresh) {
-            if (http->flags.accel && request->flags.ims) {
-                if ((str = req_hdr->getStr(HDR_USER_AGENT))) {
-                    if (strstr(str, "MSIE 5.01") != NULL)
-                        no_cache=true;
-                    else if (strstr(str, "MSIE 5.0") != NULL)
-                        no_cache=true;
-                    else if (strstr(str, "MSIE 4.") != NULL)
-                        no_cache=true;
-                    else if (strstr(str, "MSIE 3.") != NULL)
-                        no_cache=true;
-                }
-            }
-        }
+        } else if (req_hdr->has(Http::HdrType::PRAGMA))
+            no_cache = req_hdr->hasListMember(Http::HdrType::PRAGMA,"no-cache",',');
     }
 
     if (request->method == Http::METHOD_OTHER) {
@@ -1139,12 +1113,12 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
      * If these headers appear on any other type of request, delete them now.
      */
     else {
-        req_hdr->delById(HDR_RANGE);
-        req_hdr->delById(HDR_REQUEST_RANGE);
+        req_hdr->delById(Http::HdrType::RANGE);
+        req_hdr->delById(Http::HdrType::REQUEST_RANGE);
         request->ignoreRange("neither HEAD nor GET");
     }
 
-    if (req_hdr->has(HDR_AUTHORIZATION))
+    if (req_hdr->has(Http::HdrType::AUTHORIZATION))
         request->flags.auth = true;
 
     clientCheckPinning(http);
@@ -1152,8 +1126,8 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
     if (!request->url.userInfo().isEmpty())
         request->flags.auth = true;
 
-    if (req_hdr->has(HDR_VIA)) {
-        String s = req_hdr->getList(HDR_VIA);
+    if (req_hdr->has(Http::HdrType::VIA)) {
+        String s = req_hdr->getList(Http::HdrType::VIA);
         /*
          * ThisCache cannot be a member of Via header, "1.1 ThisCache" can.
          * Note ThisCache2 has a space prepended to the hostname so we don't
@@ -1176,8 +1150,8 @@ clientInterpretRequestHeaders(ClientHttpRequest * http)
 
 #if USE_FORW_VIA_DB
 
-    if (req_hdr->has(HDR_X_FORWARDED_FOR)) {
-        String s = req_hdr->getList(HDR_X_FORWARDED_FOR);
+    if (req_hdr->has(Http::HdrType::X_FORWARDED_FOR)) {
+        String s = req_hdr->getList(Http::HdrType::X_FORWARDED_FOR);
         fvdbCountForw(s.termedBuf());
         s.clean();
     }
@@ -1230,8 +1204,8 @@ ClientRequestContext::clientRedirectDone(const Helper::Reply &reply)
 
     // Put helper response Notes into the transaction state record (ALE) eventually
     // do it early to ensure that no matter what the outcome the notes are present.
-    if (http->al != NULL)
-        (void)SyncNotes(*http->al, *old_request);
+    if (http->al)
+        http->al->syncNotes(old_request);
 
     UpdateRequestNotes(http->getConn(), *old_request, reply.notes);
 
@@ -1300,8 +1274,8 @@ ClientRequestContext::clientRedirectDone(const Helper::Reply &reply)
                 // XXX: validate the URL properly *without* generating a whole new request object right here.
                 // XXX: the clone() should be done only AFTER we know the new URL is valid.
                 HttpRequest *new_request = old_request->clone();
-                if (urlParse(old_request->method, const_cast<char*>(urlNote), new_request)) {
-                    debugs(61,2, HERE << "URL-rewriter diverts URL from " << urlCanonical(old_request) << " to " << urlCanonical(new_request));
+                if (urlParse(old_request->method, const_cast<char*>(urlNote), *new_request)) {
+                    debugs(61, 2, "URL-rewriter diverts URL from " << old_request->effectiveRequestUri() << " to " << new_request->effectiveRequestUri());
 
                     // update the new request to flag the re-writing was done on it
                     new_request->flags.redirected = true;
@@ -1314,8 +1288,8 @@ ClientRequestContext::clientRedirectDone(const Helper::Reply &reply)
                     }
 
                     // update the current working ClientHttpRequest fields
-                    safe_free(http->uri);
-                    http->uri = xstrdup(urlCanonical(new_request));
+                    xfree(http->uri);
+                    http->uri = SBufToCstring(new_request->effectiveRequestUri());
                     HTTPMSGUNLOCK(old_request);
                     http->request = new_request;
                     HTTPMSGLOCK(http->request);
@@ -1353,8 +1327,8 @@ ClientRequestContext::clientStoreIdDone(const Helper::Reply &reply)
 
     // Put helper response Notes into the transaction state record (ALE) eventually
     // do it early to ensure that no matter what the outcome the notes are present.
-    if (http->al != NULL)
-        (void)SyncNotes(*http->al, *old_request);
+    if (http->al)
+        http->al->syncNotes(old_request);
 
     UpdateRequestNotes(http->getConn(), *old_request, reply.notes);
 
@@ -1423,7 +1397,10 @@ void
 ClientRequestContext::checkNoCacheDone(const allow_t &answer)
 {
     acl_checklist = NULL;
-    http->request->flags.cachable = (answer == ACCESS_ALLOWED);
+    if (answer == ACCESS_DENIED) {
+        http->request->flags.noCache = true; // dont read reply from cache
+        http->request->flags.cachable = false; // dont store reply into cache
+    }
     http->doCallouts();
 }
 
@@ -1431,12 +1408,34 @@ ClientRequestContext::checkNoCacheDone(const allow_t &answer)
 bool
 ClientRequestContext::sslBumpAccessCheck()
 {
-    // If SSL connection tunneling or bumping decision has been made, obey it.
+    if (!http->getConn()) {
+        http->al->ssl.bumpMode = Ssl::bumpEnd; // SslBump does not apply; log -
+        return false;
+    }
+
     const Ssl::BumpMode bumpMode = http->getConn()->sslBumpMode;
+    if (http->request->flags.forceTunnel) {
+        debugs(85, 5, "not needed; already decided to tunnel " << http->getConn());
+        if (bumpMode != Ssl::bumpEnd)
+            http->al->ssl.bumpMode = bumpMode; // inherited from bumped connection
+        return false;
+    }
+
+    // If SSL connection tunneling or bumping decision has been made, obey it.
     if (bumpMode != Ssl::bumpEnd) {
         debugs(85, 5, HERE << "SslBump already decided (" << bumpMode <<
                "), " << "ignoring ssl_bump for " << http->getConn());
-        if (!http->getConn()->serverBump())
+
+        // We need the following "if" for transparently bumped TLS connection,
+        // because in this case we are running ssl_bump access list before
+        // the doCallouts runs. It can be removed after the bug #4340 fixed.
+        // We do not want to proceed to bumping steps:
+        //  - if the TLS connection with the client is already established
+        //    because we are accepting normal HTTP requests on TLS port,
+        //    or because of the client-first bumping mode
+        //  - When the bumping is already started
+        if (!http->getConn()->switchedToHttps() &&
+                !http->getConn()->serverBump())
             http->sslBumpNeed(bumpMode); // for processRequest() to bump if needed and not already bumped
         http->al->ssl.bumpMode = bumpMode; // inherited from bumped connection
         return false;
@@ -1463,6 +1462,13 @@ ClientRequestContext::sslBumpAccessCheck()
         return false;
     }
 
+    if (error) {
+        debugs(85, 5, "SslBump applies. Force bump action on error " << errorTypeName(error->type));
+        http->sslBumpNeed(Ssl::bumpBump);
+        http->al->ssl.bumpMode = Ssl::bumpBump;
+        return false;
+    }
+
     debugs(85, 5, HERE << "SslBump possible, checking ACL");
 
     ACLFilledChecklist *aclChecklist = clientAclChecklistCreate(Config.accessList.ssl_bump, http);
@@ -1491,10 +1497,19 @@ ClientRequestContext::sslBumpAccessCheckDone(const allow_t &answer)
         return;
 
     const Ssl::BumpMode bumpMode = answer == ACCESS_ALLOWED ?
-                                   static_cast<Ssl::BumpMode>(answer.kind) : Ssl::bumpNone;
+                                   static_cast<Ssl::BumpMode>(answer.kind) : Ssl::bumpSplice;
     http->sslBumpNeed(bumpMode); // for processRequest() to bump if needed
     http->al->ssl.bumpMode = bumpMode; // for logging
 
+    if (bumpMode == Ssl::bumpTerminate) {
+        const Comm::ConnectionPointer clientConn = http->getConn() ? http->getConn()->clientConnection : nullptr;
+        if (Comm::IsConnOpen(clientConn)) {
+            debugs(85, 3, "closing after Ssl::bumpTerminate ");
+            clientConn->close();
+        }
+        return;
+    }
+
     http->doCallouts();
 }
 #endif
@@ -1509,15 +1524,19 @@ ClientHttpRequest::processRequest()
 {
     debugs(85, 4, request->method << ' ' << uri);
 
-    if (request->method == Http::METHOD_CONNECT && !redirect.status) {
+    const bool untouchedConnect = request->method == Http::METHOD_CONNECT && !redirect.status;
+
 #if USE_OPENSSL
-        if (sslBumpNeeded()) {
-            sslBumpStart();
-            return;
-        }
+    if (untouchedConnect && sslBumpNeeded()) {
+        assert(!request->flags.forceTunnel);
+        sslBumpStart();
+        return;
+    }
 #endif
+
+    if (untouchedConnect || request->flags.forceTunnel) {
         getConn()->stopReading(); // tunnels read for themselves
-        tunnelStart(this, &out.size, &al->http.code, al);
+        tunnelStart(this);
         return;
     }
 
@@ -1529,7 +1548,7 @@ ClientHttpRequest::httpStart()
 {
     PROF_start(httpStart);
     logType = LOG_TAG_NONE;
-    debugs(85, 4, LogTags_str[logType] << " for '" << uri << "'");
+    debugs(85, 4, logType.c_str() << " for '" << uri << "'");
 
     /* no one should have touched this */
     assert(out.offset == 0);
@@ -1684,17 +1703,18 @@ ClientHttpRequest::doCallouts()
 {
     assert(calloutContext);
 
+    auto &ale = calloutContext->http->al;
     /*Save the original request for logging purposes*/
-    if (!calloutContext->http->al->request) {
-        calloutContext->http->al->request = request;
-        HTTPMSGLOCK(calloutContext->http->al->request);
+    if (!ale->request) {
+        ale->request = request;
+        HTTPMSGLOCK(ale->request);
 
-        NotePairs &notes = SyncNotes(*calloutContext->http->al, *calloutContext->http->request);
         // Make the previously set client connection ID available as annotation.
         if (ConnStateData *csd = calloutContext->http->getConn()) {
-            if (!csd->connectionTag().isEmpty())
-                notes.add("clt_conn_tag", SBuf(csd->connectionTag()).c_str());
+            if (!csd->notes()->empty())
+                calloutContext->http->request->notes()->appendNewOnly(csd->notes().getRaw());
         }
+        ale->syncNotes(calloutContext->http->request);
     }
 
     if (!calloutContext->error) {
@@ -1725,7 +1745,6 @@ ClientHttpRequest::doCallouts()
 
         if (!calloutContext->redirect_done) {
             calloutContext->redirect_done = true;
-            assert(calloutContext->redirect_state == REDIRECT_NONE);
 
             if (Config.Program.redirect) {
                 debugs(83, 3, HERE << "Doing calloutContext->clientRedirectStart()");
@@ -1744,7 +1763,6 @@ ClientHttpRequest::doCallouts()
 
         if (!calloutContext->store_id_done) {
             calloutContext->store_id_done = true;
-            assert(calloutContext->store_id_state == REDIRECT_NONE);
 
             if (Config.Program.store_id) {
                 debugs(83, 3,"Doing calloutContext->clientStoreIdStart()");
@@ -1796,8 +1814,9 @@ ClientHttpRequest::doCallouts()
     }
 
 #if USE_OPENSSL
-    // We need to check for SslBump even if the calloutContext->error is set
-    // because bumping may require delaying the error until after CONNECT.
+    // Even with calloutContext->error, we call sslBumpAccessCheck() to decide
+    // whether SslBump applies to this transaction. If it applies, we will
+    // attempt to bump the client to serve the error.
     if (!calloutContext->sslBumpCheckDone) {
         calloutContext->sslBumpCheckDone = true;
         if (calloutContext->sslBumpAccessCheck())
@@ -1807,14 +1826,16 @@ ClientHttpRequest::doCallouts()
 #endif
 
     if (calloutContext->error) {
-        const char *storeUri = request->storeId();
-        StoreEntry *e= storeCreateEntry(storeUri, storeUri, request->flags, request->method);
+        // XXX: prformance regression. c_str() reallocates
+        SBuf storeUriBuf(request->storeId());
+        const char *storeUri = storeUriBuf.c_str();
+        StoreEntry *e = storeCreateEntry(storeUri, storeUri, request->flags, request->method);
 #if USE_OPENSSL
         if (sslBumpNeeded()) {
             // We have to serve an error, so bump the client first.
             sslBumpNeed(Ssl::bumpClientFirst);
             // set final error but delay sending until we bump
-            Ssl::ServerBump *srvBump = new Ssl::ServerBump(request, e);
+            Ssl::ServerBump *srvBump = new Ssl::ServerBump(request, e, Ssl::bumpClientFirst);
             errorAppendEntry(e, calloutContext->error);
             calloutContext->error = NULL;
             getConn()->setServerBump(srvBump);
@@ -1829,7 +1850,7 @@ ClientHttpRequest::doCallouts()
             repContext->setReplyToStoreEntry(e, "immediate SslBump error");
             errorAppendEntry(e, calloutContext->error);
             calloutContext->error = NULL;
-            if (calloutContext->readNextRequest)
+            if (calloutContext->readNextRequest && getConn())
                 getConn()->flags.readMore = true; // resume any pipeline reads.
             node = (clientStreamNode *)client_stream.tail->data;
             clientStreamRead(node, this, node->readBuffer);
@@ -1856,10 +1877,6 @@ ClientHttpRequest::doCallouts()
 #endif
 }
 
-#if !_USE_INLINE_
-#include "client_side_request.cci"
-#endif
-
 #if USE_ADAPTATION
 /// Initiate an asynchronous adaptation transaction which will call us back.
 void
@@ -1885,7 +1902,7 @@ ClientHttpRequest::noteAdaptationAnswer(const Adaptation::Answer &answer)
 
     switch (answer.kind) {
     case Adaptation::Answer::akForward:
-        handleAdaptedHeader(const_cast<HttpMsg*>(answer.message.getRaw()));
+        handleAdaptedHeader(const_cast<Http::Message*>(answer.message.getRaw()));
         break;
 
     case Adaptation::Answer::akBlock:
@@ -1899,7 +1916,7 @@ ClientHttpRequest::noteAdaptationAnswer(const Adaptation::Answer &answer)
 }
 
 void
-ClientHttpRequest::handleAdaptedHeader(HttpMsg *msg)
+ClientHttpRequest::handleAdaptedHeader(Http::Message *msg)
 {
     assert(msg);
 
@@ -1912,14 +1929,14 @@ ClientHttpRequest::handleAdaptedHeader(HttpMsg *msg)
         HTTPMSGLOCK(request);
 
         // update the new message to flag whether URL re-writing was done on it
-        if (strcmp(urlCanonical(request),uri) != 0)
+        if (request->effectiveRequestUri().cmp(uri) != 0)
             request->flags.redirected = 1;
 
         /*
          * Store the new URI for logging
          */
         xfree(uri);
-        uri = xstrdup(urlCanonical(request));
+        uri = SBufToCstring(request->effectiveRequestUri());
         setLogUri(this, urlCanonicalClean(request));
         assert(request->method.id());
     } else if (HttpReply *new_rep = dynamic_cast<HttpReply*>(msg)) {
@@ -2076,6 +2093,21 @@ ClientHttpRequest::handleAdaptationFailure(int errDetail, bool bypassable)
         doCallouts();
 }
 
+void
+ClientHttpRequest::callException(const std::exception &ex)
+{
+    if (const auto clientConn = getConn() ? getConn()->clientConnection : nullptr) {
+        if (Comm::IsConnOpen(clientConn)) {
+            debugs(85, 3, "closing after exception: " << ex.what());
+            clientConn->close(); // initiate orderly top-to-bottom cleanup
+            return;
+        }
+    }
+    debugs(85, DBG_IMPORTANT, "ClientHttpRequest exception without connection. Ignoring " << ex.what());
+    // XXX: Normally, we mustStop() but we cannot do that here because it is
+    // likely to leave Http::Stream and ConnStateData with a dangling http
+    // pointer. See r13480 or XXX in Http::Stream class description.
+}
 #endif
 
 // XXX: modify and use with ClientRequestContext::clientAccessCheckDone too.