2016-11-11 8:27 GMT+03:00 Amos Jeffries <[email protected]>:

> In Adaptation::Icap::ModXact::expectIcapTrailers() please use
> getByIdIfPresent(Http::TRAILER, ...) since Trailer is a registered
> header.

Fixed.

> In answer to "TODO: should we add a new Http::scInvalidTrailer?" only if
> the draft defines an HTTP 5xx status code called "Ivalid > Trailer".
> Since the Http::sc* are HTTP status codes.

The draft does not define HTTP 5xx status codes for invalid trailers.
Also please note that scInvalidHeader (600 code) is marked for
internal/Squid-only use. Probably we could similarly use
scInvalidTrailer with code 602 (for example).

> Only if all the callers of this function are handling the case where it
> produces non-1 and adding a terminator LF explicitly for a re-parse
> attempt.

I have only found such 're-parse' logic inside
HttpStateData::processReplyHeader(), marked with the comment. However
this code does not use the patch-affected HttpHeader::parse(): it uses
Http::One::Parser::grabMimeBlock() instead. So, nothing to fix here, IMO.


Eduard.

Initial ICAP trailer support.

ICAP trailers are currently specified by
https://datatracker.ietf.org/doc/draft-rousskov-icap-trailers/

This implementation complies with version -01 of that draft:

- Squid unconditionally announces ICAP Trailer support via the ICAP
  Allow request header field.

- Squid parses the ICAP response trailer if and only if the ICAP server
  signals its presence by sending both Trailer header and Allow/trailers
  in the ICAP response.

Squid logs and ignores all parsed ICAP header fields (for now).

Also refactored HttpHeader parsing methods in order to reuse them for
ICAP trailer parsing.

Also simplified and fixed headers isolating code while dealing with
empty (i.e. zero header fields) headers. Old httpMsgIsolateHeaders()
tried to re-implement header end detection/processing logic that is
actually covered by headersEnd(). Old httpMsgIsolateHeaders() could
return success for some garbage input (e.g., a buffer of several CRs)
even if no end of headers was found.

=== modified file 'src/HttpHeader.cc'
--- src/HttpHeader.cc	2016-10-06 21:02:32 +0000
+++ src/HttpHeader.cc	2016-11-08 13:37:55 +0000
@@ -1,54 +1,55 @@
 /*
  * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 55    HTTP Header */
 
 #include "squid.h"
 #include "base/EnumIterator.h"
 #include "base64.h"
 #include "globals.h"
 #include "http/ContentLengthInterpreter.h"
 #include "HttpHdrCc.h"
 #include "HttpHdrContRange.h"
 #include "HttpHdrScTarget.h" // also includes HttpHdrSc.h
 #include "HttpHeader.h"
 #include "HttpHeaderFieldInfo.h"
 #include "HttpHeaderStat.h"
 #include "HttpHeaderTools.h"
 #include "MemBuf.h"
 #include "mgr/Registration.h"
+#include "mime_header.h"
 #include "profiler/Profiler.h"
 #include "rfc1123.h"
 #include "SquidConfig.h"
 #include "StatHist.h"
 #include "Store.h"
 #include "StrList.h"
 #include "TimeOrTag.h"
 #include "util.h"
 
 #include <algorithm>
 
 /* XXX: the whole set of API managing the entries vector should be rethought
  *      after the parse4r-ng effort is complete.
  */
 
 /*
  * On naming conventions:
  *
  * HTTP/1.1 defines message-header as
  *
  * message-header = field-name ":" [ field-value ] CRLF
  * field-name     = token
  * field-value    = *( field-content | LWS )
  *
  * HTTP/1.1 does not give a name name a group of all message-headers in a message.
  * Squid 1.1 seems to refer to that group _plus_ start-line as "headers".
  *
  * HttpHeader is an object that represents all message-headers in a message.
  * HttpHeader does not manage start-line.
  *
@@ -289,60 +290,105 @@
 
     const HttpHeaderEntry *e;
     HttpHeaderPos pos = HttpHeaderInitPos;
 
     while ((e = fresh->getEntry(&pos))) {
         /* deny bad guys (ok to check for Http::HdrType::OTHER) here */
 
         if (skipUpdateHeader(e->id))
             continue;
 
         if (e->id != Http::HdrType::OTHER)
             delById(e->id);
         else
             delByName(e->name.termedBuf());
     }
 
     pos = HttpHeaderInitPos;
     while ((e = fresh->getEntry(&pos))) {
         /* deny bad guys (ok to check for Http::HdrType::OTHER) here */
 
         if (skipUpdateHeader(e->id))
             continue;
 
         debugs(55, 7, "Updating header '" << Http::HeaderLookupTable.lookup(e->id).name << "' in cached entry");
 
         addEntry(e->clone());
     }
     return true;
 }
 
