#include "HttpHeaderTools.h"
#include "MemBuf.h"
#include "mgr/Registration.h"
+#include "mime_header.h"
#include "profiler/Profiler.h"
#include "rfc1123.h"
#include "SquidConfig.h"
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)
{
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;
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();
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)
* 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;
}
replyHttpHeaderSize(-1),
replyHttpBodySize(-1),
adaptHistoryId(-1),
+ trailerParser(nullptr),
alMaster(alp)
{
assert(virginHeader);
if (state.parsing == State::psBody)
parseBody();
+
+ if (state.parsing == State::psIcapTrailer)
+ parseIcapTrailer();
}
void Adaptation::Icap::ModXact::callException(const std::exception &e)
// end all activities associated with the ICAP server
- stopParsing();
+ stopParsing(false);
stopWriting(true); // or should we force it?
if (haveConnection()) {
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;
stopWriting(true);
}
-bool Adaptation::Icap::ModXact::validate200Ok()
-{
- if (ICAP::methodRespmod == service().cfg().method) {
- if (!gotEncapsulated("res-hdr"))
- return false;
+/// Parses ICAP trailers and stops parsing, if all trailer data
+/// have been received.
+void Adaptation::Icap::ModXact::parseIcapTrailer() {
- return true;
+ if (parsePart(trailerParser, "trailer")) {
+ for (const auto &e: trailerParser->trailer.entries)
+ debugs(93, 5, "ICAP trailer: " << e->name << ": " << e->value);
+ stopParsing();
}
+}
- if (ICAP::methodReqmod == service().cfg().method) {
- if (!gotEncapsulated("res-hdr") && !gotEncapsulated("req-hdr"))
- return false;
-
- return true;
- }
+bool Adaptation::Icap::ModXact::validate200Ok()
+{
+ if (service().cfg().method == ICAP::methodRespmod)
+ return gotEncapsulated("res-hdr");
- return false;
+ return service().cfg().method == ICAP::methodReqmod &&
+ expectHttpHeader();
}
void Adaptation::Icap::ModXact::handle100Continue()
void Adaptation::Icap::ModXact::handleUnknownScode()
{
- stopParsing();
+ stopParsing(false);
stopBackup();
// TODO: mark connection as "bad"
void Adaptation::Icap::ModXact::parseHttpHead()
{
- if (gotEncapsulated("res-hdr") || gotEncapsulated("req-hdr")) {
+ if (expectHttpHeader()) {
replyHttpHeaderSize = 0;
maybeAllocateHttpMsg();
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;
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);
}
}
}
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;
}
}
}
-void Adaptation::Icap::ModXact::stopParsing()
+void Adaptation::Icap::ModXact::stopParsing(const bool checkUnparsedData)
{
if (state.parsing == State::psDone)
return;
- debugs(93, 7, HERE << "will no longer parse" << status());
+ if (checkUnparsedData)
+ Must(readBuf.isEmpty());
+
+ debugs(93, 7, "will no longer parse" << status());
delete bodyParser;
+ bodyParser = nullptr;
- bodyParser = NULL;
+ delete trailerParser;
+ trailerParser = nullptr;
state.parsing = State::psDone;
}
Adaptation::Icap::ModXact::~ModXact()
{
delete bodyParser;
+ delete trailerParser;
}
// internal cleanup
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;
// 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");
}
}
}
}
+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;
+}
+
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);
void decideOnParsingBody();
void parseBody();
+ void parseIcapTrailer();
void maybeAllocateHttpMsg();
void handle100Continue();
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 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();
int adaptHistoryId; ///< adaptation history slot reservation
+ TrailerParser *trailerParser;
+
class State
{
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,
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
* - 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)