]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/adaptation/icap/ModXact.cc
Merge form trunk
[thirdparty/squid.git] / src / adaptation / icap / ModXact.cc
index 1441dd26bba02406067cf7901d8652a944100454..6aee947d77900044a72b9e69d155a0dc817f4856 100644 (file)
@@ -3,20 +3,26 @@
  */
 
 #include "squid.h"
+#include "AccessLogEntry.h"
+#include "adaptation/History.h"
+#include "adaptation/icap/Client.h"
+#include "adaptation/icap/Config.h"
+#include "adaptation/icap/History.h"
+#include "adaptation/icap/Launcher.h"
+#include "adaptation/icap/ModXact.h"
+#include "adaptation/icap/ServiceRep.h"
+#include "adaptation/Initiator.h"
+#include "auth/UserRequest.h"
+#include "base/TextException.h"
+#include "base64.h"
+#include "ChunkedCodingParser.h"
 #include "comm.h"
+#include "comm/Connection.h"
 #include "HttpMsg.h"
 #include "HttpRequest.h"
 #include "HttpReply.h"
-#include "adaptation/Initiator.h"
-#include "adaptation/icap/ServiceRep.h"
-#include "adaptation/icap/Launcher.h"
-#include "adaptation/icap/ModXact.h"
-#include "adaptation/icap/Client.h"
-#include "ChunkedCodingParser.h"
-#include "TextException.h"
-#include "auth/UserRequest.h"
-#include "adaptation/icap/Config.h"
 #include "SquidTime.h"
+#include "err_detail_type.h"
 
 // flow and terminology:
 //     HTTP| --> receive --> encode --> write --> |network
@@ -29,22 +35,22 @@ CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Icap, ModXactLauncher);
 
 static const size_t TheBackupLimit = BodyPipe::MaxCapacity;
 
-extern Adaptation::Icap::Config Adaptation::Icap::TheConfig;
-
-
 Adaptation::Icap::ModXact::State::State()
 {
     memset(this, 0, sizeof(*this));
 }
 
-Adaptation::Icap::ModXact::ModXact(Adaptation::Initiator *anInitiator, HttpMsg *virginHeader,
+Adaptation::Icap::ModXact::ModXact(HttpMsg *virginHeader,
                                    HttpRequest *virginCause, Adaptation::Icap::ServiceRep::Pointer &aService):
         AsyncJob("Adaptation::Icap::ModXact"),
-        Adaptation::Icap::Xaction("Adaptation::Icap::ModXact", anInitiator, aService),
-        icapReply(NULL),
+        Adaptation::Icap::Xaction("Adaptation::Icap::ModXact", aService),
         virginConsumed(0),
         bodyParser(NULL),
-        canStartBypass(false) // too early
+        canStartBypass(false), // too early
+        protectGroupBypass(true),
+        replyHttpHeaderSize(-1),
+        replyHttpBodySize(-1),
+        adaptHistoryId(-1)
 {
     assert(virginHeader);
 
@@ -58,7 +64,7 @@ Adaptation::Icap::ModXact::ModXact(Adaptation::Initiator *anInitiator, HttpMsg *
     // encoding
     // nothing to do because we are using temporary buffers
 
-    // parsing
+    // parsing; TODO: do not set until we parse, see ICAPOptXact
     icapReply = new HttpReply;
     icapReply->protoPrefix = "ICAP/"; // TODO: make an IcapReply class?
 
@@ -70,6 +76,11 @@ void Adaptation::Icap::ModXact::start()
 {
     Adaptation::Icap::Xaction::start();
 
+    // reserve an adaptation history slot (attempts are known at this time)
+    Adaptation::History::Pointer ah = virginRequest().adaptLogHistory();
+    if (ah != NULL)
+        adaptHistoryId = ah->recordXactStart(service().cfg().key, icap_tr_start, attempts > 1);
+
     estimateVirginBody(); // before virgin disappears!
 
     canStartBypass = service().cfg().bypass;
@@ -86,10 +97,11 @@ void Adaptation::Icap::ModXact::waitForService()
 {
     Must(!state.serviceWaiting);
     debugs(93, 7, HERE << "will wait for the ICAP service" << status());
-    state.serviceWaiting = true;
-    AsyncCall::Pointer call = asyncCall(93,5, "Adaptation::Icap::ModXact::noteServiceReady",
-                                        MemFun(this, &Adaptation::Icap::ModXact::noteServiceReady));
+    typedef NullaryMemFunT<ModXact> Dialer;
+    AsyncCall::Pointer call = JobCallback(93,5,
+                                          Dialer, this, Adaptation::Icap::ModXact::noteServiceReady);
     service().callWhenReady(call);
+    state.serviceWaiting = true; // after callWhenReady() which may throw
 }
 
 void Adaptation::Icap::ModXact::noteServiceReady()
@@ -101,6 +113,7 @@ void Adaptation::Icap::ModXact::noteServiceReady()
         startWriting();
     } else {
         disableRetries();
+        disableRepeats("ICAP service is unusable");
         throw TexcHere("ICAP service is unusable");
     }
 }
@@ -131,6 +144,7 @@ void Adaptation::Icap::ModXact::handleCommConnected()
 
     // write headers
     state.writing = State::writingHeaders;
+    icap_tio_start = current_time;
     scheduleWrite(requestBuf);
 }
 
@@ -149,15 +163,17 @@ void Adaptation::Icap::ModXact::handleCommWroteHeaders()
     Must(state.writing == State::writingHeaders);
 
     // determine next step
-    if (preview.enabled())
-        state.writing = preview.done() ? State::writingPaused : State::writingPreview;
-    else
-        if (virginBody.expected())
-            state.writing = State::writingPrime;
-        else {
-            stopWriting(true);
-            return;
-        }
+    if (preview.enabled()) {
+        if (preview.done())
+            decideWritingAfterPreview("zero-size");
+        else
+            state.writing = State::writingPreview;
+    } else if (virginBody.expected()) {
+        state.writing = State::writingPrime;
+    } else {
+        stopWriting(true);
+        return;
+    }
 
     writeMore();
 }