+bool
+HttpHeader::Isolate(const char **parse_start, size_t l, const char **blk_start, const char **blk_end)
+{
+    /*
+     * parse_start points to the first line of HTTP message *headers*,
+     * not including the request or status lines
+     */
+    const size_t end = headersEnd(*parse_start, l);
+
+    if (end) {
+        *blk_start = *parse_start;
+        *blk_end = *parse_start + end - 1;
+        assert(**blk_end == '\n');
+        // Point blk_end to the first character after the last header field.
+        // In other words, blk_end should point to the CR?LF header terminator.
+        if (end > 1 && *(*blk_end - 1) == '\r')
+            --(*blk_end);
+        *parse_start += end;
+    }
+    return end;
+}
+
+int
+HttpHeader::parse(const char *buf, size_t buf_len, bool atEnd, size_t &hdr_sz)
+{
+    const char *parse_start = buf;
+    const char *blk_start, *blk_end;
+    hdr_sz = 0;
+
+    if (!Isolate(&parse_start, buf_len, &blk_start, &blk_end)) {
+        // XXX: do not parse non-isolated headers even if the connection is closed.
+        // Treat unterminated headers as "partial headers" framing errors.
+        if (!atEnd)
+            return 0;
+        blk_start = parse_start;
+        blk_end = blk_start + strlen(blk_start);
+    }
+
+    if (parse(blk_start, blk_end - blk_start)) {
+        hdr_sz = parse_start - buf;
+        return 1;
+    }
+    return -1;
+}
+
 int
 HttpHeader::parse(const char *header_start, size_t hdrLen)
 {
     const char *field_ptr = header_start;
     const char *header_end = header_start + hdrLen; // XXX: remove
     int warnOnError = (Config.onoff.relaxed_header_parser <= 0 ? DBG_IMPORTANT : 2);
 
     PROF_start(HttpHeaderParse);
 
     assert(header_start && header_end);
     debugs(55, 7, "parsing hdr: (" << this << ")" << std::endl << getStringPrefix(header_start, hdrLen));
     ++ HttpHeaderStats[owner].parsedCount;
 
     char *nulpos;
     if ((nulpos = (char*)memchr(header_start, '\0', hdrLen))) {
         debugs(55, DBG_IMPORTANT, "WARNING: HTTP header contains NULL characters {" <<
                getStringPrefix(header_start, nulpos-header_start) << "}\nNULL\n{" << getStringPrefix(nulpos+1, hdrLen-(nulpos-header_start)-1));
         PROF_stop(HttpHeaderParse);
         clean();
         return 0;
     }
 
     Http::ContentLengthInterpreter clen(warnOnError);
     /* common format headers are "<name>:[ws]<value>" lines delimited by <CRLF>.
      * continuation lines start with a (single) space or tab */
     while (field_ptr < header_end) {
         const char *field_start = field_ptr;
         const char *field_end;
 
         do {

=== modified file 'src/HttpHeader.h'
--- src/HttpHeader.h	2016-07-23 13:36:56 +0000
+++ src/HttpHeader.h	2016-11-08 13:37:55 +0000
@@ -57,60 +57,65 @@
     HttpHeaderEntry *clone() const;
     void packInto(Packable *p) const;
     int getInt() const;
     int64_t getInt64() const;
 
     Http::HdrType id;
     String name;
     String value;
 };
 
 class ETag;
 class TimeOrTag;
 
 class HttpHeader
 {
 
 public:
     HttpHeader();
     explicit HttpHeader(const http_hdr_owner_type owner);
     HttpHeader(const HttpHeader &other);
     ~HttpHeader();
 
     HttpHeader &operator =(const HttpHeader &other);
 
     /* Interface functions */
     void clean();
     void append(const HttpHeader * src);
     bool update(HttpHeader const *fresh);
     void compact();
     int parse(const char *header_start, size_t len);
+    /// Parses headers stored in a buffer.
+    /// \returns 1 and sets hdr_sz on success
+    /// \returns 0 when needs more data
+    /// \returns -1 on error
+    int parse(const char *buf, size_t buf_len, bool atEnd, size_t &hdr_sz);
     void packInto(Packable * p, bool mask_sensitive_info=false) const;
     HttpHeaderEntry *getEntry(HttpHeaderPos * pos) const;
     HttpHeaderEntry *findEntry(Http::HdrType id) const;
     int delByName(const char *name);
     int delById(Http::HdrType id);
     void delAt(HttpHeaderPos pos, int &headers_deleted);
     void refreshMask();
     void addEntry(HttpHeaderEntry * e);
     void insertEntry(HttpHeaderEntry * e);
     String getList(Http::HdrType id) const;
     bool getList(Http::HdrType id, String *s) const;
     bool conflictingContentLength() const { return conflictingContentLength_; }
     String getStrOrList(Http::HdrType id) const;
     String getByName(const SBuf &name) const;
     String getByName(const char *name) const;
     String getById(Http::HdrType id) const;
     /// sets value and returns true iff a [possibly empty] field identified by id is there
     bool getByIdIfPresent(Http::HdrType id, String &result) const;
     /// sets value and returns true iff a [possibly empty] named field is there
     bool getByNameIfPresent(const SBuf &s, String &value) const;
     bool getByNameIfPresent(const char *name, int namelen, String &value) const;
     String getByNameListMember(const char *name, const char *member, const char separator) const;
     String getListMember(Http::HdrType id, const char *member, const char separator) const;
     int has(Http::HdrType id) const;
     void putInt(Http::HdrType id, int number);
     void putInt64(Http::HdrType id, int64_t number);
     void putTime(Http::HdrType id, time_t htime);
     void putStr(Http::HdrType id, const char *str);
     void putAuth(const char *auth_scheme, const char *realm);
     void putCc(const HttpHdrCc * cc);
@@ -118,57 +123,64 @@
     void putRange(const HttpHdrRange * range);
     void putSc(HttpHdrSc *sc);
     void putWarning(const int code, const char *const text); ///< add a Warning header
     void putExt(const char *name, const char *value);
     int getInt(Http::HdrType id) const;
     int64_t getInt64(Http::HdrType id) const;
     time_t getTime(Http::HdrType id) const;
     const char *getStr(Http::HdrType id) const;
     const char *getLastStr(Http::HdrType id) const;
     HttpHdrCc *getCc() const;
     HttpHdrRange *getRange() const;
     HttpHdrSc *getSc() const;
     HttpHdrContRange *getContRange() const;
     const char *getAuth(Http::HdrType id, const char *auth_scheme) const;
     ETag getETag(Http::HdrType id) const;
     TimeOrTag getTimeOrTag(Http::HdrType id) const;
     int hasListMember(Http::HdrType id, const char *member, const char separator) const;
     int hasByNameListMember(const char *name, const char *member, const char separator) const;
     void removeHopByHopEntries();
     inline bool chunked() const; ///< whether message uses chunked Transfer-Encoding
 
     /* protected, do not use these, use interface functions instead */
     std::vector<HttpHeaderEntry *> entries;     /**< parsed fields in raw format */
     HttpHeaderMask mask;    /**< bit set <=> entry present */
     http_hdr_owner_type owner;  /**< request or reply */
     int len;            /**< length when packed, not counting terminating null-byte */
 
 protected:
     /** \deprecated Public access replaced by removeHopByHopEntries() */
     void removeConnectionHeaderEntries();
+    /// either finds the end of headers or returns false
+    /// If the end was found:
+    /// *parse_start points to the first character after the header delimiter
+    /// *blk_start points to the first header character (i.e. old parse_start value)
+    /// *blk_end points to the first header delimiter character (CR or LF in CR?LF).
+    /// If block starts where it ends, then there are no fields in the header.
+    static bool Isolate(const char **parse_start, size_t l, const char **blk_start, const char **blk_end);
     bool needUpdate(const HttpHeader *fresh) const;
     bool skipUpdateHeader(const Http::HdrType id) const;
     void updateWarnings();
 
 private:
     HttpHeaderEntry *findLastEntry(Http::HdrType id) const;
     bool conflictingContentLength_; ///< found different Content-Length fields
 };
 
 int httpHeaderParseQuotedString(const char *start, const int len, String *val);
 
 /// quotes string using RFC 7230 quoted-string rules
 SBuf httpHeaderQuoteString(const char *raw);
 
 void httpHeaderCalcMask(HttpHeaderMask * mask, Http::HdrType http_hdr_type_enums[], size_t count);
 
 inline bool
 HttpHeader::chunked() const
 {
     return has(Http::HdrType::TRANSFER_ENCODING) &&
            hasListMember(Http::HdrType::TRANSFER_ENCODING, "chunked", ',');
 }
 
 void httpHeaderInitModule(void);
 
 #endif /* SQUID_HTTPHEADER_H */
 

=== modified file 'src/HttpMsg.cc'
--- src/HttpMsg.cc	2016-09-13 17:30:03 +0000
+++ src/HttpMsg.cc	2016-11-08 13:37:55 +0000
@@ -33,119 +33,60 @@
 {
     assert(!body_pipe);
 }
 
 void
 HttpMsg::putCc(const HttpHdrCc *otherCc)
 {
     // get rid of the old CC, if any
     if (cache_control) {
         delete cache_control;
         cache_control = nullptr;
         if (!otherCc)
             header.delById(Http::HdrType::CACHE_CONTROL);
         // else it will be deleted inside putCc() below
     }
 
     // add new CC, if any
     if (otherCc) {
         cache_control = new HttpHdrCc(*otherCc);
         header.putCc(cache_control);
     }
 }
 
 HttpMsgParseState &operator++ (HttpMsgParseState &aState)
 {
     int tmp = (int)aState;
     aState = (HttpMsgParseState)(++tmp);
     return aState;
 }
 
-/* find end of headers */
-static int
-httpMsgIsolateHeaders(const char **parse_start, int l, const char **blk_start, const char **blk_end)
-{
-    /*
-     * parse_start points to the first line of HTTP message *headers*,
-     * not including the request or status lines
-     */
-    size_t end = headersEnd(*parse_start, l);
-    int nnl;
-
-    if (end) {
-        *blk_start = *parse_start;
-        *blk_end = *parse_start + end - 1;
-        /*
-         * leave blk_end pointing to the first character after the
-         * first newline which terminates the headers
-         */
-        assert(**blk_end == '\n');
-
-        while (*(*blk_end - 1) == '\r')
-            --(*blk_end);
-
-        assert(*(*blk_end - 1) == '\n');
-
-        *parse_start += end;
-
-        return 1;
-    }
-
-    /*
-     * If we didn't find the end of headers, and parse_start does
-     * NOT point to a CR or NL character, then return failure
-     */
-    if (**parse_start != '\r' && **parse_start != '\n')
-        return 0;       /* failure */
-
-    /*
-     * If we didn't find the end of headers, and parse_start does point
-     * to an empty line, then we have empty headers.  Skip all CR and
-     * NL characters up to the first NL.  Leave parse_start pointing at
-     * the first character after the first NL.
-     */
-    *blk_start = *parse_start;
-
-    *blk_end = *blk_start;
-
-    for (nnl = 0; nnl == 0; ++(*parse_start)) {
-        if (**parse_start == '\r')
-            (void) 0;
-        else if (**parse_start == '\n')
-            ++nnl;
-        else
-            break;
-    }
-
-    return 1;
-}
-
 /* find first CRLF */
 static int
 httpMsgIsolateStart(const char **parse_start, const char **blk_start, const char **blk_end)
 {
     int slen = strcspn(*parse_start, "\r\n");
 
     if (!(*parse_start)[slen])  /* no CRLF found */
         return 0;
 
     *blk_start = *parse_start;
 
     *blk_end = *blk_start + slen;
 
     while (**blk_end == '\r')   /* CR */
         ++(*blk_end);
 
     if (**blk_end == '\n')      /* LF */
         ++(*blk_end);
 
     *parse_start = *blk_end;
 
     return 1;
 }
 
 // negative return is the negated Http::StatusCode error code
 // zero return means need more data
 // positive return is the size of parsed headers
 bool
 HttpMsg::parse(const char *buf, const size_t sz, bool eof, Http::StatusCode *error)
 {
@@ -248,81 +189,68 @@
 
     *parse_end_ptr = parse_start;
 
     PROF_start(HttpMsg_httpMsgParseStep);
 
     if (pstate == psReadyToParseStartLine) {
         if (!httpMsgIsolateStart(&parse_start, &blk_start, &blk_end)) {
             PROF_stop(HttpMsg_httpMsgParseStep);
             return 0;
         }
 
         if (!parseFirstLine(blk_start, blk_end)) {
             PROF_stop(HttpMsg_httpMsgParseStep);
             return httpMsgParseError();
         }
 
         *parse_end_ptr = parse_start;
 
         hdr_sz = *parse_end_ptr - buf;
         parse_len = parse_len - hdr_sz;
 
         ++pstate;
     }
 
     /*
      * XXX This code uses parse_start; but if we're incrementally parsing then
      * this code might not actually be given parse_start at the right spot (just
      * after headers.) Grr.
      */
     if (pstate == psReadyToParseHeaders) {
-        if (!httpMsgIsolateHeaders(&parse_start, parse_len, &blk_start, &blk_end)) {
-            if (atEnd) {
-                blk_start = parse_start;
-                blk_end = blk_start + strlen(blk_start);
-            } else {
-                PROF_stop(HttpMsg_httpMsgParseStep);
-                return 0;
-            }
-        }
-
-        if (!header.parse(blk_start, blk_end-blk_start)) {
+        size_t hsize = 0;
+        const int parsed = header.parse(parse_start, parse_len, atEnd, hsize);
+        if (parsed <= 0) {
             PROF_stop(HttpMsg_httpMsgParseStep);
-            return httpMsgParseError();
+            return !parsed ? 0 : httpMsgParseError();
         }
-
+        hdr_sz += hsize;
         hdrCacheInit();
-
-        *parse_end_ptr = parse_start;
-
-        hdr_sz = *parse_end_ptr - buf;
-
         ++pstate;
     }
 
     PROF_stop(HttpMsg_httpMsgParseStep);
     return 1;
 }
 
 bool
 HttpMsg::parseHeader(Http1::Parser &hp)
 {
     // HTTP/1 message contains "zero or more header fields"
     // zero does not need parsing
     // XXX: c_str() reallocates. performance regression.
     if (hp.headerBlockSize() && !header.parse(hp.mimeHeader().c_str(), hp.headerBlockSize())) {
         pstate = psError;
         return false;
     }
 
     // XXX: we are just parsing HTTP headers, not the whole message prefix here
     hdr_sz = hp.messageHeaderSize();
     pstate = psParsed;
     hdrCacheInit();
     return true;
 }
 
 /* handy: resets and returns -1 */
 int
 HttpMsg::httpMsgParseError()
 {
     reset();

=== modified file 'src/adaptation/icap/ModXact.cc'
--- src/adaptation/icap/ModXact.cc	2016-10-24 08:31:40 +0000
+++ src/adaptation/icap/ModXact.cc	2016-11-12 17:04:29 +0000
@@ -33,60 +33,61 @@
 #include "SquidTime.h"
 #include "URL.h"
 
 // flow and terminology:
 //     HTTP| --> receive --> encode --> write --> |network
 //     end | <-- send    <-- parse  <-- read  <-- |end
 
 // TODO: replace gotEncapsulated() with something faster; we call it often
 
 CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Icap, ModXact);
 CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Icap, ModXactLauncher);
 
 static const size_t TheBackupLimit = BodyPipe::MaxCapacity;
 
 Adaptation::Icap::ModXact::State::State()
 {
     memset(this, 0, sizeof(*this));
 }
 
 Adaptation::Icap::ModXact::ModXact(HttpMsg *virginHeader,
                                    HttpRequest *virginCause, AccessLogEntry::Pointer &alp, Adaptation::Icap::ServiceRep::Pointer &aService):
     AsyncJob("Adaptation::Icap::ModXact"),
     Adaptation::Icap::Xaction("Adaptation::Icap::ModXact", aService),
     virginConsumed(0),
     bodyParser(NULL),
     canStartBypass(false), // too early
     protectGroupBypass(true),
     replyHttpHeaderSize(-1),
     replyHttpBodySize(-1),
     adaptHistoryId(-1),
+    trailerParser(nullptr),
     alMaster(alp)
 {
     assert(virginHeader);
 
     virgin.setHeader(virginHeader); // sets virgin.body_pipe if needed
     virgin.setCause(virginCause); // may be NULL
 
     // adapted header and body are initialized when we parse them
 
     // writing and reading ends are handled by Adaptation::Icap::Xaction
 
     // encoding
     // nothing to do because we are using temporary buffers
 
     // parsing; TODO: do not set until we parse, see ICAPOptXact
     icapReply = new HttpReply;
     icapReply->protoPrefix = "ICAP/"; // TODO: make an IcapReply class?
 
     debugs(93,7, HERE << "initialized." << status());
 }
 
 // initiator wants us to start
 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);
@@ -630,102 +631,105 @@
     } else {
         debugs(93, 7, HERE << "will not start sending" << status());
         Must(!adapted.body_pipe);
     }
 
     state.sending = State::sendingDone;
     checkConsuming();
 }
 
 // should be called after certain state.writing or state.sending changes
 void Adaptation::Icap::ModXact::checkConsuming()
 {
     // quit if we already stopped or are still using the pipe
     if (!virgin.body_pipe || !state.doneConsumingVirgin())
         return;
 
     debugs(93, 7, HERE << "will stop consuming" << status());
     stopConsumingFrom(virgin.body_pipe);
 }
 
 void Adaptation::Icap::ModXact::parseMore()
 {
     debugs(93, 5, "have " << readBuf.length() << " bytes to parse" << status());
     debugs(93, 5, "\n" << readBuf);
 
     if (state.parsingHeaders())
         parseHeaders();
 
     if (state.parsing == State::psBody)
         parseBody();
+
+    if (state.parsing == State::psIcapTrailer)
+        parseIcapTrailer();
 }
 
 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;
     }
 
     try {
         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", false);
 
     Must(!isRetriable); // or we should not be bypassing
     // TODO: should the same be enforced for isRepeatable? Check icap_repeat??
 
     prepEchoing();
 
     startSending();
 
     // end all activities associated with the ICAP server
 
-    stopParsing();
+    stopParsing(false);
 
     stopWriting(true); // or should we force it?
     if (haveConnection()) {
         reuseConnection = false; // be conservative
         cancelRead(); // may not work; and we cannot stop connecting either
         if (!doneWithIo())
             debugs(93, 7, HERE << "Warning: bypass failed to stop I/O" << status());
     }
 
     service().noteFailure(); // we are bypassing, but this is still a failure
 }
 
 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;
     }
 }
 
 // note that allocation for echoing is done in handle204NoContent()
 void Adaptation::Icap::ModXact::maybeAllocateHttpMsg()
 {
     if (adapted.header) // already allocated
         return;
 
@@ -763,60 +767,65 @@
 }
 
 // called after parsing all headers or when bypassing an exception
 void Adaptation::Icap::ModXact::startSending()
 {
     disableRepeats("sent headers");
     disableBypass("sent headers", true);
     sendAnswer(Answer::Forward(adapted.header));
 
     if (state.sending == State::sendingVirgin)
         echoMore();
     else {
         // If we are not using the virgin HTTP object update the
         // HttpMsg::sources flag.
         // The state.sending may set to State::sendingVirgin in the case
         // of 206 responses too, where we do not want to update HttpMsg::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()
 {
     Must(state.sending == State::sendingUndecided);
 
     if (!parseHead(icapReply.getRaw()))
         return;
 
+    if (expectIcapTrailers()) {
+        Must(!trailerParser);
+        trailerParser = new TrailerParser();
+    }
+
     if (httpHeaderHasConnDir(&icapReply->header, "close")) {
         debugs(93, 5, HERE << "found connection close");
         reuseConnection = false;
     }
 
     switch (icapReply->sline.status()) {
 
     case Http::scContinue:
         handle100Continue();
         break;
 
     case Http::scOkay:
     case Http::scCreated: // Symantec Scan Engine 5.0 and later when modifying HTTP msg
 
         if (!validate200Ok()) {
             throw TexcHere("Invalid ICAP Response");
         } else {
             handle200Ok();
         }
 
         break;
 
     case Http::scNoContent:
         handle204NoContent();
         break;
 
     case Http::scPartialContent:
         handle206PartialContent();
         break;
 
@@ -837,77 +846,78 @@
             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(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 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
     // request, old headers will be replaced with the new one.
 
     Adaptation::History::Pointer ah = request->adaptLogHistory();
     if (ah != NULL)
         ah->recordMeta(&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)
         stopWriting(true);
 }
 
+/// Parses ICAP trailers and stops parsing, if all trailer data
+/// have been received.
+void Adaptation::Icap::ModXact::parseIcapTrailer() {
+
+    if (parsePart(trailerParser, "trailer")) {
+        for (const auto &e: trailerParser->trailer.entries)
+            debugs(93, 5, "ICAP trailer: " << e->name << ": " << e->value);
+        stopParsing();
+    }
+}
+
 bool Adaptation::Icap::ModXact::validate200Ok()
 {
-    if (ICAP::methodRespmod == service().cfg().method) {
-        if (!gotEncapsulated("res-hdr"))
-            return false;
-
-        return true;
-    }
-
-    if (ICAP::methodReqmod == service().cfg().method) {
-        if (!gotEncapsulated("res-hdr") && !gotEncapsulated("req-hdr"))
-            return false;
-
-        return true;
-    }
-
-    return false;
+    if (service().cfg().method == ICAP::methodRespmod)
+        return gotEncapsulated("res-hdr");
+
+    return service().cfg().method == ICAP::methodReqmod &&
+           expectHttpHeader();
 }
 
 void Adaptation::Icap::ModXact::handle100Continue()
 {
     Must(state.writing == State::writingPaused);
     // 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,
     // but not commitment to handle 204 or 206 outside Preview
     if (!state.allowedPostview204 && !state.allowedPostview206)
         stopBackup();
 
     state.parsing = State::psIcapHeader; // eventually
     icapReply->reset();
 
     state.writing = State::writingPrime;
 
     writeMore();
 }
 
 void Adaptation::Icap::ModXact::handle200Ok()
 {
     state.parsing = State::psHttpHeader;
     state.sending = State::sendingAdapted;
     stopBackup();
     checkConsuming();
 }
 
 void Adaptation::Icap::ModXact::handle204NoContent()
@@ -1013,260 +1023,294 @@
 {
     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();
+    stopParsing(false);
     stopBackup();
     // TODO: mark connection as "bad"
 
     // Terminate the transaction; we do not know how to handle this response.
     throw TexcHere("Unsupported ICAP status code");
 }
 
 void Adaptation::Icap::ModXact::parseHttpHead()
 {
-    if (gotEncapsulated("res-hdr") || gotEncapsulated("req-hdr")) {
+    if (expectHttpHeader()) {
         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);
             // TODO: the adapted request did not really originate from the
             // client; give proxy admin an option to prevent copying of
             // sensitive client information here. See the following thread:
             // http://www.squid-cache.org/mail-archive/squid-dev/200703/0040.html
         }
 
         // Maybe adapted.header==NULL if HttpReply and have Http 0.9 ....
         if (adapted.header)
             adapted.header->inheritProperties(virgin.header);
     }
 
     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
     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(HttpMsg *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;
         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);
     }
 }
 
 void Adaptation::Icap::ModXact::parseBody()
 {
     Must(state.parsing == State::psBody);
     Must(bodyParser);
 
     debugs(93, 5, "have " << readBuf.length() << " body bytes to parse");
 
     // the parser will throw on errors
     BodyPipeCheckout bpc(*adapted.body_pipe);
     bodyParser->setPayloadBuffer(&bpc.buf);
     const bool parsed = bodyParser->parse(readBuf);
     readBuf = bodyParser->remaining(); // sync buffers after parse
     bpc.checkIn();
 
     debugs(93, 5, "have " << readBuf.length() << " body bytes after parsed all: " << parsed);
     replyHttpBodySize += adapted.body_pipe->buf().contentSize();
 
     // TODO: expose BodyPipe::putSize() to make this check simpler and clearer
     // 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));
+        if (state.readyForUob && bodyParser->useOriginBody >= 0)
+            prepPartialBodyEchoing(static_cast<uint64_t>(bodyParser->useOriginBody));
+        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;
     }
 
     debugs(93,3,HERE << this << " needsMoreData = " << bodyParser->needsMoreData());
 
     if (bodyParser->needsMoreData()) {
         debugs(93,3,HERE << this);
         Must(mayReadMore());
         readMore();
     }
 
     if (bodyParser->needsMoreSpace()) {
         Must(!doneSending()); // can hope for more space
         Must(adapted.body_pipe->buf().contentSize() > 0); // paranoid
         // TODO: there should be a timeout in case the sink is broken
         // or cannot consume partial content (while we need more space)
     }
 }
 
-void Adaptation::Icap::ModXact::stopParsing()
+void Adaptation::Icap::ModXact::stopParsing(const bool checkUnparsedData)
 {
     if (state.parsing == State::psDone)
         return;
 
+    if (checkUnparsedData)
+        Must(readBuf.isEmpty());
+
     debugs(93, 7, HERE << "will no longer parse" << status());
 
     delete bodyParser;
-
     bodyParser = NULL;
 
+    delete trailerParser;
+    trailerParser = NULL;
+
     state.parsing = State::psDone;
 }
 
 // HTTP side added virgin body data
 void Adaptation::Icap::ModXact::noteMoreBodyDataAvailable(BodyPipe::Pointer)
 {
     writeMore();
 
     if (state.sending == State::sendingVirgin)
         echoMore();
 }
 
 // HTTP side sent us all virgin info
 void Adaptation::Icap::ModXact::noteBodyProductionEnded(BodyPipe::Pointer)
 {
     Must(virgin.body_pipe->productionEnded());
 
     // push writer and sender in case we were waiting for the last-chunk
     writeMore();
 
     if (state.sending == State::sendingVirgin)
         echoMore();
 }
 
 // body producer aborted, but the initiator may still want to know
 // the answer, even though the HTTP message has been truncated
 void Adaptation::Icap::ModXact::noteBodyProducerAborted(BodyPipe::Pointer)
 {
     Must(virgin.body_pipe->productionEnded());
 
     // push writer and sender in case we were waiting for the last-chunk
     writeMore();
 
     if (state.sending == State::sendingVirgin)
         echoMore();
 }
 
 // adapted body consumer wants more adapted data and
 // possibly freed some buffer space
 void Adaptation::Icap::ModXact::noteMoreBodySpaceAvailable(BodyPipe::Pointer)
 {
     if (state.sending == State::sendingVirgin)
         echoMore();
     else if (state.sending == State::sendingAdapted)
         parseMore();
     else
         Must(state.sending == State::sendingUndecided);
 }
 
 // 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;
+    delete trailerParser;
 }
 
 // internal cleanup
 void Adaptation::Icap::ModXact::swanSong()
 {
     debugs(93, 5, HERE << "swan sings" << status());
 
     stopWriting(false);
     stopSending(false);
 
     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::Pointer &);
 
 void Adaptation::Icap::ModXact::finalizeLogInfo()
 {
     HttpRequest *adapted_request_ = nullptr;
     HttpReply *adapted_reply_ = nullptr;
     HttpRequest *virgin_request_ = const_cast<HttpRequest*>(&virginRequest());
     if (!(adapted_request_ = dynamic_cast<HttpRequest*>(adapted.header))) {
         // if the request was not adapted, use virgin request to simplify
@@ -1455,88 +1499,87 @@
             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);
             }
         }
     }
 
     // fprintf(stderr, "%s\n", buf.content());
 
     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();
