Hello, This patch introduces the initial ICAP trailer support. ICAP trailers are currently specified by https://datatracker.ietf.org/doc/draft-rousskov-icap-trailers/. For now, Squid logs and ignores all parsed ICAP header fields. In future we plan to add code for using ICAP trailers for transaction annotations and access logging.
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. Regards, 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-07 21:20:57 +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-07 21:26:31 +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-02 17:23:53 +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-08 00:08:19 +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,295 @@ { 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; + static const SBuf name("Trailer"); + const bool promisesToSendTrailer = icapReply->header.getByNameIfPresent(name, 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 +1500,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 +2032,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-07 22:56:14 +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-02 17:23:53 +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-02 17:23:53 +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