@@ -213,14 +229,22 @@ void Adaptation::Icap::ModXact::writePreviewBody()
 
     // change state once preview is written
 
-    if (preview.done()) {
-        debugs(93, 7, HERE << "wrote entire Preview body" << status());
+    if (preview.done())
+        decideWritingAfterPreview("body");
+}
 
-        if (preview.ieof())
-            stopWriting(true);
-        else
-            state.writing = State::writingPaused;
-    }
+/// determine state.writing after we wrote the entire preview
+void Adaptation::Icap::ModXact::decideWritingAfterPreview(const char *kind)
+{
+    if (preview.ieof()) // nothing more to write
+        stopWriting(true);
+    else if (state.parsing == State::psIcapHeader) // did not get a reply yet
+        state.writing = State::writingPaused; // wait for the ICAP server reply
+    else
+        stopWriting(true); // ICAP server reply implies no post-preview writing
+
+    debugs(93, 6, HERE << "decided on writing after " << kind << " preview" <<
+           status());
 }
 
 void Adaptation::Icap::ModXact::writePrimeBody()
@@ -304,6 +328,14 @@ void Adaptation::Icap::ModXact::closeChunk(MemBuf &buf)
     buf.append(ICAP::crlf, 2); // chunk-terminating CRLF
 }
 