+    const bool allowTrailers = true; // TODO: make configurable
 
     debugs(93,9, HERE << "Allows: " << allow204in << allow204out <<
-           allow206in << allow206out);
+           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();
 
     // 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");
     }
 }
 
 void Adaptation::Icap::ModXact::makeUsernameHeader(const HttpRequest *request, MemBuf &buf)
 {
 #if USE_AUTH
     struct base64_encode_ctx ctx;
     base64_encode_init(&ctx);
 
     const char *value = NULL;
     if (request->auth_user_request != NULL) {
         value = request->auth_user_request->username();
     } else if (request->extacl_user.size() > 0) {
         value = request->extacl_user.termedBuf();
     }
 
     if (value) {
         if (TheConfig.client_username_encode) {
             uint8_t 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);
         } else
             buf.appendf("%s: %s\r\n", TheConfig.client_username_header, value);
     }
 #endif
 }
 
 void Adaptation::Icap::ModXact::encapsulateHead(MemBuf &icapBuf, const char *section, MemBuf &httpBuf, const HttpMsg *head)
 {
@@ -1988,30 +2031,37 @@
     Adaptation::Icap::ServiceRep::Pointer s =
         dynamic_cast<Adaptation::Icap::ServiceRep*>(theService.getRaw());
     Must(s != NULL);
     return new Adaptation::Icap::ModXact(virgin.header, virgin.cause, al, 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");
         }
     }
 }
 
