]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/adaptation/icap/ModXact.cc
Allow upgrading from HTTP/1.1 to other protocols (#481)
[thirdparty/squid.git] / src / adaptation / icap / ModXact.cc
index 8cb90777f6abcb07e7ba38de81fb80466313df67..a73f32c547e39599c667992b168fad22f706aa23 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
 #include "comm.h"
 #include "comm/Connection.h"
 #include "err_detail_type.h"
-#include "http/one/TeChunkedParser.h"
+#include "http/ContentLengthInterpreter.h"
 #include "HttpHeaderTools.h"
-#include "HttpMsg.h"
 #include "HttpReply.h"
-#include "HttpRequest.h"
+#include "MasterXaction.h"
+#include "parser/Tokenizer.h"
+#include "sbuf/Stream.h"
 #include "SquidTime.h"
-#include "URL.h"
 
 // flow and terminology:
 //     HTTP| --> receive --> encode --> write --> |network
@@ -44,12 +44,14 @@ CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Icap, ModXactLauncher);
 
 static const size_t TheBackupLimit = BodyPipe::MaxCapacity;
 
+const SBuf Adaptation::Icap::ChunkExtensionValueParser::UseOriginalBodyName("use-original-body");
+
 Adaptation::Icap::ModXact::State::State()
 {
     memset(this, 0, sizeof(*this));
 }
 