+const HttpRequest &Adaptation::Icap::ModXact::virginRequest() const
+{
+    const HttpRequest *request = virgin.cause ?
+                                 virgin.cause : dynamic_cast<const HttpRequest*>(virgin.header);
+    Must(request);
+    return *request;
+}
+
 // did the activity reached the end of the virgin body?
 bool Adaptation::Icap::ModXact::virginBodyEndReached(const Adaptation::Icap::VirginBodyAct &act) const
 {
@@ -318,25 +350,26 @@ size_t Adaptation::Icap::ModXact::virginContentSize(const Adaptation::Icap::Virg
 {
     Must(act.active());
     // asbolute start of unprocessed data
-    const uint64_t start = act.offset();
+    const uint64_t dataStart = act.offset();
     // absolute end of buffered data
-    const uint64_t end = virginConsumed + virgin.body_pipe->buf().contentSize();
-    Must(virginConsumed <= start && start <= end);
-    return static_cast<size_t>(end - start);
+    const uint64_t dataEnd = virginConsumed + virgin.body_pipe->buf().contentSize();
+    Must(virginConsumed <= dataStart && dataStart <= dataEnd);
+    return static_cast<size_t>(dataEnd - dataStart);
 }
 
 // pointer to buffered virgin body data available for the specified activity
 const char *Adaptation::Icap::ModXact::virginContentData(const Adaptation::Icap::VirginBodyAct &act) const
 {
     Must(act.active());
-    const uint64_t start = act.offset();
-    Must(virginConsumed <= start);
-    return virgin.body_pipe->buf().content() + static_cast<size_t>(start-virginConsumed);
+    const uint64_t dataStart = act.offset();
+    Must(virginConsumed <= dataStart);
+    return virgin.body_pipe->buf().content() + static_cast<size_t>(dataStart-virginConsumed);
 }
 
 void Adaptation::Icap::ModXact::virginConsume()
 {
-    debugs(93, 9, HERE << "consumption guards: " << !virgin.body_pipe << isRetriable);
+    debugs(93, 9, HERE << "consumption guards: " << !virgin.body_pipe << isRetriable <<
+           isRepeatable << canStartBypass << protectGroupBypass);
 
     if (!virgin.body_pipe)
         return; // nothing to consume
@@ -345,11 +378,12 @@ void Adaptation::Icap::ModXact::virginConsume()
         return; // do not consume if we may have to retry later
 
     BodyPipe &bp = *virgin.body_pipe;
+    const bool wantToPostpone = isRepeatable || canStartBypass || protectGroupBypass;
 
     // Why > 2? HttpState does not use the last bytes in the buffer
     // because delayAwareRead() is arguably broken. See
     // HttpStateData::maybeReadVirginBody for more details.
-    if (canStartBypass && bp.buf().spaceSize() > 2) {
+    if (wantToPostpone && bp.buf().spaceSize() > 2) {
         // Postponing may increase memory footprint and slow the HTTP side
         // down. Not postponing may increase the number of ICAP errors
         // if the ICAP service fails. We may also use "potential" space to
@@ -381,7 +415,8 @@ void Adaptation::Icap::ModXact::virginConsume()
         bp.consume(size);
         virginConsumed += size;
         Must(!isRetriable); // or we should not be consuming
-        disableBypass("consumed content");
+        disableRepeats("consumed content");
+        disableBypass("consumed content", true);
     }
 }
 
@@ -444,7 +479,7 @@ bool Adaptation::Icap::ModXact::doneAll() const
 
 void Adaptation::Icap::ModXact::startReading()
 {
-    Must(connection >= 0);
+    Must(haveConnection());
     Must(!reader);
     Must(!adapted.header);
     Must(!adapted.body_pipe);
@@ -477,6 +512,7 @@ void Adaptation::Icap::ModXact::readMore()
 void Adaptation::Icap::ModXact::handleCommRead(size_t)
 {
     Must(!state.doneParsing());
+    icap_tio_finish = current_time;
     parseMore();
     readMore();
 }
@@ -498,8 +534,9 @@ void Adaptation::Icap::ModXact::echoMore()
         debugs(93,5, HERE << "echoed " << size << " out of " << sizeMax <<
                " bytes");
         virginBodySending.progress(size);
+        disableRepeats("echoed content");
+        disableBypass("echoed content", true);
         virginConsume();
-        disableBypass("echoed content");
     }
 
     if (virginBodyEndReached(virginBodySending)) {
@@ -521,8 +558,10 @@ bool Adaptation::Icap::ModXact::doneSending() const
 // stop (or do not start) sending adapted message body
 void Adaptation::Icap::ModXact::stopSending(bool nicely)
 {
+    debugs(93, 7, HERE << "Enter stop sending ");
     if (doneSending())
         return;
+    debugs(93, 7, HERE << "Proceed with stop sending ");
 
     if (state.sending != State::sendingUndecided) {
         debugs(93, 7, HERE << "will no longer send" << status());
@@ -569,6 +608,12 @@ void Adaptation::Icap::ModXact::parseMore()
 void Adaptation::Icap::ModXact::callException(const std::exception &e)
 {
     if (!canStartBypass || isRetriable) {
+        if (!isRetriable) {
+            if (const TextException *te = dynamic_cast<const TextException *>(&e))
+                detailError(ERR_DETAIL_EXCEPTION_START + te->id());
+            else
+                detailError(ERR_DETAIL_EXCEPTION_OTHER);
+        }
         Adaptation::Icap::Xaction::callException(e);
         return;
     }
@@ -577,16 +622,21 @@ void Adaptation::Icap::ModXact::callException(const std::exception &e)
         debugs(93, 3, HERE << "bypassing " << inCall << " exception: " <<
                e.what() << ' ' << status());
         bypassFailure();
+    } catch (const TextException &bypassTe) {
+        detailError(ERR_DETAIL_EXCEPTION_START + bypassTe.id());
+        Adaptation::Icap::Xaction::callException(bypassTe);
     } catch (const std::exception &bypassE) {
+        detailError(ERR_DETAIL_EXCEPTION_OTHER);
         Adaptation::Icap::Xaction::callException(bypassE);
     }
 }
 
 void Adaptation::Icap::ModXact::bypassFailure()
 {
-    disableBypass("already started to bypass");
+    disableBypass("already started to bypass", false);
 
     Must(!isRetriable); // or we should not be bypassing
+    // TODO: should the same be enforced for isRepeatable? Check icap_repeat??
 
     prepEchoing();
 
@@ -597,7 +647,7 @@ void Adaptation::Icap::ModXact::bypassFailure()
     stopParsing();
 
     stopWriting(true); // or should we force it?
-    if (connection >= 0) {
+    if (haveConnection()) {
         reuseConnection = false; // be conservative
         cancelRead(); // may not work; and we cannot stop connecting either
         if (!doneWithIo())
@@ -605,12 +655,16 @@ void Adaptation::Icap::ModXact::bypassFailure()
     }
 }
 
-void Adaptation::Icap::ModXact::disableBypass(const char *reason)
+void Adaptation::Icap::ModXact::disableBypass(const char *reason, bool includingGroupBypass)
 {
     if (canStartBypass) {
         debugs(93,7, HERE << "will never start bypass because " << reason);
         canStartBypass = false;
     }
+    if (protectGroupBypass && includingGroupBypass) {
+        debugs(93,7, HERE << "not protecting group bypass because " << reason);
+        protectGroupBypass = false;
+    }
 }
 
 
@@ -623,8 +677,11 @@ void Adaptation::Icap::ModXact::maybeAllocateHttpMsg()
 
     if (gotEncapsulated("res-hdr")) {
         adapted.setHeader(new HttpReply);
+        setOutcome(service().cfg().method == ICAP::methodReqmod ?
+                   xoSatisfied : xoModified);
     } else if (gotEncapsulated("req-hdr")) {
         adapted.setHeader(new HttpRequest);
+        setOutcome(xoModified);
     } else
         throw TexcHere("Neither res-hdr nor req-hdr in maybeAllocateHttpMsg()");
 }
@@ -654,7 +711,8 @@ void Adaptation::Icap::ModXact::parseHeaders()
 // called after parsing all headers or when bypassing an exception
 void Adaptation::Icap::ModXact::startSending()
 {
-    disableBypass("sent headers");
+    disableRepeats("sent headers");
+    disableBypass("sent headers", true);
     sendAnswer(adapted.header);
 
     if (state.sending == State::sendingVirgin)
@@ -694,12 +752,50 @@ void Adaptation::Icap::ModXact::parseIcapHead()
         handle204NoContent();
         break;
 
+    case 206:
+        handle206PartialContent();
+        break;
+
     default:
         debugs(93, 5, HERE << "ICAP status " << icapReply->sline.status);
         handleUnknownScode();
         break;
     }
 
+    const HttpRequest *request = dynamic_cast<HttpRequest*>(adapted.header);
+    if (!request)
+        request = &virginRequest();
+
+    // update the cross-transactional database if needed (all status codes!)
+    if (const char *xxName = Adaptation::Config::masterx_shared_name) {
+        Adaptation::History::Pointer ah = request->adaptHistory(true);
+        if (ah != NULL) {
+            const String val = icapReply->header.getByName(xxName);
+            if (val.size() > 0) // XXX: HttpHeader lacks empty value detection
+                ah->updateXxRecord(xxName, val);
+        }
+    }
+
+    // update the adaptation plan if needed (all status codes!)
+    if (service().cfg().routing) {
+        String services;
+        if (icapReply->header.getList(HDR_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
+
+    // We need to store received ICAP headers for <icapLastHeader logformat option.
+    // If we already have stored headers from previous ICAP transaction related to this
+    // request, old headers will be replaced with the new one.
+
+    Adaptation::Icap::History::Pointer h = request->icapHistory();
+    if (h != NULL) {
+        h->mergeIcapHeaders(&icapReply->header);
+        h->setIcapLastHeader(&icapReply->header);
+    }
+
     // handle100Continue() manages state.writing on its own.
     // Non-100 status means the server needs no postPreview data from us.
     if (state.writing == State::writingPaused)
@@ -731,8 +827,9 @@ void Adaptation::Icap::ModXact::handle100Continue()
     // server must not respond before the end of preview: we may send ieof
     Must(preview.enabled() && preview.done() && !preview.ieof());
 
-    // 100 "Continue" cancels our preview commitment, not 204s outside preview
-    if (!state.allowedPostview204)
+    // 100 "Continue" cancels our Preview commitment,
+    // but not commitment to handle 204 or 206 outside Preview
+    if (!state.allowedPostview204 && !state.allowedPostview206)
         stopBackup();
 
     state.parsing = State::psIcapHeader; // eventually
@@ -757,17 +854,37 @@ void Adaptation::Icap::ModXact::handle204NoContent()
     prepEchoing();
 }
 
+void Adaptation::Icap::ModXact::handle206PartialContent()
+{
+    if (state.writing == State::writingPaused) {
+        Must(preview.enabled());
+        Must(state.allowedPreview206);
+        debugs(93, 7, HERE << "206 inside preview");
+    } else {
+        Must(state.writing > State::writingPaused);
+        Must(state.allowedPostview206);
+        debugs(93, 7, HERE << "206 outside preview");
+    }
+    state.parsing = State::psHttpHeader;
+    state.sending = State::sendingAdapted;
+    state.readyForUob = true;
+    checkConsuming();
+}
+
 // Called when we receive a 204 No Content response and
 // when we are trying to bypass a service failure.
 // We actually start sending (echoig or not) in startSending.
 void Adaptation::Icap::ModXact::prepEchoing()
 {
-    disableBypass("preparing to echo content");
+    disableRepeats("preparing to echo content");
+    disableBypass("preparing to echo content", true);
+    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.
     // 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()!
 
     HttpMsg *oldHead = virgin.header;
     debugs(93, 7, HERE << "cloning virgin message " << oldHead);
@@ -780,30 +897,34 @@ void Adaptation::Icap::ModXact::prepEchoing()
 
     // allocate the adapted message and copy metainfo
     Must(!adapted.header);
-    HttpMsg *newHead = NULL;
-    if (dynamic_cast<const HttpRequest*>(oldHead)) {
-        HttpRequest *newR = new HttpRequest;
-        newHead = newR;
-    } else if (dynamic_cast<const HttpReply*>(oldHead)) {
-        HttpReply *newRep = new HttpReply;
-        newHead = newRep;
-    }
-    Must(newHead);
-    newHead->inheritProperties(oldHead);
+    {
+        HttpMsg::Pointer newHead;
+        if (const HttpRequest *oldR = dynamic_cast<const HttpRequest*>(oldHead)) {
+            HttpRequest::Pointer newR(new HttpRequest);
+            newR->canonical = oldR->canonical ?
+                              xstrdup(oldR->canonical) : NULL; // parse() does not set it
+            newHead = newR;
+        } else if (dynamic_cast<const HttpReply*>(oldHead)) {
+            newHead = new HttpReply;
+        }
+        Must(newHead != NULL);
+
+        newHead->inheritProperties(oldHead);
 
-    adapted.setHeader(newHead);
+        adapted.setHeader(newHead);
+    }
 
     // parse the buffer back
     http_status error = HTTP_STATUS_NONE;
 
-    Must(newHead->parse(&httpBuf, true, &error));
+    Must(adapted.header->parse(&httpBuf, true, &error));
 
-    Must(newHead->hdr_sz == httpBuf.contentSize()); // no leftovers
+    Must(adapted.header->hdr_sz == httpBuf.contentSize()); // no leftovers
 
     httpBuf.clean();
 
     debugs(93, 7, HERE << "cloned virgin message " << oldHead << " to " <<
-           newHead);
+           adapted.header);
 
     // setup adapted body pipe if needed
     if (oldHead->body_pipe != NULL) {
@@ -827,6 +948,37 @@ void Adaptation::Icap::ModXact::prepEchoing()
     }
 }
 
+/// Called when we received use-original-body chunk extension in 206 response.
+/// We actually start sending (echoing or not) in startSending().
+void Adaptation::Icap::ModXact::prepPartialBodyEchoing(uint64_t pos)
+{
+    Must(virginBodySending.active());
+    Must(virgin.header->body_pipe != NULL);
+
+    setOutcome(xoPartEcho);
+
+    debugs(93, 7, HERE << "will echo virgin body suffix from " <<
+           virgin.header->body_pipe << " offset " << pos );
+
+    // check that use-original-body=N does not point beyond buffered data
+    const uint64_t virginDataEnd = virginConsumed +
+                                   virgin.body_pipe->buf().contentSize();
+    Must(pos <= virginDataEnd);
+    virginBodySending.progress(static_cast<size_t>(pos));
+
+    state.sending = State::sendingVirgin;
+    checkConsuming();
+
+    if (virgin.header->body_pipe->bodySizeKnown())
+        adapted.body_pipe->expectProductionEndAfter(virgin.header->body_pipe->bodySize() - pos);
+
+    debugs(93, 7, HERE << "will echo virgin body suffix to " <<
+           adapted.body_pipe);
+
+    // Start echoing data
+    echoMore();
+}
+
 void Adaptation::Icap::ModXact::handleUnknownScode()
 {
     stopParsing();
@@ -840,11 +992,15 @@ void Adaptation::Icap::ModXact::handleUnknownScode()
 void Adaptation::Icap::ModXact::parseHttpHead()
 {
     if (gotEncapsulated("res-hdr") || gotEncapsulated("req-hdr")) {
+        replyHttpHeaderSize = 0;
         maybeAllocateHttpMsg();
 
         if (!parseHead(adapted.header))
             return; // need more header data
 
+        if (adapted.header)
+            replyHttpHeaderSize = adapted.header->hdr_sz;
+
         if (dynamic_cast<HttpRequest*>(adapted.header)) {
             const HttpRequest *oldR = dynamic_cast<const HttpRequest*>(virgin.header);
             Must(oldR);
@@ -879,6 +1035,9 @@ bool Adaptation::Icap::ModXact::parseHead(HttpMsg *head)
         return false;
     }
 
+    if (HttpRequest *r = dynamic_cast<HttpRequest*>(head))
+        urlCanonical(r); // parse does not set HttpRequest::canonical
+
     debugs(93, 5, HERE << "parse success, consume " << head->hdr_sz << " bytes, return true");
     readBuf.consume(head->hdr_sz);
     return true;
@@ -889,6 +1048,7 @@ void Adaptation::Icap::ModXact::decideOnParsingBody()
     if (gotEncapsulated("res-body") || gotEncapsulated("req-body")) {
         debugs(93, 5, HERE << "expecting a body");
         state.parsing = State::psBody;
+        replyHttpBodySize = 0;
         bodyParser = new ChunkedCodingParser;
         makeAdaptedBodyPipe("adapted response from the ICAP server");
         Must(state.sending == State::sendingAdapted);
@@ -913,12 +1073,23 @@ void Adaptation::Icap::ModXact::parseBody()
 
     debugs(93, 5, HERE << "have " << readBuf.contentSize() << " body bytes after " <<
            "parse; parsed all: " << parsed);
+    replyHttpBodySize += adapted.body_pipe->buf().contentSize();
 
     // TODO: expose BodyPipe::putSize() to make this check simpler and clearer
-    if (adapted.body_pipe->buf().contentSize() > 0) // parsed something sometime
-        disableBypass("sent adapted content");
+    // TODO: do we really need this if we disable when sending headers?
+    if (adapted.body_pipe->buf().contentSize() > 0) { // parsed something sometime
+        disableRepeats("sent adapted content");
+        disableBypass("sent adapted content", true);
+    }
 
     if (parsed) {
+        if (state.readyForUob && bodyParser->useOriginBody >= 0) {
+            prepPartialBodyEchoing(
+                static_cast<uint64_t>(bodyParser->useOriginBody));
+            stopParsing();
+            return;
+        }
+
         stopParsing();
         stopSending(true); // the parser succeeds only if all parsed data fits
         return;
@@ -1003,9 +1174,15 @@ void Adaptation::Icap::ModXact::noteMoreBodySpaceAvailable(BodyPipe::Pointer)
 // adapted body consumer aborted
 void Adaptation::Icap::ModXact::noteBodyConsumerAborted(BodyPipe::Pointer)
 {
+    detailError(ERR_DETAIL_ICAP_XACT_BODY_CONSUMER_ABORT);
     mustStop("adapted body consumer aborted");
 }
 
+Adaptation::Icap::ModXact::~ModXact()
+{
+    delete bodyParser;
+}
+
 // internal cleanup
 void Adaptation::Icap::ModXact::swanSong()
 {
@@ -1014,14 +1191,86 @@ void Adaptation::Icap::ModXact::swanSong()
     stopWriting(false);
     stopSending(false);
 
-    if (icapReply) {
-        delete icapReply;
-        icapReply = NULL;
-    }
+    if (theInitiator.set()) // we have not sent the answer to the initiator
+        detailError(ERR_DETAIL_ICAP_XACT_OTHER);
+
+    // update adaptation history if start was called and we reserved a slot
+    Adaptation::History::Pointer ah = virginRequest().adaptLogHistory();
+    if (ah != NULL && adaptHistoryId >= 0)
+        ah->recordXactFinish(adaptHistoryId);
 
     Adaptation::Icap::Xaction::swanSong();
 }
 
+void prepareLogWithRequestDetails(HttpRequest *, AccessLogEntry *);
+
+void Adaptation::Icap::ModXact::finalizeLogInfo()
+{
+    HttpRequest * request_ = NULL;
+    HttpReply * reply_ = NULL;
+    if (!(request_ = dynamic_cast<HttpRequest*>(adapted.header))) {
+        request_ = (virgin.cause? virgin.cause: dynamic_cast<HttpRequest*>(virgin.header));
+        reply_ = dynamic_cast<HttpReply*>(adapted.header);
+    }
+
+    Adaptation::Icap::History::Pointer h = 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.request = HTTPMSGLOCK(request_);
+    if (reply_)
+        al.reply = HTTPMSGLOCK(reply_);
+    else
+        al.reply = NULL;
+
+    if (h->rfc931.size())
+        al.cache.rfc931 = h->rfc931.termedBuf();
+
+#if USE_SSL
+    if (h->ssluser.size())
+        al.cache.ssluser = h->ssluser.termedBuf();
+#endif
+    al.cache.code = h->logType;
+    al.cache.requestSize = h->req_sz;
+
+    // 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);
+    }
+
+    if (reply_) {
+        al.http.code = reply_->sline.status;
+        al.http.content_type = reply_->content_type.termedBuf();
+        if (replyHttpBodySize >= 0) {
+            al.cache.replySize = replyHttpBodySize + reply_->hdr_sz;
+            al.cache.highOffset = replyHttpBodySize;
+        }
+        //don't set al.cache.objectSize because it hasn't exist yet
+
+        Packer p;
+        MemBuf mb;
+
+        mb.init();
+        packerToMemInit(&p, &mb);
+
+        reply_->header.packInto(&p);
+        al.headers.reply = xstrdup(mb.buf);
+
+        packerClean(&p);
+        mb.clean();
+    }
+    prepareLogWithRequestDetails(request_, &al);
+    Xaction::finalizeLogInfo();
+}
+
+
 void Adaptation::Icap::ModXact::makeRequestHeaders(MemBuf &buf)
 {
     char ntoabuf[MAX_IPSTRLEN];
@@ -1048,6 +1297,21 @@ void Adaptation::Icap::ModXact::makeRequestHeaders(MemBuf &buf)
         buf.Printf("Proxy-Authorization: " SQUIDSTRINGPH "\r\n", SQUIDSTRINGPRINT(vh));
     }
 
+    const HttpRequest *request = &virginRequest();
+
+    // share the cross-transactional database records if needed
+    if (Adaptation::Config::masterx_shared_name) {
+        Adaptation::History::Pointer ah = request->adaptHistory(true);
+        if (ah != NULL) {
+            String name, value;
+            if (ah->getXxRecord(name, value)) {
+                buf.Printf(SQUIDSTRINGPH ": " SQUIDSTRINGPH "\r\n",
+                           SQUIDSTRINGPRINT(name), SQUIDSTRINGPRINT(value));
+            }
+        }
+    }
+
+
     buf.Printf("Encapsulated: ");
 
     MemBuf httpBuf;
@@ -1057,10 +1321,6 @@ void Adaptation::Icap::ModXact::makeRequestHeaders(MemBuf &buf)
     // build HTTP request header, if any
     ICAP::Method m = s.method;
 
-    const HttpRequest *request = virgin.cause ?
-                                 virgin.cause :
-                                 dynamic_cast<const HttpRequest*>(virgin.header);
-
     // to simplify, we could assume that request is always available
 
     String urlPath;
@@ -1068,9 +1328,8 @@ void Adaptation::Icap::ModXact::makeRequestHeaders(MemBuf &buf)
         urlPath = request->urlpath;
         if (ICAP::methodRespmod == m)
             encapsulateHead(buf, "req-hdr", httpBuf, request);
-        else
-            if (ICAP::methodReqmod == m)
-                encapsulateHead(buf, "req-hdr", httpBuf, virgin.header);
+        else if (ICAP::methodReqmod == m)
+            encapsulateHead(buf, "req-hdr", httpBuf, virgin.header);
     }
 
     if (ICAP::methodRespmod == m)
@@ -1088,24 +1347,24 @@ void Adaptation::Icap::ModXact::makeRequestHeaders(MemBuf &buf)
 
     if (preview.enabled()) {
         buf.Printf("Preview: %d\r\n", (int)preview.ad());
-        if (virginBody.expected()) // there is a body to preview
-            virginBodySending.plan();
-        else
+        if (!virginBody.expected()) // there is no body to preview
             finishNullOrEmptyBodyPreview(httpBuf);
     }
 
-    if (shouldAllow204()) {
-        debugs(93,5, HERE << "will allow 204s outside of preview");
-        state.allowedPostview204 = true;
-        buf.Printf("Allow: 204\r\n");
-        if (virginBody.expected()) // there is a body to echo
-            virginBodySending.plan();
+    makeAllowHeader(buf);
+
+    if (TheConfig.send_client_ip && request) {
+        Ip::Address client_addr;
+#if FOLLOW_X_FORWARDED_FOR
+        if (TheConfig.icap_uses_indirect_client) {
+            client_addr = request->indirect_client_addr;
+        } else
+#endif
+            client_addr = request->client_addr;
+        if (!client_addr.IsAnyAddr() && !client_addr.IsNoAddr())
+            buf.Printf("X-Client-IP: %s\r\n", client_addr.NtoA(ntoabuf,MAX_IPSTRLEN));
     }
 
-    if (TheConfig.send_client_ip && request)
-        if (!request->client_addr.IsAnyAddr() && !request->client_addr.IsNoAddr())
-            buf.Printf("X-Client-IP: %s\r\n", request->client_addr.NtoA(ntoabuf,MAX_IPSTRLEN));
-
     if (TheConfig.send_client_username && request)
         makeUsernameHeader(request, buf);
 
@@ -1113,20 +1372,60 @@ void Adaptation::Icap::ModXact::makeRequestHeaders(MemBuf &buf)
 
     buf.append(ICAP::crlf, 2); // terminate ICAP header
 
+    // fill icapRequest for logging
+    Must(icapRequest->parseCharBuf(buf.content(), buf.contentSize()));
+
     // start ICAP request body with encapsulated HTTP headers
     buf.append(httpBuf.content(), httpBuf.contentSize());
 
     httpBuf.clean();
 }
 
+// decides which Allow values to write and updates the request buffer
+void Adaptation::Icap::ModXact::makeAllowHeader(MemBuf &buf)
+{
+    const bool allow204in = preview.enabled(); // TODO: add shouldAllow204in()
+    const bool allow204out = state.allowedPostview204 = shouldAllow204();
+    const bool allow206in = state.allowedPreview206 = shouldAllow206in();
+    const bool allow206out = state.allowedPostview206 = shouldAllow206out();
+
+    debugs(93,9, HERE << "Allows: " << allow204in << allow204out <<
+           allow206in << allow206out);
+
+    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();
+
+    // 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);
+    }
+}
+
 void Adaptation::Icap::ModXact::makeUsernameHeader(const HttpRequest *request, MemBuf &buf)
 {
-    if (const AuthUserRequest *auth = request->auth_user_request) {
-        if (char const *name = auth->username()) {
-            const char *value = TheConfig.client_username_encode ?
-                                base64_encode(name) : name;
-            buf.Printf("%s: %s\r\n", TheConfig.client_username_header,
-                       value);
+    if (request->auth_user_request != NULL) {
+        char const *name = request->auth_user_request->username();
+        if (name) {
+            const char *value = TheConfig.client_username_encode ? base64_encode(name) : name;
+            buf.Printf("%s: %s\r\n", TheConfig.client_username_header, value);
         }
     }
 }
@@ -1137,20 +1436,20 @@ void Adaptation::Icap::ModXact::encapsulateHead(MemBuf &icapBuf, const char *sec
     icapBuf.Printf("%s=%d, ", section, (int) httpBuf.contentSize());
 
     // begin cloning
-    HttpMsg *headClone = NULL;
+    HttpMsg::Pointer headClone;
 
     if (const HttpRequest* old_request = dynamic_cast<const HttpRequest*>(head)) {
-        HttpRequest* new_request = new HttpRequest;
-        urlParse(old_request->method, old_request->canonical,new_request);
+        HttpRequest::Pointer new_request(new HttpRequest);
+        Must(old_request->canonical);
+        urlParse(old_request->method, old_request->canonical, new_request);
         new_request->http_ver = old_request->http_ver;
         headClone = new_request;
     } else if (const HttpReply *old_reply = dynamic_cast<const HttpReply*>(head)) {
-        HttpReply* new_reply = new HttpReply;
+        HttpReply::Pointer new_reply(new HttpReply);
         new_reply->sline = old_reply->sline;
         headClone = new_reply;
     }
-
-    Must(headClone);
+    Must(headClone != NULL);
     headClone->inheritProperties(head);
 
     HttpHeaderPos pos = HttpHeaderInitPos;
@@ -1167,7 +1466,7 @@ void Adaptation::Icap::ModXact::encapsulateHead(MemBuf &icapBuf, const char *sec
     // pack polished HTTP header
     packHead(httpBuf, headClone);
 
-    delete headClone;
+    // headClone unlocks and, hence, deletes the message we packed
 }
 
 void Adaptation::Icap::ModXact::packHead(MemBuf &httpBuf, const HttpMsg *head)
@@ -1186,10 +1485,7 @@ void Adaptation::Icap::ModXact::decideOnPreview()
         return;
     }
 
-    const HttpRequest *request = virgin.cause ?
-                                 virgin.cause :
-                                 dynamic_cast<const HttpRequest*>(virgin.header);
-    const String urlPath = request ? request->urlpath : String();
+    const String urlPath = virginRequest().urlpath;
     size_t wantedSize;
     if (!service().wantsPreview(urlPath, wantedSize)) {
         debugs(93, 5, HERE << "should not offer preview for " << urlPath);
@@ -1205,9 +1501,8 @@ void Adaptation::Icap::ModXact::decideOnPreview()
 
     if (!virginBody.expected())
         ad = 0;
-    else
-        if (virginBody.knownSize())
-            ad = min(static_cast<uint64_t>(ad), virginBody.size()); // not more than we have
+    else if (virginBody.knownSize())
+        ad = min(static_cast<uint64_t>(ad), virginBody.size()); // not more than we have
 
     debugs(93, 5, HERE << "should offer " << ad << "-byte preview " <<
            "(service wanted " << wantedSize << ")");
@@ -1225,6 +1520,25 @@ bool Adaptation::Icap::ModXact::shouldAllow204()
     return canBackupEverything();
 }
 
+// decides whether to allow 206 responses in some mode
+bool Adaptation::Icap::ModXact::shouldAllow206any()
+{
+    return TheConfig.allow206_enable && service().allows206() &&
+           virginBody.expected(); // no need for 206 without a body
+}
+
+// decides whether to allow 206 responses in preview mode
+bool Adaptation::Icap::ModXact::shouldAllow206in()
+{
+    return shouldAllow206any() && preview.enabled();
+}
+
+// decides whether to allow 206 responses outside of preview
+bool Adaptation::Icap::ModXact::shouldAllow206out()
+{
+    return shouldAllow206any() && canBackupEverything();
+}
+
 // used by shouldAllow204 and decideOnRetries
 bool Adaptation::Icap::ModXact::canBackupEverything() const
 {
@@ -1287,7 +1601,7 @@ void Adaptation::Icap::ModXact::fillPendingStatus(MemBuf &buf) const
     if (virgin.body_pipe != NULL)
         buf.append("R", 1);
 
-    if (connection > 0 && !doneReading())
+    if (haveConnection() && !doneReading())
         buf.append("r", 1);
 
     if (!state.doneWriting() && state.writing != State::writingInit)
@@ -1307,8 +1621,14 @@ void Adaptation::Icap::ModXact::fillPendingStatus(MemBuf &buf) const
     if (!doneSending() && state.sending != State::sendingUndecided)
         buf.Printf("S(%d)", state.sending);
 
+    if (state.readyForUob)
+        buf.append("6", 1);
+
     if (canStartBypass)
         buf.append("Y", 1);
+
+    if (protectGroupBypass)
+        buf.append("G", 1);
 }
 
 void Adaptation::Icap::ModXact::fillDoneStatus(MemBuf &buf) const
@@ -1356,11 +1676,10 @@ void Adaptation::Icap::ModXact::estimateVirginBody()
 
     if (virgin.cause)
         method = virgin.cause->method;
+    else if (HttpRequest *req = dynamic_cast<HttpRequest*>(msg))
+        method = req->method;
     else
-        if (HttpRequest *req = dynamic_cast<HttpRequest*>(msg))
-            method = req->method;
-        else
-            method = METHOD_NONE;
+        method = METHOD_NONE;
 
     int64_t size;
     // expectingBody returns true for zero-sized bodies, but we will not
@@ -1507,9 +1826,8 @@ void Adaptation::Icap::Preview::wrote(size_t size, bool wroteEof)
 
     if (wroteEof)
         theState = stIeof; // written size is irrelevant
-    else
-        if (theWritten >= theAd)
-            theState = stDone;
+    else if (theWritten >= theAd)
+        theState = stDone;
 }
 
 bool Adaptation::Icap::ModXact::fillVirginHttpHeader(MemBuf &mb) const
@@ -1522,15 +1840,23 @@ bool Adaptation::Icap::ModXact::fillVirginHttpHeader(MemBuf &mb) const
     return true;
 }
 
+void Adaptation::Icap::ModXact::detailError(int errDetail)
+{
+    if (HttpRequest *request = virgin.cause ?
+                               virgin.cause : dynamic_cast<HttpRequest*>(virgin.header)) {
+        request->detailError(ERR_ICAP_FAILURE, errDetail);
+    }
+}
 
 /* Adaptation::Icap::ModXactLauncher */
 
-Adaptation::Icap::ModXactLauncher::ModXactLauncher(Adaptation::Initiator *anInitiator, HttpMsg *virginHeader, HttpRequest *virginCause, Adaptation::ServicePointer aService):
+Adaptation::Icap::ModXactLauncher::ModXactLauncher(HttpMsg *virginHeader, HttpRequest *virginCause, Adaptation::ServicePointer aService):
         AsyncJob("Adaptation::Icap::ModXactLauncher"),
-        Adaptation::Icap::Launcher("Adaptation::Icap::ModXactLauncher", anInitiator, aService)
+        Adaptation::Icap::Launcher("Adaptation::Icap::ModXactLauncher", aService)
 {
     virgin.setHeader(virginHeader);
     virgin.setCause(virginCause);
+    updateHistory(true);
 }
 
 Adaptation::Icap::Xaction *Adaptation::Icap::ModXactLauncher::createXaction()
@@ -1538,5 +1864,29 @@ Adaptation::Icap::Xaction *Adaptation::Icap::ModXactLauncher::createXaction()
     Adaptation::Icap::ServiceRep::Pointer s =
         dynamic_cast<Adaptation::Icap::ServiceRep*>(theService.getRaw());
     Must(s != NULL);
-    return new Adaptation::Icap::ModXact(this, virgin.header, virgin.cause, s);
+    return new Adaptation::Icap::ModXact(virgin.header, virgin.cause, s);
+}
+
+void Adaptation::Icap::ModXactLauncher::swanSong()
+{
+    debugs(93, 5, HERE << "swan sings");
+    updateHistory(false);
+    Adaptation::Icap::Launcher::swanSong();
+}
+
+void Adaptation::Icap::ModXactLauncher::updateHistory(bool doStart)
+{
+    HttpRequest *r = virgin.cause ?
+                     virgin.cause : dynamic_cast<HttpRequest*>(virgin.header);
+
+    // r should never be NULL but we play safe; TODO: add Should()
+    if (r) {
+        Adaptation::Icap::History::Pointer h = r->icapHistory();
+        if (h != NULL) {
+            if (doStart)
+                h->start("ICAPModXactLauncher");
+            else
+                h->stop("ICAPModXactLauncher");
+        }
+    }
 }