+bool Adaptation::Icap::TrailerParser::parse(const char *buf, int len, int atEnd, Http::StatusCode *error) {
+    const int parsed = trailer.parse(buf, len, atEnd, hdr_sz);
+    if (parsed < 0)
+        *error = Http::scInvalidHeader; // TODO: should we add a new Http::scInvalidTrailer?
+    return parsed > 0;
+}
+

=== modified file 'src/adaptation/icap/ModXact.h'
--- src/adaptation/icap/ModXact.h	2016-02-02 15:39:23 +0000
+++ src/adaptation/icap/ModXact.h	2016-11-08 13:37:55 +0000
@@ -78,60 +78,75 @@
 
     typedef enum { stUndecided, stActive, stDisabled } State;
     State theState;
 };
 
 // maintains preview-related sizes
 
 class Preview
 {
 
 public:
     Preview();            // disabled
     void enable(size_t anAd); // enabled with advertised size
     bool enabled() const;
 
     /* other members can be accessed iff enabled() */
 
     size_t ad() const;      // advertised preview size
     size_t debt() const;    // remains to write
     bool done() const;      // wrote everything
     bool ieof() const;      // premature EOF
 
     void wrote(size_t size, bool wroteEof);
 
 private:
     size_t theWritten;
     size_t theAd;
     enum State { stDisabled, stWriting, stIeof, stDone } theState;
 };
 