-Adaptation::Icap::ModXact::ModXact(HttpMsg *virginHeader,
+Adaptation::Icap::ModXact::ModXact(Http::Message *virginHeader,
                                    HttpRequest *virginCause, AccessLogEntry::Pointer &alp, Adaptation::Icap::ServiceRep::Pointer &aService):
     AsyncJob("Adaptation::Icap::ModXact"),
     Adaptation::Icap::Xaction("Adaptation::Icap::ModXact", aService),
@@ -60,6 +62,7 @@ Adaptation::Icap::ModXact::ModXact(HttpMsg *virginHeader,
     replyHttpHeaderSize(-1),
     replyHttpBodySize(-1),
     adaptHistoryId(-1),
+    trailerParser(nullptr),
     alMaster(alp)
 {
     assert(virginHeader);
@@ -396,7 +399,7 @@ bool Adaptation::Icap::ModXact::virginBodyEndReached(const Adaptation::Icap::Vir
 {
     return
         !act.active() || // did all (assuming it was originally planned)
-        !virgin.body_pipe->expectMoreAfter(act.offset()); // wont have more
+        !virgin.body_pipe->expectMoreAfter(act.offset()); // will not have more
 }
 
 // the size of buffered virgin body data available for the specified activity
@@ -657,6 +660,9 @@ void Adaptation::Icap::ModXact::parseMore()
 
     if (state.parsing == State::psBody)
         parseBody();
+
+    if (state.parsing == State::psIcapTrailer)
+        parseIcapTrailer();
 }
 
 void Adaptation::Icap::ModXact::callException(const std::exception &e)
@@ -698,7 +704,7 @@ void Adaptation::Icap::ModXact::bypassFailure()
 
     // end all activities associated with the ICAP server
 
-    stopParsing();
+    stopParsing(false);
 
     stopWriting(true); // or should we force it?
     if (haveConnection()) {
@@ -734,7 +740,7 @@ void Adaptation::Icap::ModXact::maybeAllocateHttpMsg()
         setOutcome(service().cfg().method == ICAP::methodReqmod ?
                    xoSatisfied : xoModified);
     } else if (gotEncapsulated("req-hdr")) {
-        adapted.setHeader(new HttpRequest);
+        adapted.setHeader(new HttpRequest(virginRequest().masterXaction));
         setOutcome(xoModified);
     } else
         throw TexcHere("Neither res-hdr nor req-hdr in maybeAllocateHttpMsg()");
@@ -771,6 +777,16 @@ void Adaptation::Icap::ModXact::startSending()
 
     if (state.sending == State::sendingVirgin)
         echoMore();
+    else {
+        // If we are not using the virgin HTTP object update the
+        // Http::Message::sources flag.
+        // The state.sending may set to State::sendingVirgin in the case
+        // of 206 responses too, where we do not want to update Http::Message::sources
+        // flag. However even for 206 responses the state.sending is
+        // not set yet to sendingVirgin. This is done in later step
+        // after the parseBody method called.
+        updateSources();
+    }
 }
 
 void Adaptation::Icap::ModXact::parseIcapHead()
@@ -780,7 +796,13 @@ void Adaptation::Icap::ModXact::parseIcapHead()
     if (!parseHead(icapReply.getRaw()))
         return;
 
-    if (httpHeaderHasConnDir(&icapReply->header, "close")) {
+    if (expectIcapTrailers()) {
+        Must(!trailerParser);
+        trailerParser = new TrailerParser;
+    }
+
+    static SBuf close("close", 5);
+    if (httpHeaderHasConnDir(&icapReply->header, close)) {
         debugs(93, 5, HERE << "found connection close");
         reuseConnection = false;
     }
@@ -833,12 +855,12 @@ void Adaptation::Icap::ModXact::parseIcapHead()
     // update the adaptation plan if needed (all status codes!)
     if (service().cfg().routing) {
         String services;
-        if (icapReply->header.getList(HDR_X_NEXT_SERVICES, &services)) {
+        if (icapReply->header.getList(Http::HdrType::X_NEXT_SERVICES, &services)) {
             Adaptation::History::Pointer ah = request->adaptHistory(true);
             if (ah != NULL)
                 ah->updateNextServices(services);
         }
-    } // TODO: else warn (occasionally!) if we got HDR_X_NEXT_SERVICES
+    } // TODO: else warn (occasionally!) if we got Http::HdrType::X_NEXT_SERVICES
 
     // We need to store received ICAP headers for <icapLastHeader logformat option.
     // If we already have stored headers from previous ICAP transaction related to this
@@ -854,23 +876,24 @@ void Adaptation::Icap::ModXact::parseIcapHead()
         stopWriting(true);
 }
 
-bool Adaptation::Icap::ModXact::validate200Ok()
-{
-    if (ICAP::methodRespmod == service().cfg().method) {
-        if (!gotEncapsulated("res-hdr"))
-            return false;
+/// Parses ICAP trailers and stops parsing, if all trailer data
+/// have been received.
+void Adaptation::Icap::ModXact::parseIcapTrailer() {
 
-        return true;
+    if (parsePart(trailerParser, "trailer")) {
+        for (const auto &e: trailerParser->trailer.entries)
+            debugs(93, 5, "ICAP trailer: " << e->name << ": " << e->value);
+        stopParsing();
     }
+}
 
-    if (ICAP::methodReqmod == service().cfg().method) {
-        if (!gotEncapsulated("res-hdr") && !gotEncapsulated("req-hdr"))
-            return false;
-
-        return true;
-    }
+bool Adaptation::Icap::ModXact::validate200Ok()
+{
+    if (service().cfg().method == ICAP::methodRespmod)
+        return gotEncapsulated("res-hdr");
 
-    return false;
+    return service().cfg().method == ICAP::methodReqmod &&
+           expectHttpHeader();
 }
 
 void Adaptation::Icap::ModXact::handle100Continue()
@@ -933,12 +956,12 @@ void Adaptation::Icap::ModXact::prepEchoing()
     setOutcome(xoEcho);
 
     // We want to clone the HTTP message, but we do not want
-    // to copy some non-HTTP state parts that HttpMsg kids carry in them.
+    // to copy some non-HTTP state parts that Http::Message kids carry in them.
     // Thus, we cannot use a smart pointer, copy constructor, or equivalent.
     // Instead, we simply write the HTTP message and "clone" it by parsing.
-    // TODO: use HttpMsg::clone()!
+    // TODO: use Http::Message::clone()!
 
-    HttpMsg *oldHead = virgin.header;
+    Http::Message *oldHead = virgin.header;
     debugs(93, 7, HERE << "cloning virgin message " << oldHead);
 
     MemBuf httpBuf;
@@ -950,13 +973,13 @@ void Adaptation::Icap::ModXact::prepEchoing()
     // allocate the adapted message and copy metainfo
     Must(!adapted.header);
     {
-        HttpMsg::Pointer newHead;
-        if (dynamic_cast<const HttpRequest*>(oldHead)) {
-            newHead = new HttpRequest;
+        Http::MessagePointer newHead;
+        if (const HttpRequest *r = dynamic_cast<const HttpRequest*>(oldHead)) {
+            newHead = new HttpRequest(r->masterXaction);
         } else if (dynamic_cast<const HttpReply*>(oldHead)) {
             newHead = new HttpReply;
         }
-        Must(newHead != NULL);
+        Must(newHead);
 
         newHead->inheritProperties(oldHead);
 
@@ -966,7 +989,7 @@ void Adaptation::Icap::ModXact::prepEchoing()
     // parse the buffer back
     Http::StatusCode error = Http::scNone;
 
-    httpBuf.terminate(); // HttpMsg::parse requires nil-terminated buffer
+    httpBuf.terminate(); // Http::Message::parse requires nil-terminated buffer
     Must(adapted.header->parse(httpBuf.content(), httpBuf.contentSize(), true, &error));
     Must(adapted.header->hdr_sz == httpBuf.contentSize()); // no leftovers
 
@@ -1030,7 +1053,7 @@ void Adaptation::Icap::ModXact::prepPartialBodyEchoing(uint64_t pos)
 
 void Adaptation::Icap::ModXact::handleUnknownScode()
 {
-    stopParsing();
+    stopParsing(false);
     stopBackup();
     // TODO: mark connection as "bad"
 
@@ -1040,7 +1063,7 @@ void Adaptation::Icap::ModXact::handleUnknownScode()
 
 void Adaptation::Icap::ModXact::parseHttpHead()
 {
-    if (gotEncapsulated("res-hdr") || gotEncapsulated("req-hdr")) {
+    if (expectHttpHeader()) {
         replyHttpHeaderSize = 0;
         maybeAllocateHttpMsg();
 
@@ -1067,42 +1090,73 @@ void Adaptation::Icap::ModXact::parseHttpHead()
     decideOnParsingBody();
 }
 
-// parses both HTTP and ICAP headers
-bool Adaptation::Icap::ModXact::parseHead(HttpMsg *head)
+template<class Part>
+bool Adaptation::Icap::ModXact::parsePart(Part *part, const char *description)
 {
-    Must(head);
-    debugs(93, 5, "have " << readBuf.length() << " head bytes to parse; state: " << state.parsing);
-
+    Must(part);
+    debugs(93, 5, "have " << readBuf.length() << ' ' << description << " bytes to parse; state: " << state.parsing);
     Http::StatusCode error = Http::scNone;
     // XXX: performance regression. c_str() data copies
-    // XXX: HttpMsg::parse requires a terminated string buffer
+    // XXX: Http::Message::parse requires a terminated string buffer
     const char *tmpBuf = readBuf.c_str();
-    const bool parsed = head->parse(tmpBuf, readBuf.length(), commEof, &error);
-    Must(parsed || !error); // success or need more data
+    const bool parsed = part->parse(tmpBuf, readBuf.length(), commEof, &error);
+    debugs(93, (!parsed && error) ? 2 : 5, description << " parsing result: " << parsed << " detail: " << error);
+    Must(parsed || !error);
+    if (parsed)
+        readBuf.consume(part->hdr_sz);
+    return parsed;
+}
 
-    if (!parsed) { // need more data
-        debugs(93, 5, HERE << "parse failed, need more data, return false");
+// parses both HTTP and ICAP headers
+bool
+Adaptation::Icap::ModXact::parseHead(Http::Message *head)
+{
+    if (!parsePart(head, "head")) {
         head->reset();
         return false;
     }
-
-    debugs(93, 5, HERE << "parse success, consume " << head->hdr_sz << " bytes, return true");
-    readBuf.consume(head->hdr_sz);
     return true;
 }
 
+bool Adaptation::Icap::ModXact::expectHttpHeader() const
+{
+    return gotEncapsulated("res-hdr") || gotEncapsulated("req-hdr");
+}
+
+bool Adaptation::Icap::ModXact::expectHttpBody() const
+{
+    return gotEncapsulated("res-body") || gotEncapsulated("req-body");
+}
+
+bool Adaptation::Icap::ModXact::expectIcapTrailers() const
+{
+    String trailers;
+    const bool promisesToSendTrailer = icapReply->header.getByIdIfPresent(Http::HdrType::TRAILER, &trailers);
+    const bool supportsTrailers = icapReply->header.hasListMember(Http::HdrType::ALLOW, "trailers", ',');
+    // ICAP Trailer specs require us to reject transactions having either Trailer
+    // header or Allow:trailers
+    Must((promisesToSendTrailer == supportsTrailers) || (!promisesToSendTrailer && supportsTrailers));
+    if (promisesToSendTrailer && !trailers.size())
+        debugs(93, DBG_IMPORTANT, "ERROR: ICAP Trailer response header field must not be empty (salvaged)");
+    return promisesToSendTrailer;
+}
+
 void Adaptation::Icap::ModXact::decideOnParsingBody()
 {
-    if (gotEncapsulated("res-body") || gotEncapsulated("req-body")) {
+    if (expectHttpBody()) {
         debugs(93, 5, HERE << "expecting a body");
         state.parsing = State::psBody;
         replyHttpBodySize = 0;
         bodyParser = new Http1::TeChunkedParser;
+        bodyParser->parseExtensionValuesWith(&extensionParser);
         makeAdaptedBodyPipe("adapted response from the ICAP server");
         Must(state.sending == State::sendingAdapted);
     } else {
         debugs(93, 5, HERE << "not expecting a body");
-        stopParsing();
+        if (trailerParser)
+            state.parsing = State::psIcapTrailer;
+        else
+            stopParsing();
         stopSending(true);
     }
 }
@@ -1132,15 +1186,14 @@ void Adaptation::Icap::ModXact::parseBody()
     }
 
     if (parsed) {
-        if (state.readyForUob && bodyParser->useOriginBody >= 0) {
-            prepPartialBodyEchoing(
-                static_cast<uint64_t>(bodyParser->useOriginBody));
+        if (state.readyForUob && extensionParser.sawUseOriginalBody())
+            prepPartialBodyEchoing(extensionParser.useOriginalBody());
+        else
+            stopSending(true); // the parser succeeds only if all parsed data fits
+        if (trailerParser)
+            state.parsing = State::psIcapTrailer;
+        else
             stopParsing();
-            return;
-        }
-
-        stopParsing();
-        stopSending(true); // the parser succeeds only if all parsed data fits
         return;
     }
 
@@ -1160,16 +1213,21 @@ void Adaptation::Icap::ModXact::parseBody()
     }
 }
 
-void Adaptation::Icap::ModXact::stopParsing()
+void Adaptation::Icap::ModXact::stopParsing(const bool checkUnparsedData)
 {
     if (state.parsing == State::psDone)
         return;
 
-    debugs(93, 7, HERE << "will no longer parse" << status());
+    if (checkUnparsedData)
+        Must(readBuf.isEmpty());
+
+    debugs(93, 7, "will no longer parse" << status());
 
     delete bodyParser;
+    bodyParser = nullptr;
 
-    bodyParser = NULL;
+    delete trailerParser;
+    trailerParser = nullptr;
 
     state.parsing = State::psDone;
 }
@@ -1230,6 +1288,7 @@ void Adaptation::Icap::ModXact::noteBodyConsumerAborted(BodyPipe::Pointer)
 Adaptation::Icap::ModXact::~ModXact()
 {
     delete bodyParser;
+    delete trailerParser;
 }
 
 // internal cleanup
@@ -1255,34 +1314,32 @@ void prepareLogWithRequestDetails(HttpRequest *, AccessLogEntry::Pointer &);
 
 void Adaptation::Icap::ModXact::finalizeLogInfo()
 {
-    HttpRequest * request_ = NULL;
-    HttpRequest * adapted_request_ = NULL;
-    HttpReply * reply_ = NULL;
-    request_ = (virgin.cause? virgin.cause: dynamic_cast<HttpRequest*>(virgin.header));
+    HttpRequest *adapted_request_ = nullptr;
+    HttpReply *adapted_reply_ = nullptr;
+    HttpRequest *virgin_request_ = const_cast<HttpRequest*>(&virginRequest());
     if (!(adapted_request_ = dynamic_cast<HttpRequest*>(adapted.header))) {
-        adapted_request_ = request_;
-        reply_ = dynamic_cast<HttpReply*>(adapted.header);
+        // if the request was not adapted, use virgin request to simplify
+        // the code further below
+        adapted_request_ = virgin_request_;
+        adapted_reply_ = dynamic_cast<HttpReply*>(adapted.header);
     }
 
-    Adaptation::Icap::History::Pointer h = (request_ ? request_->icapHistory() : NULL);
+    Adaptation::Icap::History::Pointer h = virgin_request_->icapHistory();
     Must(h != NULL); // ICAPXaction::maybeLog calls only if there is a log
     al.icp.opcode = ICP_INVALID;
     al.url = h->log_uri.termedBuf();
     const Adaptation::Icap::ServiceRep  &s = service();
     al.icap.reqMethod = s.cfg().method;
 
-    al.cache.caddr = request_->client_addr;
+    al.cache.caddr = virgin_request_->client_addr;
 
-    al.request = request_;
+    al.request = virgin_request_;
     HTTPMSGLOCK(al.request);
     al.adapted_request = adapted_request_;
     HTTPMSGLOCK(al.adapted_request);
 
-    if (reply_) {
-        al.reply = reply_;
-        HTTPMSGLOCK(al.reply);
-    } else
-        al.reply = NULL;
+    // XXX: This reply (and other ALE members!) may have been needed earlier.
+    al.reply = adapted_reply_;
 
     if (h->rfc931.size())
         al.cache.rfc931 = h->rfc931.termedBuf();
@@ -1292,32 +1349,31 @@ void Adaptation::Icap::ModXact::finalizeLogInfo()
         al.cache.ssluser = h->ssluser.termedBuf();
 #endif
     al.cache.code = h->logType;
-    // XXX: should use icap-specific counters instead ?
-    al.http.clientRequestSz.payloadData = h->req_sz;
+
+    const Http::Message *virgin_msg = dynamic_cast<HttpReply*>(virgin.header);
+    if (!virgin_msg)
+        virgin_msg = virgin_request_;
+    assert(virgin_msg != virgin.cause);
+    al.http.clientRequestSz.header = virgin_msg->hdr_sz;
+    if (virgin_msg->body_pipe != NULL)
+        al.http.clientRequestSz.payloadData = virgin_msg->body_pipe->producedSize();
 
     // leave al.icap.bodyBytesRead negative if no body
     if (replyHttpHeaderSize >= 0 || replyHttpBodySize >= 0) {
         const int64_t zero = 0; // to make max() argument types the same
-        al.icap.bodyBytesRead =
-            max(zero, replyHttpHeaderSize) + max(zero, replyHttpBodySize);
+        const uint64_t headerSize = max(zero, replyHttpHeaderSize);
+        const uint64_t bodySize =  max(zero, replyHttpBodySize);
+        al.icap.bodyBytesRead = headerSize + bodySize;
+        al.http.clientReplySz.header = headerSize;
+        al.http.clientReplySz.payloadData = bodySize;
     }
 
-    if (reply_) {
-        al.http.code = reply_->sline.status();
-        al.http.content_type = reply_->content_type.termedBuf();
-        if (replyHttpBodySize >= 0) {
-            // XXX: should use icap-specific counters instead ?
-            al.http.clientReplySz.payloadData = replyHttpBodySize;
-            al.http.clientReplySz.header =  reply_->hdr_sz;
+    if (adapted_reply_) {
+        al.http.code = adapted_reply_->sline.status();
+        al.http.content_type = adapted_reply_->content_type.termedBuf();
+        if (replyHttpBodySize >= 0)
             al.cache.highOffset = replyHttpBodySize;
-        }
         //don't set al.cache.objectSize because it hasn't exist yet
-
-        MemBuf mb;
-        mb.init();
-        reply_->header.packInto(&mb);
-        al.headers.reply = xstrdup(mb.buf);
-        mb.clean();
     }
     prepareLogWithRequestDetails(adapted_request_, alep);
     Xaction::finalizeLogInfo();
@@ -1341,18 +1397,18 @@ void Adaptation::Icap::ModXact::makeRequestHeaders(MemBuf &buf)
 
     // we must forward "Proxy-Authenticate" and "Proxy-Authorization"
     // as ICAP headers.
-    if (virgin.header->header.has(HDR_PROXY_AUTHENTICATE)) {
-        String vh=virgin.header->header.getByName("Proxy-Authenticate");
+    if (virgin.header->header.has(Http::HdrType::PROXY_AUTHENTICATE)) {
+        String vh=virgin.header->header.getById(Http::HdrType::PROXY_AUTHENTICATE);
         buf.appendf("Proxy-Authenticate: " SQUIDSTRINGPH "\r\n",SQUIDSTRINGPRINT(vh));
     }
 
-    if (virgin.header->header.has(HDR_PROXY_AUTHORIZATION)) {
-        String vh=virgin.header->header.getByName("Proxy-Authorization");
+    if (virgin.header->header.has(Http::HdrType::PROXY_AUTHORIZATION)) {
+        String vh=virgin.header->header.getById(Http::HdrType::PROXY_AUTHORIZATION);
         buf.appendf("Proxy-Authorization: " SQUIDSTRINGPH "\r\n", SQUIDSTRINGPRINT(vh));
     } else if (request->extacl_user.size() > 0 && request->extacl_passwd.size() > 0) {
         struct base64_encode_ctx ctx;
         base64_encode_init(&ctx);
-        uint8_t base64buf[base64_encode_len(MAX_LOGIN_SZ)];
+        char base64buf[base64_encode_len(MAX_LOGIN_SZ)];
         size_t resultLen = base64_encode_update(&ctx, base64buf, request->extacl_user.size(), reinterpret_cast<const uint8_t*>(request->extacl_user.rawBuf()));
         resultLen += base64_encode_update(&ctx, base64buf+resultLen, 1, reinterpret_cast<const uint8_t*>(":"));
         resultLen += base64_encode_update(&ctx, base64buf+resultLen, request->extacl_passwd.size(), reinterpret_cast<const uint8_t*>(request->extacl_passwd.rawBuf()));
@@ -1390,7 +1446,7 @@ void Adaptation::Icap::ModXact::makeRequestHeaders(MemBuf &buf)
     }
 
     if (ICAP::methodRespmod == m)
-        if (const HttpMsg *prime = virgin.header)
+        if (const Http::Message *prime = virgin.header)
             encapsulateHead(buf, "res-hdr", httpBuf, prime);
 
     if (!virginBody.expected())
@@ -1426,22 +1482,25 @@ void Adaptation::Icap::ModXact::makeRequestHeaders(MemBuf &buf)
         makeUsernameHeader(request, buf);
 
     // Adaptation::Config::metaHeaders
-    typedef Notes::iterator ACAMLI;
-    for (ACAMLI i = Adaptation::Config::metaHeaders.begin(); i != Adaptation::Config::metaHeaders.end(); ++i) {
+    for (auto h: Adaptation::Config::metaHeaders) {
         HttpRequest *r = virgin.cause ?
                          virgin.cause : dynamic_cast<HttpRequest*>(virgin.header);
         Must(r);
 
         HttpReply *reply = dynamic_cast<HttpReply*>(virgin.header);
 
-        if (const char *value = (*i)->match(r, reply, alMaster)) {
-            buf.appendf("%s: %s\r\n", (*i)->key.termedBuf(), value);
+        SBuf matched;
+        if (h->match(r, reply, alMaster, matched)) {
+            buf.append(h->key().rawContent(), h->key().length());
+            buf.append(": ", 2);
+            buf.append(matched.rawContent(), matched.length());
+            buf.append("\r\n", 2);
             Adaptation::History::Pointer ah = request->adaptHistory(false);
             if (ah != NULL) {
                 if (ah->metaHeaders == NULL)
                     ah->metaHeaders = new NotePairs;
-                if (!ah->metaHeaders->hasPair((*i)->key.termedBuf(), value))
-                    ah->metaHeaders->add((*i)->key.termedBuf(), value);
+                if (!ah->metaHeaders->hasPair(h->key(), matched))
+                    ah->metaHeaders->add(h->key(), matched);
             }
         }
     }
@@ -1466,34 +1525,30 @@ void Adaptation::Icap::ModXact::makeAllowHeader(MemBuf &buf)
     const bool allow204out = state.allowedPostview204 = shouldAllow204();
     const bool allow206in = state.allowedPreview206 = shouldAllow206in();
     const bool allow206out = state.allowedPostview206 = shouldAllow206out();
+    const bool allowTrailers = true; // TODO: make configurable
 
-    debugs(93,9, HERE << "Allows: " << allow204in << allow204out <<
-           allow206in << allow206out);
+    debugs(93, 9, "Allows: " << allow204in << allow204out <<
+           allow206in << allow206out << allowTrailers);
 
     const bool allow204 = allow204in || allow204out;
     const bool allow206 = allow206in || allow206out;
 
-    if (!allow204 && !allow206)
-        return; // nothing to do
-
-    if (virginBody.expected()) // if there is a virgin body, plan to send it
-        virginBodySending.plan();
+    if ((allow204 || allow206) && virginBody.expected())
+        virginBodySending.plan(); // if there is a virgin body, plan to send it
 
     // writing Preview:...   means we will honor 204 inside preview
     // writing Allow/204     means we will honor 204 outside preview
     // writing Allow:206     means we will honor 206 inside preview
     // writing Allow:204,206 means we will honor 206 outside preview
-    const char *allowHeader = NULL;
-    if (allow204out && allow206)
-        allowHeader = "Allow: 204, 206\r\n";
-    else if (allow204out)
-        allowHeader = "Allow: 204\r\n";
-    else if (allow206)
-        allowHeader = "Allow: 206\r\n";
-
-    if (allowHeader) { // may be nil if only allow204in is true
-        buf.append(allowHeader, strlen(allowHeader));
-        debugs(93,5, HERE << "Will write " << allowHeader);
+    if (allow204 || allow206 || allowTrailers) {
+        buf.appendf("Allow: ");
+        if (allow204out)
+            buf.appendf("204, ");
+        if (allow206)
+            buf.appendf("206, ");
+        if (allowTrailers)
+            buf.appendf("trailers");
+        buf.appendf("\r\n");
     }
 }
 
@@ -1512,7 +1567,7 @@ void Adaptation::Icap::ModXact::makeUsernameHeader(const HttpRequest *request, M
 
     if (value) {
         if (TheConfig.client_username_encode) {
-            uint8_t base64buf[base64_encode_len(MAX_LOGIN_SZ)];
+            char base64buf[base64_encode_len(MAX_LOGIN_SZ)];
             size_t resultLen = base64_encode_update(&ctx, base64buf, strlen(value), reinterpret_cast<const uint8_t*>(value));
             resultLen += base64_encode_final(&ctx, base64buf+resultLen);
             buf.appendf("%s: %.*s\r\n", TheConfig.client_username_header, (int)resultLen, base64buf);
@@ -1522,17 +1577,18 @@ void Adaptation::Icap::ModXact::makeUsernameHeader(const HttpRequest *request, M
 #endif
 }
 
-void Adaptation::Icap::ModXact::encapsulateHead(MemBuf &icapBuf, const char *section, MemBuf &httpBuf, const HttpMsg *head)
+void
+Adaptation::Icap::ModXact::encapsulateHead(MemBuf &icapBuf, const char *section, MemBuf &httpBuf, const Http::Message *head)
 {
     // update ICAP header
     icapBuf.appendf("%s=%d, ", section, (int) httpBuf.contentSize());
 
     // begin cloning
-    HttpMsg::Pointer headClone;
+    Http::MessagePointer headClone;
 
     if (const HttpRequest* old_request = dynamic_cast<const HttpRequest*>(head)) {
-        HttpRequest::Pointer new_request(new HttpRequest);
-        // copy the requst-line details
+        HttpRequest::Pointer new_request(new HttpRequest(old_request->masterXaction));
+        // copy the request-line details
         new_request->method = old_request->method;
         new_request->url = old_request->url;
         new_request->http_ver = old_request->http_ver;
@@ -1542,27 +1598,34 @@ void Adaptation::Icap::ModXact::encapsulateHead(MemBuf &icapBuf, const char *sec
         new_reply->sline = old_reply->sline;
         headClone = new_reply.getRaw();
     }
-    Must(headClone != NULL);
+    Must(headClone);
     headClone->inheritProperties(head);
 
     HttpHeaderPos pos = HttpHeaderInitPos;
-    HttpHeaderEntry* p_head_entry = NULL;
-    while (NULL != (p_head_entry = head->header.getEntry(&pos)) )
+    while (HttpHeaderEntry* p_head_entry = head->header.getEntry(&pos))
         headClone->header.addEntry(p_head_entry->clone());
 
     // end cloning
 
     // remove all hop-by-hop headers from the clone
-    headClone->header.delById(HDR_PROXY_AUTHENTICATE);
+    headClone->header.delById(Http::HdrType::PROXY_AUTHENTICATE);
     headClone->header.removeHopByHopEntries();
 
+    // TODO: modify HttpHeader::removeHopByHopEntries to accept a list of
+    // excluded hop-by-hop headers
+    if (head->header.has(Http::HdrType::UPGRADE)) {
+        const auto upgrade = head->header.getList(Http::HdrType::UPGRADE);
+        headClone->header.putStr(Http::HdrType::UPGRADE, upgrade.termedBuf());
+    }
+
     // pack polished HTTP header
     packHead(httpBuf, headClone.getRaw());
 
     // headClone unlocks and, hence, deletes the message we packed
 }
 
-void Adaptation::Icap::ModXact::packHead(MemBuf &httpBuf, const HttpMsg *head)
+void
+Adaptation::Icap::ModXact::packHead(MemBuf &httpBuf, const Http::Message *head)
 {
     head->packInto(&httpBuf, true);
 }
@@ -1746,8 +1809,8 @@ void Adaptation::Icap::ModXact::fillDoneStatus(MemBuf &buf) const
 
 bool Adaptation::Icap::ModXact::gotEncapsulated(const char *section) const
 {
-    return icapReply->header.getByNameListMember("Encapsulated",
-            section, ',').size() > 0;
+    return !icapReply->header.getByNameListMember("Encapsulated",
+            section, ',').isEmpty();
 }
 
 // calculate whether there is a virgin HTTP body and
@@ -1757,7 +1820,7 @@ void Adaptation::Icap::ModXact::estimateVirginBody()
 {
     // note: lack of size info may disable previews and 204s
 
-    HttpMsg *msg = virgin.header;
+    Http::Message *msg = virgin.header;
     Must(msg);
 
     HttpRequestMethod method;
@@ -1949,9 +2012,15 @@ void Adaptation::Icap::ModXact::clearError()
         request->clearError();
 }
 
+void Adaptation::Icap::ModXact::updateSources()
+{
+    Must(adapted.header);
+    adapted.header->sources |= (service().cfg().connectionEncryption ? Http::Message::srcIcaps : Http::Message::srcIcap);
+}
+
 /* Adaptation::Icap::ModXactLauncher */
 
-Adaptation::Icap::ModXactLauncher::ModXactLauncher(HttpMsg *virginHeader, HttpRequest *virginCause, AccessLogEntry::Pointer &alp, Adaptation::ServicePointer aService):
+Adaptation::Icap::ModXactLauncher::ModXactLauncher(Http::Message *virginHeader, HttpRequest *virginCause, AccessLogEntry::Pointer &alp, Adaptation::ServicePointer aService):
     AsyncJob("Adaptation::Icap::ModXactLauncher"),
     Adaptation::Icap::Launcher("Adaptation::Icap::ModXactLauncher", aService),
     al(alp)
@@ -1993,3 +2062,25 @@ void Adaptation::Icap::ModXactLauncher::updateHistory(bool doStart)
     }
 }
 
+bool Adaptation::Icap::TrailerParser::parse(const char *buf, int len, int atEnd, Http::StatusCode *error) {
+    Http::ContentLengthInterpreter clen;
+    // RFC 7230 section 4.1.2: MUST NOT generate a trailer that contains
+    // a field necessary for message framing (e.g., Transfer-Encoding and Content-Length)
+    clen.applyTrailerRules();
+    const int parsed = trailer.parse(buf, len, atEnd, hdr_sz, clen);
+    if (parsed < 0)
+        *error = Http::scInvalidHeader; // TODO: should we add a new Http::scInvalidTrailer?
+    return parsed > 0;
+}
+
+void
+Adaptation::Icap::ChunkExtensionValueParser::parse(Tokenizer &tok, const SBuf &extName)
+{
+    if (extName == UseOriginalBodyName) {
+        useOriginalBody_ = tok.udec64("use-original-body");
+        assert(useOriginalBody_ >= 0);
+    } else {
+        Ignore(tok, extName);
+    }
+}
+