+/// Parses and stores ICAP trailer header block.
+class TrailerParser
+{
+public:
+    TrailerParser() : trailer(hoReply), hdr_sz(0) {}
+    /// Parses trailers stored in a buffer.
+    /// \returns true and sets hdr_sz on success
+    /// \returns false and sets *error to zero when needs more data
+    /// \returns false and sets *error to a positive Http::StatusCode on error
+    bool parse(const char *buf, int len, int atEnd, Http::StatusCode *error);
+    HttpHeader trailer;
+    /// parsed trailer size if parse() was successful
+    size_t hdr_sz; // pedantic XXX: wrong type dictated by HttpHeader::parse() API
+};
+
 class ModXact: public Xaction, public BodyProducer, public BodyConsumer
 {
     CBDATA_CLASS(ModXact);
 
 public:
     ModXact(HttpMsg *virginHeader, HttpRequest *virginCause, AccessLogEntry::Pointer &alp, ServiceRep::Pointer &s);
     virtual ~ModXact();
 
     // BodyProducer methods
     virtual void noteMoreBodySpaceAvailable(BodyPipe::Pointer);
     virtual void noteBodyConsumerAborted(BodyPipe::Pointer);
 
     // BodyConsumer methods
     virtual void noteMoreBodyDataAvailable(BodyPipe::Pointer);
     virtual void noteBodyProductionEnded(BodyPipe::Pointer);
     virtual void noteBodyProducerAborted(BodyPipe::Pointer);
 
     // comm handlers
     virtual void handleCommConnected();
     virtual void handleCommWrote(size_t size);
     virtual void handleCommRead(size_t size);
     void handleCommWroteHeaders();
     void handleCommWroteBody();
 
     // service waiting
     void noteServiceReady();
     void noteServiceAvailable();
 
 public:
     InOut virgin;
@@ -179,159 +194,175 @@
 
     void makeRequestHeaders(MemBuf &buf);
     void makeAllowHeader(MemBuf &buf);
     void makeUsernameHeader(const HttpRequest *request, MemBuf &buf);
     void addLastRequestChunk(MemBuf &buf);
     void openChunk(MemBuf &buf, size_t chunkSize, bool ieof);
     void closeChunk(MemBuf &buf);
     void virginConsume();
     void finishNullOrEmptyBodyPreview(MemBuf &buf);
 
     void decideOnPreview();
     void decideOnRetries();
     bool shouldAllow204();
     bool shouldAllow206any();
     bool shouldAllow206in();
     bool shouldAllow206out();
     bool canBackupEverything() const;
 
     void prepBackup(size_t expectedSize);
     void backup(const MemBuf &buf);
 
     void parseMore();
 
     void parseHeaders();
     void parseIcapHead();
     void parseHttpHead();
     bool parseHead(HttpMsg *head);
 
     void decideOnParsingBody();
     void parseBody();
+    void parseIcapTrailer();
     void maybeAllocateHttpMsg();
 
     void handle100Continue();
     bool validate200Ok();
     void handle200Ok();
     void handle204NoContent();
     void handle206PartialContent();
     void handleUnknownScode();
 
     void bypassFailure();
 
     void startSending();
     void disableBypass(const char *reason, bool includeGroupBypass);
 
     void prepEchoing();
     void prepPartialBodyEchoing(uint64_t pos);
     void echoMore();
     void updateSources(); ///< Update the HttpMsg sources
 
     virtual bool doneAll() const;
     virtual void swanSong();
 
     void stopReceiving();
     void stopSending(bool nicely);
     void stopWriting(bool nicely);
-    void stopParsing();
+    void stopParsing(const bool checkUnparsedData = true);
     void stopBackup();
 
     virtual void fillPendingStatus(MemBuf &buf) const;
     virtual void fillDoneStatus(MemBuf &buf) const;
     virtual bool fillVirginHttpHeader(MemBuf&) const;
 
 private:
+    /// parses a message header or trailer
+    /// \returns true on success
+    /// \returns false if more data is needed
+    /// \throw TextException on unrecoverable error
+    template<class Part>
+    bool parsePart(Part *part, const char *description);
+
     void packHead(MemBuf &httpBuf, const HttpMsg *head);
     void encapsulateHead(MemBuf &icapBuf, const char *section, MemBuf &httpBuf, const HttpMsg *head);
     bool gotEncapsulated(const char *section) const;
+    /// whether ICAP response header indicates HTTP header presence
+    bool expectHttpHeader() const;
+    /// whether ICAP response header indicates HTTP body presence
+    bool expectHttpBody() const;
+    /// whether ICAP response header indicates ICAP trailers presence
+    bool expectIcapTrailers() const;
     void checkConsuming();
 
     virtual void finalizeLogInfo();
 
     SizedEstimate virginBody;
     VirginBodyAct virginBodyWriting; // virgin body writing state
     VirginBodyAct virginBodySending;  // virgin body sending state
     uint64_t virginConsumed;        // virgin data consumed so far
     Preview preview; // use for creating (writing) the preview
 
     Http1::TeChunkedParser *bodyParser; // ICAP response body parser
 
     bool canStartBypass; // enables bypass of transaction failures
     bool protectGroupBypass; // protects ServiceGroup-wide bypass of failures
 
     /**
      * size of HTTP header in ICAP reply or -1 if there is not any encapsulated
      * message data
      */
     int64_t replyHttpHeaderSize;
     /**
      * size of dechunked HTTP body in ICAP reply or -1 if there is not any
      * encapsulated message data
      */
     int64_t replyHttpBodySize;
 
     int adaptHistoryId; ///< adaptation history slot reservation
 
+    TrailerParser *trailerParser;
+
     class State
     {
 
     public:
         State();
 
     public:
 
         bool serviceWaiting; // waiting for ICAP service options
         bool allowedPostview204; // mmust handle 204 No Content outside preview
         bool allowedPostview206; // must handle 206 Partial Content outside preview
         bool allowedPreview206; // must handle 206 Partial Content inside preview
         bool readyForUob; ///< got a 206 response and expect a use-origin-body
         bool waitedForService; ///< true if was queued at least once
 
         // will not write anything [else] to the ICAP server connection
         bool doneWriting() const { return writing == writingReallyDone; }
 
         // will not use virgin.body_pipe
         bool doneConsumingVirgin() const {
             return writing >= writingAlmostDone
                    && ((sending == sendingAdapted && !readyForUob) ||
                        sending == sendingDone);
         }
 
         // parsed entire ICAP response from the ICAP server
         bool doneParsing() const { return parsing == psDone; }
 
         // is parsing ICAP or HTTP headers read from the ICAP server
         bool parsingHeaders() const {
             return parsing == psIcapHeader ||
                    parsing == psHttpHeader;
         }
 
-        enum Parsing { psIcapHeader, psHttpHeader, psBody, psDone } parsing;
+        enum Parsing { psIcapHeader, psHttpHeader, psBody, psIcapTrailer, psDone } parsing;
 
         // measures ICAP request writing progress
         enum Writing { writingInit, writingConnect, writingHeaders,
                        writingPreview, writingPaused, writingPrime,
                        writingAlmostDone, // waiting for the last write() call to finish
                        writingReallyDone
                      } writing;
 
         enum Sending { sendingUndecided, sendingVirgin, sendingAdapted,
                        sendingDone
                      } sending;
     } state;
 
     AccessLogEntry::Pointer alMaster; ///< Master transaction AccessLogEntry
 };
 
 // An Launcher that stores ModXact construction info and
 // creates ModXact when needed
 class ModXactLauncher: public Launcher
 {
     CBDATA_CLASS(ModXactLauncher);
 
 public:
     ModXactLauncher(HttpMsg *virginHeader, HttpRequest *virginCause, AccessLogEntry::Pointer &alp, Adaptation::ServicePointer s);
 
 protected:
     virtual Xaction *createXaction();
 
     virtual void swanSong();
 

=== modified file 'src/adaptation/icap/OptXact.cc'
--- src/adaptation/icap/OptXact.cc	2016-01-01 00:12:18 +0000
+++ src/adaptation/icap/OptXact.cc	2016-11-08 13:37:55 +0000
@@ -34,62 +34,64 @@
 {
     Adaptation::Icap::Xaction::start();
 
     openConnection();
 }
 
 void Adaptation::Icap::OptXact::handleCommConnected()
 {
     scheduleRead();
 
     MemBuf requestBuf;
     requestBuf.init();
     makeRequest(requestBuf);
     debugs(93, 9, HERE << "request " << status() << ":\n" <<
            (requestBuf.terminate(), requestBuf.content()));
     icap_tio_start = current_time;
     scheduleWrite(requestBuf);
 }
 
 void Adaptation::Icap::OptXact::makeRequest(MemBuf &buf)
 {
     const Adaptation::Service &s = service();
     const String uri = s.cfg().uri;
     buf.appendf("OPTIONS " SQUIDSTRINGPH " ICAP/1.0\r\n", SQUIDSTRINGPRINT(uri));
     const String host = s.cfg().host;
     buf.appendf("Host: " SQUIDSTRINGPH ":%d\r\n", SQUIDSTRINGPRINT(host), s.cfg().port);
 
     if (!TheConfig.reuse_connections)
         buf.append("Connection: close\r\n", 19);
 
+    buf.append("Allow: ", 7);
     if (TheConfig.allow206_enable)
-        buf.append("Allow: 206\r\n", 12);
+        buf.append("206, ", 5);
+    buf.append("trailers\r\n", 10);
     buf.append(ICAP::crlf, 2);
 
     // XXX: HttpRequest cannot fully parse ICAP Request-Line
     Http::StatusCode reqStatus;
     buf.terminate(); // HttpMsg::parse requires terminated buffer
     Must(icapRequest->parse(buf.content(), buf.contentSize(), true, &reqStatus) > 0);
 }
 
 void Adaptation::Icap::OptXact::handleCommWrote(size_t size)
 {
     debugs(93, 9, HERE << "finished writing " << size <<
            "-byte request " << status());
 }
 
 // comm module read a portion of the ICAP response for us
 void Adaptation::Icap::OptXact::handleCommRead(size_t)
 {
     if (parseResponse()) {
         Must(icapReply != NULL);
         // We read everything if there is no response body. If there is a body,
         // we cannot parse it because we do not support any opt-body-types, so
         // we leave readAll false which forces connection closure.
         readAll = !icapReply->header.getByNameListMember("Encapsulated",
                   "opt-body", ',').size();
         debugs(93, 7, HERE << "readAll=" << readAll);
         icap_tio_finish = current_time;
         setOutcome(xoOpt);
         sendAnswer(Answer::Forward(icapReply.getRaw()));
         Must(done()); // there should be nothing else to do
         return;

=== modified file 'src/mime_header.h'
--- src/mime_header.h	2016-05-20 08:28:33 +0000
+++ src/mime_header.h	2016-11-08 13:37:55 +0000
@@ -1,45 +1,48 @@
 /*
  * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 25    MiME Header Parsing */
 
 #ifndef SQUID_MIME_HEADER_H_
 #define SQUID_MIME_HEADER_H_
 
 /**
  * Scan for the end of mime header block.
  *
  * Which is one of the following octet patterns:
  * - CRLF CRLF, or
  * - CRLF LF, or
  * - LF CRLF, or
- * - LF LF
+ * - LF LF or,
+ *   if mime header block is empty:
+ * - LF or
+ * - CRLF
  *
  * Also detects whether a obf-fold pattern exists within the mime block
  * - CR*LF (SP / HTAB)
  *
  * \param containsObsFold will be set to true if obs-fold pattern is found.
  */
 size_t headersEnd(const char *, size_t, bool &containsObsFold);
 
 inline size_t
 headersEnd(const SBuf &buf, bool &containsObsFold)
 {
     return headersEnd(buf.rawContent(), buf.length(), containsObsFold);
 }
 
 /// \deprecated caller needs to be fixed to handle obs-fold
 inline size_t
 headersEnd(const char *buf, size_t sz)
 {
     bool ignored;
     return headersEnd(buf, sz, ignored);
 }
 
 #endif /* SQUID_MIME_HEADER_H_ */
 

_______________________________________________
squid-dev mailing list
[email protected]
http://lists.squid-cache.org/listinfo/squid-dev

Reply via email to