]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Fix outstanding build issues and parser audit results
authorAmos Jeffries <squid3@treenet.co.nz>
Thu, 15 May 2014 10:44:05 +0000 (03:44 -0700)
committerAmos Jeffries <squid3@treenet.co.nz>
Thu, 15 May 2014 10:44:05 +0000 (03:44 -0700)
* Give SBuf I/O buffer directly to Http1::RequestParser

* Redesign parser state engine to represent the current state
  being parsed instead of previous completed. This allows much
  more incremental resume of a parse and reliable consume() of
  the input buffer as sections complete instead of complex byte
  accounting outide the parser.

* Maintain an internal counter of bytes parsed and consumed by
  the parser instead of a buffer offset. This allows much more
  reliable positioning of the state/section boundaries.

* Remove erroneous fprintf debug left in previous commit.

* Redesign HttpRequestMethod constructor to drop end parameter.

* Redesign all parser unit tests. Marking RFC non-compliance
  for future fixing.

src/HttpRequest.cc
src/acl/MethodData.cc
src/client_side.cc
src/htcp.cc
src/http/Http1Parser.cc
src/http/Http1Parser.h
src/http/RequestMethod.cc
src/http/RequestMethod.h
src/mgr/ActionParams.cc
src/tests/testHttp1Parser.cc
src/tests/testHttpRequestMethod.cc

index 5d187e9ea04b3bcdbb0c7d5c86cb6bb114b2f314..1997b253196070113df776ffeff2f25b8003ee37 100644 (file)
@@ -304,7 +304,7 @@ HttpRequest::sanityCheckStartLine(MemBuf *buf, const size_t hdr_len, Http::Statu
     }
 
     /* See if the request buffer starts with a known HTTP request method. */
-    if (HttpRequestMethod(buf->content(),NULL) == Http::METHOD_NONE) {
+    if (HttpRequestMethod(buf->content()) == Http::METHOD_NONE) {
         debugs(73, 3, "HttpRequest::sanityCheckStartLine: did not find HTTP request method");
         *error = Http::scInvalidHeader;
         return false;
@@ -317,7 +317,8 @@ bool
 HttpRequest::parseFirstLine(const char *start, const char *end)
 {
     const char *t = start + strcspn(start, w_space);
-    method = HttpRequestMethod(start, t);
+    SBuf m(start, start-t);
+    method = HttpRequestMethod(m);
 
     if (method == Http::METHOD_NONE)
         return false;
index 495e89c8221405655c16d7c91c222094be07b657..f31ffb41133d3d35338438f6515e41197ddce327 100644 (file)
@@ -91,7 +91,7 @@ ACLMethodData::parse()
     while ((t = strtokFile())) {
         if (strcmp(t, "PURGE") == 0)
             ++ThePurgeCount; // configuration code wants to know
-        CbDataList<HttpRequestMethod> *q = new CbDataList<HttpRequestMethod> (HttpRequestMethod(t, NULL));
+        CbDataList<HttpRequestMethod> *q = new CbDataList<HttpRequestMethod> (HttpRequestMethod(t));
         *(Tail) = q;
         Tail = &q->next;
     }
index a4de94b37663b47df9d85d0a6a4eb5ad91e6a405..5fc5d9c68baeef2dc6f867489b6c271d10f79801 100644 (file)
@@ -2198,6 +2198,11 @@ parseHttpRequest(ConnStateData *csd, Http1::RequestParser &hp)
     {
         const bool parsedOk = hp.parse();
 
+        if (hp.doneBytes()) {
+            // we are done with some of the buffer. update the ConnStateData copy now.
+            csd->in.buf = hp.buf;
+        }
+
         if (!hp.isDone()) {
             debugs(33, 5, "Incomplete request, waiting for end of request line");
             return NULL;
@@ -2827,17 +2832,12 @@ ConnStateData::clientParseRequests()
         // a) dont have one already
         // b) have completed the previous request parsing already
         if (!parser_ || parser_->isDone())
-            parser_ = new Http1::RequestParser(in.buf.c_str(), in.buf.length());
+            parser_ = new Http1::RequestParser(in.buf);
         else // update the buffer space being parsed
-            parser_->bufsiz = in.buf.length();
+            parser_->buf = in.buf;
 
         /* Process request */
         ClientSocketContext *context = parseHttpRequest(this, *parser_);
-        if (parser_->doneBytes()) {
-            // we are done with some of the buffer. consume it now.
-            connNoteUseOfBuffer(this, parser_->doneBytes());
-            parser_->noteBufferShift(parser_->doneBytes());
-        }
         PROF_stop(parseHttpRequest);
 
         /* status -1 or 1 */
index 2e7778fd2bd0fb8d2cb2a5a676ea6193780bb80b..350fe9b6c1588a0163660deec394224efd6c66bb 100644 (file)
@@ -748,7 +748,7 @@ htcpUnpackSpecifier(char *buf, int sz)
     /*
      * Parse the request
      */
-    method = HttpRequestMethod(s->method, NULL);
+    method = HttpRequestMethod(s->method);
 
     s->request = HttpRequest::CreateFromUrlAndMethod(s->uri, method == Http::METHOD_NONE ? HttpRequestMethod(Http::METHOD_GET) : method);
 
index d89423775cfdae293cf72ce80d589e7bb2ad1dfe..ad1523b027269b81235d6b842750baccda92636c 100644 (file)
@@ -11,8 +11,7 @@ Http::One::Parser::clear()
 {
     parsingStage_ = HTTP_PARSE_NONE;
     buf = NULL;
-    bufsiz = 0;
-    parseOffset_ = 0;
+    parsedCount_ = 0;
     msgProtocol_ = AnyP::ProtocolVersion();
     mimeHeaderBlock_.clear();
 }
@@ -31,28 +30,13 @@ Http::One::RequestParser::clear()
 }
 
 void
-Http::One::Parser::reset(const char *aBuf, int len)
+Http::One::Parser::reset(const SBuf &aBuf)
 {
     clear(); // empty the state.
     parsingStage_ = HTTP_PARSE_NEW;
-    parseOffset_ = 0;
+    parsedCount_ = 0;
     buf = aBuf;
-    bufsiz = len;
-    debugs(74, DBG_DATA, "Parse " << Raw("buf", buf, bufsiz));
-}
-
-void
-Http::One::RequestParser::noteBufferShift(int64_t n)
-{
-    // if parsing done, ignore buffer changes.
-    if (parsingStage_ == HTTP_PARSE_DONE)
-        return;
-
-    // shift the parser resume point to match buffer content change
-    parseOffset_ -= n;
-
-    // and remember where to stop before performing buffered data overreads
-    bufsiz -= n;
+    debugs(74, DBG_DATA, "Parse buf={length=" << aBuf.length() << ", data='" << aBuf << "'}");
 }
 
 /**
@@ -78,13 +62,16 @@ Http::One::RequestParser::skipGarbageLines()
 {
 #if WHEN_RFC_COMPLIANT // CRLF or bare-LF is what RFC 2616 tolerant parsers do ...
     if (Config.onoff.relaxed_header_parser) {
-        if (Config.onoff.relaxed_header_parser < 0 && (buf[parseOffset_] == '\r' || buf[parseOffset_] == '\n'))
+        if (Config.onoff.relaxed_header_parser < 0 && (buf[0] == '\r' || buf[0] == '\n'))
             debugs(74, DBG_IMPORTANT, "WARNING: Invalid HTTP Request: " <<
                    "CRLF bytes received ahead of request-line. " <<
                    "Ignored due to relaxed_header_parser.");
         // Be tolerant of prefix empty lines
         // ie any series of either \n or \r\n with no other characters and no repeated \r
-        for (; parseOffset_ < (size_t)bufsiz && (buf[parseOffset_] == '\n' || ((buf[parseOffset_] == '\r' && (buf[parseOffset_+1] == '\n')); ++parseOffset_);
+        while (!buf.isEmpty() && (buf[0] == '\n' || (buf[0] == '\r' && buf[1] == '\n'))) {
+            buf.consume(1);
+            ++parsedCount_;
+        }
     }
 #endif
 
@@ -96,12 +83,15 @@ Http::One::RequestParser::skipGarbageLines()
      */
 #if USE_HTTP_VIOLATIONS
     if (Config.onoff.relaxed_header_parser) {
-        if (Config.onoff.relaxed_header_parser < 0 && buf[parseOffset_] == ' ')
+        if (Config.onoff.relaxed_header_parser < 0 && buf[0] == ' ')
             debugs(74, DBG_IMPORTANT, "WARNING: Invalid HTTP Request: " <<
                    "Whitespace bytes received ahead of method. " <<
                    "Ignored due to relaxed_header_parser.");
         // Be tolerant of prefix spaces (other bytes are valid method values)
-        for (; parseOffset_ < (size_t)bufsiz && buf[parseOffset_] == ' '; ++parseOffset_);
+        while (!buf.isEmpty() && buf[0] == ' ') {
+            buf.consume(1);
+            ++parsedCount_;
+        }
     }
 #endif
 }
@@ -128,14 +118,14 @@ Http::One::RequestParser::parseRequestFirstLine()
     int first_whitespace = -1, last_whitespace = -1; // track the first and last SP byte
     int line_end = -1; // tracks the last byte BEFORE terminal \r\n or \n sequence
 
-    debugs(74, 5, "parsing possible request: bufsiz=" << bufsiz << ", offset=" << parseOffset_);
-    debugs(74, DBG_DATA, Raw("(buf+offset)", buf+parseOffset_, bufsiz-parseOffset_));
+    debugs(74, 5, "parsing possible request: buf.length=" << buf.length() << ", offset=" << parsedCount_);
+    debugs(74, DBG_DATA, buf);
 
     // Single-pass parse: (provided we have the whole line anyways)
 
-    req.start = parseOffset_; // avoid re-parsing any portion we managed to complete
+    req.start = 0;
     req.end = -1;
-    for (int i = 0; i < bufsiz; ++i) {
+    for (SBuf::size_type i = 0; i < buf.length(); ++i) {
         // track first and last whitespace (SP only)
         if (buf[i] == ' ') {
             last_whitespace = i;
@@ -154,7 +144,7 @@ Http::One::RequestParser::parseRequestFirstLine()
             line_end = i - 1;
             break;
         }
-        if (i < bufsiz - 1 && buf[i] == '\r') {
+        if (i < buf.length() - 1 && buf[i] == '\r') {
             if (Config.onoff.relaxed_header_parser) {
                 if (Config.onoff.relaxed_header_parser < 0 && buf[i + 1] == '\r')
                     debugs(74, DBG_IMPORTANT, "WARNING: Invalid HTTP Request: " <<
@@ -164,7 +154,7 @@ Http::One::RequestParser::parseRequestFirstLine()
                 // Be tolerant of invalid multiple \r prior to terminal \n
                 if (buf[i + 1] == '\n' || buf[i + 1] == '\r')
                     line_end = i - 1;
-                while (i < bufsiz - 1 && buf[i + 1] == '\r')
+                while (i < buf.length() - 1 && buf[i + 1] == '\r')
                     ++i;
 
                 if (buf[i + 1] == '\n') {
@@ -188,7 +178,7 @@ Http::One::RequestParser::parseRequestFirstLine()
 
     if (req.end == -1) {
         // DoS protection against long first-line
-        if ( (size_t)bufsiz >= Config.maxRequestHeaderSize) {
+        if ((size_t)buf.length() >= Config.maxRequestHeaderSize) {
             debugs(33, 5, "Too large request-line");
             // XXX: return URL-too-log status code if second_whitespace is not yet found.
             request_parse_status = Http::scHeaderTooLarge;
@@ -234,7 +224,8 @@ Http::One::RequestParser::parseRequestFirstLine()
     }
 
     /* Set method_ */
-    method_ = HttpRequestMethod(&buf[req.m_start], &buf[req.m_end]+1);
+    SBuf tmp = buf.substr(req.m_start, req.m_end - req.m_start + 1);
+    method_ = HttpRequestMethod(tmp);
 
     // First non-whitespace after first SP = beginning of URL+Version
     if (second_word > line_end || second_word < req.start) {
@@ -248,6 +239,7 @@ Http::One::RequestParser::parseRequestFirstLine()
     if (last_whitespace < second_word && last_whitespace >= req.start) {
         msgProtocol_ = Http::ProtocolVersion(0,9);
         req.u_end = line_end;
+        uri_ = buf.substr(req.u_start, req.u_end - req.u_start + 1);
         request_parse_status = Http::scOkay; // HTTP/0.9
         return 1;
     } else {
@@ -260,7 +252,7 @@ Http::One::RequestParser::parseRequestFirstLine()
         request_parse_status = Http::scBadRequest; // missing URI
         return -1;
     }
-    uri_.assign(&buf[req.u_start], req.u_end - req.u_start + 1);
+    uri_ = buf.substr(req.u_start, req.u_end - req.u_start + 1);
 
     // Last whitespace SP = before start of protocol/version
     if (last_whitespace >= line_end) {
@@ -272,7 +264,7 @@ Http::One::RequestParser::parseRequestFirstLine()
 
     // We only accept HTTP protocol requests right now.
     // TODO: accept other protocols; RFC 2326 (RTSP protocol) etc
-    if ((req.v_end - req.v_start +1) < 5 || strncasecmp(&buf[req.v_start], "HTTP/", 5) != 0) {
+    if ((req.v_end - req.v_start +1) < 5 || buf.substr(req.v_start, 5).caseCmp(SBuf("HTTP/")) != 0) {
 #if USE_HTTP_VIOLATIONS
         // being lax; old parser accepted strange versions
         // there is a LOT of cases which are ambiguous, therefore we cannot use relaxed_header_parser here.
@@ -347,18 +339,16 @@ Http::One::RequestParser::parseRequestFirstLine()
     request_parse_status = Http::scOkay;
     return 1;
 }
-#include <cstdio>
+
 bool
 Http::One::RequestParser::parse()
 {
     // stage 1: locate the request-line
     if (parsingStage_ == HTTP_PARSE_NEW) {
-fprintf(stderr, "parse GARBAGE: '%s'\n", buf);
         skipGarbageLines();
-fprintf(stderr, "parse GBG A(%d) < B(%u)\n", bufsiz, parseOffset_);
 
         // if we hit something before EOS treat it as a message
-        if ((size_t)bufsiz > parseOffset_)
+        if (!buf.isEmpty())
             parsingStage_ = HTTP_PARSE_FIRST;
         else
             return false;
@@ -366,35 +356,32 @@ fprintf(stderr, "parse GBG A(%d) < B(%u)\n", bufsiz, parseOffset_);
 
     // stage 2: parse the request-line
     if (parsingStage_ == HTTP_PARSE_FIRST) {
-fprintf(stderr, "parse FIRST: '%s'\n", buf);
         PROF_start(HttpParserParseReqLine);
         const int retcode = parseRequestFirstLine();
-        debugs(74, 5, "request-line: retval " << retcode << ": from " << req.start << "->" << req.end << " " << Raw("line", &buf[req.start], req.end-req.start));
+        debugs(74, 5, "request-line: retval " << retcode << ": from " << req.start << "->" << req.end <<
+               " line={" << buf.length() << ", data='" << buf << "'}");
         debugs(74, 5, "request-line: method " << req.m_start << "->" << req.m_end << " (" << method_ << ")");
         debugs(74, 5, "request-line: url " << req.u_start << "->" << req.u_end << " (" << uri_ << ")");
         debugs(74, 5, "request-line: proto " << req.v_start << "->" << req.v_end << " (" << msgProtocol_ << ")");
-        debugs(74, 5, "Parser: parse-offset=" << parseOffset_);
+        debugs(74, 5, "Parser: bytes processed=" << parsedCount_);
         PROF_stop(HttpParserParseReqLine);
 
         // syntax errors already
         if (retcode < 0) {
             parsingStage_ = HTTP_PARSE_DONE;
-fprintf(stderr, "parse FIRST DONE (error)\n");
             return false;
         }
 
         // first-line (or a look-alike) found successfully.
         if (retcode > 0) {
-            parseOffset_ += firstLineSize(); // first line bytes including CRLF terminator are now done.
+            buf.consume(firstLineSize());// first line bytes including CRLF terminator are now done.
+            parsedCount_ += firstLineSize();
             parsingStage_ = HTTP_PARSE_MIME;
-fprintf(stderr, "parse FIRST (next: MIME)\n");
         }
-else fprintf(stderr, "parse FIRST: ret=%d\n",retcode);
     }
 
     // stage 3: locate the mime header block
     if (parsingStage_ == HTTP_PARSE_MIME) {
-fprintf(stderr, "parse MIME: '%s'\n", buf);
         // HTTP/1.x request-line is valid and parsing completed.
         if (msgProtocol_.major == 1) {
             /* NOTE: HTTP/0.9 requests do not have a mime header block.
@@ -402,24 +389,22 @@ fprintf(stderr, "parse MIME: '%s'\n", buf);
              *       (ie, none, so don't try parsing em)
              */
             int64_t mimeHeaderBytes = 0;
-            if ((mimeHeaderBytes = headersEnd(buf+parseOffset_, bufsiz-parseOffset_)) == 0) {
-                if (bufsiz-parseOffset_ >= Config.maxRequestHeaderSize) {
+            if ((mimeHeaderBytes = headersEnd(buf.c_str(), buf.length())) == 0) {
+                if (buf.length()+firstLineSize() >= Config.maxRequestHeaderSize) {
                     debugs(33, 5, "Too large request");
                     request_parse_status = Http::scHeaderTooLarge;
                     parsingStage_ = HTTP_PARSE_DONE;
-fprintf(stderr, "parse DONE: HTTP/1.x\n");
-                } else {
+                } else
                     debugs(33, 5, "Incomplete request, waiting for end of headers");
-fprintf(stderr, "parse MIME incomplete\n");
-}                return false;
+                return false;
             }
-            mimeHeaderBlock_.assign(&buf[req.end+1], mimeHeaderBytes);
-            parseOffset_ += mimeHeaderBytes; // done with these bytes now.
+            mimeHeaderBlock_ = buf.substr(req.end+1, mimeHeaderBytes);
+            buf.consume(mimeHeaderBytes); // done with these bytes now.
+            parsedCount_ += mimeHeaderBytes;
 
-        } else {
+        } else
             debugs(33, 3, "Missing HTTP/1.x identifier");
-fprintf(stderr, "parse MIME: HTTP/0.9\n");
-}
+
         // NP: we do not do any further stages here yet so go straight to DONE
         parsingStage_ = HTTP_PARSE_DONE;
 
index 22140df02e0dd7b1c0c99b56b1f7e4d3a57a76b3..a863ad7588bf6c3179b4ff462a27ce42bf38c7fa 100644 (file)
@@ -13,11 +13,11 @@ namespace One {
 
 // Parser states
 enum ParseState {
-    HTTP_PARSE_NONE =0,  ///< nothing. completely unset state.
-    HTTP_PARSE_NEW =1,   ///< initialized, but nothing usefully parsed yet
+    HTTP_PARSE_NONE,     ///< nothing. completely unset state.
+    HTTP_PARSE_NEW,      ///< initialized, but nothing usefully parsed yet
     HTTP_PARSE_FIRST,    ///< HTTP/1 message first line
-    HTTP_PARSE_MIME,     ///< mime header block
-    HTTP_PARSE_DONE      ///< completed with parsing a full request header
+    HTTP_PARSE_MIME,     ///< HTTP/1 mime header block
+    HTTP_PARSE_DONE      ///< parsed a message header, or reached a terminal syntax error
 };
 
 /** HTTP protocol parser.
@@ -38,21 +38,14 @@ public:
      * NOTE: This is *not* the buffer size, just the parse-able data length.
      * The parse routines may be called again later with more data.
      */
-    Parser(const char *aBuf, int len) { reset(aBuf,len); }
+    Parser(const SBuf &aBuf) { reset(aBuf); }
 
     /// Set this parser back to a default state.
     /// Will DROP any reference to a buffer (does not free).
     virtual void clear();
 
     /// Reset the parser for use on a new buffer.
-    void reset(const char *aBuf, int len);
-
-    /** Adjust parser state to account for a buffer shift of n bytes.
-     *
-     * The leftmost n bytes bytes have been dropped and all other
-     * bytes shifted left n positions.
-     */
-    virtual void noteBufferShift(const int64_t n) = 0;
+    void reset(const SBuf &aBuf);
 
     /** Whether the parser is already done processing the buffer.
      * Use to determine between incomplete data and errors results
@@ -61,7 +54,7 @@ public:
     bool isDone() const {return parsingStage_==HTTP_PARSE_DONE;}
 
     /// number of bytes at the start of the buffer which are no longer needed
-    int64_t doneBytes() const {return (int64_t)parseOffset_;}
+    int64_t doneBytes() const {return (int64_t)parsedCount_;}
 
     /// size in bytes of the first line including CRLF terminator
     virtual int64_t firstLineSize() const = 0;
@@ -92,20 +85,19 @@ public:
     char *getHeaderField(const char *name);
 
 public:
-    const char *buf;
-    int bufsiz;
+    SBuf buf;
 
 protected:
     /// what stage the parser is currently up to
     ParseState parsingStage_;
 
-    /// what protocol label has been found in the first line
-    AnyP::ProtocolVersion msgProtocol_;
+    /// total count of bytes parsed and consumed by the parser so far
+    size_t parsedCount_;
 
-    /// byte offset for non-parsed region of the buffer
-    size_t parseOffset_;
+    /// what protocol label has been found in the first line (if any)
+    AnyP::ProtocolVersion msgProtocol_;
 
-    /// buffer holding the mime headers
+    /// buffer holding the mime headers (if any)
     SBuf mimeHeaderBlock_;
 };
 
@@ -122,9 +114,8 @@ class RequestParser : public Http1::Parser
 public:
     /* Http::One::Parser API */
     RequestParser() : Parser() {}
-    RequestParser(const char *aBuf, int len) : Parser(aBuf, len) {}
+    RequestParser(const SBuf &aBuf) : Parser(aBuf) {}
     virtual void clear();
-    virtual void noteBufferShift(const int64_t n);
     virtual int64_t firstLineSize() const {return req.end - req.start + 1;}
     virtual bool parse();
 
@@ -134,8 +125,9 @@ public:
     /// the request-line URI if this is a request message, or an empty string.
     const SBuf &requestUri() const {return uri_;}
 
-    /** HTTP status code to be used on the invalid-request error page
-     * Http::scNone indicates incomplete parse, Http::scOkay indicates no error.
+    /** HTTP status code to be used on the invalid-request error page.
+     * Http::scNone indicates incomplete parse,
+     * Http::scOkay indicates no error.
      */
     Http::StatusCode request_parse_status;
 
index f1f93b1b99d5385927c404c81c7ab4e033208267..6eb1048aaa9093e232522469f19102b5d369b863 100644 (file)
@@ -16,22 +16,17 @@ operator++ (Http::MethodType &aMethod)
 }
 
 /**
- * Construct a HttpRequestMethod from a NULL terminated string such as "GET"
- * or from a range of chars, * such as "GET" from "GETFOOBARBAZ"
- * (pass in pointer to G and pointer to F.)
+ * Construct a HttpRequestMethod from a C-string such as "GET"
+ * Assumes the string is either nul-terminated or contains whitespace
+ *
+ * \deprecated use SBuf constructor instead
  */
-HttpRequestMethod::HttpRequestMethod(char const *begin, char const *end) : theMethod(Http::METHOD_NONE)
+HttpRequestMethod::HttpRequestMethod(char const *begin) : theMethod(Http::METHOD_NONE)
 {
     if (begin == NULL)
         return;
 
-    /*
-     * if e is NULL, b must be NULL terminated and we
-     * make e point to the first whitespace character
-     * after b.
-     */
-    if (NULL == end)
-        end = begin + strcspn(begin, w_space);
+    char const *end = begin + strcspn(begin, w_space);
 
     if (end == begin)
         return;
@@ -56,6 +51,39 @@ HttpRequestMethod::HttpRequestMethod(char const *begin, char const *end) : theMe
     theImage.assign(begin, end-begin);
 }
 
+/**
+ * Construct a HttpRequestMethod from an SBuf string such as "GET"
+ * or from a range of chars such as "GET" from "GETFOOBARBAZ"
+ *
+ * Assumes the s parameter contains only the method string
+ */
+HttpRequestMethod::HttpRequestMethod(const SBuf &s) : theMethod(Http::METHOD_NONE)
+{
+    if (s.isEmpty())
+        return;
+
+    // XXX: still check for missing method name?
+
+    // TODO: Optimize this linear search.
+    for (++theMethod; theMethod < Http::METHOD_ENUM_END; ++theMethod) {
+        // RFC 2616 section 5.1.1 - Method names are case-sensitive
+        // NP: this is not a HTTP_VIOLATIONS case since there is no MUST/SHOULD involved.
+        if (0 == image().caseCmp(s)) {
+
+            // relaxed parser allows mixed-case and corrects them on output
+            if (Config.onoff.relaxed_header_parser)
+                return;
+
+            if (0 == image().cmp(s))
+                return;
+        }
+    }
+
+    // if method not found and method string is not null then it is other method
+    theMethod = Http::METHOD_OTHER;
+    theImage = s;
+}
+
 const SBuf &
 HttpRequestMethod::image() const
 {
index 14c075daeb9d684e2f9581129c3ff3718363892b..49bd8c3eac5af8d11a26d0bd5ccfe9c355243a40 100644 (file)
@@ -20,14 +20,8 @@ class HttpRequestMethod : public RefCountable
 public:
     HttpRequestMethod() : theMethod(Http::METHOD_NONE), theImage() {}
     HttpRequestMethod(Http::MethodType const aMethod) : theMethod(aMethod), theImage() {}
-
-    /**
-     \param begin    string to convert to request method.
-     \param end      end of the method string (relative to begin). Use NULL if this is unknown.
-     *
-     \note DO NOT give end a default (ie NULL). That will cause silent char* conversion clashes.
-     */
-    HttpRequestMethod(char const * begin, char const * end);
+    explicit HttpRequestMethod(char const *);
+    explicit HttpRequestMethod(const SBuf &);
 
     HttpRequestMethod & operator = (const HttpRequestMethod& aMethod) {
         theMethod = aMethod.theMethod;
index 2b7705e615cc27ea61d6965855bf92fee5793a56..d1bd91fe21b0643e4bf054411aa138ba6f851d76 100644 (file)
@@ -18,7 +18,7 @@ Mgr::ActionParams::ActionParams(const Ipc::TypedMsgHdr &msg)
 
     String method;
     msg.getString(method);
-    httpMethod = HttpRequestMethod(method.termedBuf(), NULL);
+    httpMethod = HttpRequestMethod(method.termedBuf());
 
     msg.getPod(httpFlags);
     msg.getString(httpOrigin);
index fee350b5ad8dd5b6b9d92695a98268949a44b98c..92dd0dcd98aaa33c21685080ec8245eefef41a1d 100644 (file)
@@ -32,15 +32,60 @@ testHttp1Parser::globalSetup()
     Config.maxRequestHeaderSize = 1024; // XXX: unit test the RequestParser handling of this limit
 }
 
+struct resultSet {
+    bool parsed;
+    bool done;
+    Http1::ParseState parserState;
+    Http::StatusCode status;
+    int msgStart;
+    int msgEnd;
+    SBuf::size_type suffixSz;
+    int methodStart;
+    int methodEnd;
+    HttpRequestMethod method;
+    int uriStart;
+    int uriEnd;
+    const char *uri;
+    int versionStart;
+    int versionEnd;
+    AnyP::ProtocolVersion version;
+};
+
+static void
+testResults(int line, const SBuf &input, Http1::RequestParser &output, struct resultSet &expect)
+{
+#if WHEN_TEST_DEBUG_IS_NEEDED
+    printf("TEST @%d, in=%u: " SQUIDSBUFPH "\n", line, input.length(), SQUIDSBUFPRINT(input));
+#endif
+
+    CPPUNIT_ASSERT_EQUAL(expect.parsed, output.parse());
+    CPPUNIT_ASSERT_EQUAL(expect.done, output.isDone());
+    if (!output.isDone())
+        CPPUNIT_ASSERT_EQUAL(expect.parserState, output.parsingStage_);
+    CPPUNIT_ASSERT_EQUAL(expect.status, output.request_parse_status);
+    CPPUNIT_ASSERT_EQUAL(expect.msgStart, output.req.start);
+    CPPUNIT_ASSERT_EQUAL(expect.msgEnd, output.req.end);
+    CPPUNIT_ASSERT_EQUAL(expect.suffixSz, output.buf.length());
+    CPPUNIT_ASSERT_EQUAL(expect.methodStart, output.req.m_start);
+    CPPUNIT_ASSERT_EQUAL(expect.methodEnd, output.req.m_end);
+    CPPUNIT_ASSERT_EQUAL(expect.method, output.method_);
+    CPPUNIT_ASSERT_EQUAL(expect.uriStart, output.req.u_start);
+    CPPUNIT_ASSERT_EQUAL(expect.uriEnd, output.req.u_end);
+    if (expect.uri != NULL)
+        CPPUNIT_ASSERT_EQUAL(0, output.uri_.cmp(expect.uri));
+    CPPUNIT_ASSERT_EQUAL(expect.versionStart, output.req.v_start);
+    CPPUNIT_ASSERT_EQUAL(expect.versionEnd, output.req.v_end);
+    CPPUNIT_ASSERT_EQUAL(expect.version, output.msgProtocol_);
+}
+
 void
 testHttp1Parser::testParseRequestLineProtocols()
 {
     // ensure MemPools etc exist
     globalSetup();
 
-    MemBuf input;
+    SBuf input;
     Http1::RequestParser output;
-    input.init();
 
     // TEST: Do we comply with RFC 1945 section 5.1 ?
     // TEST: Do we comply with RFC 2616 section 5.1 ?
@@ -48,125 +93,132 @@ testHttp1Parser::testParseRequestLineProtocols()
     // RFC 1945 : HTTP/0.9 simple-request
     {
         input.append("GET /\r\n", 7);
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(true, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0,memcmp("GET /\r\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start], (output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start], (output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = true,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // RFC 1945 : invalid HTTP/0.9 simple-request (only GET is valid)
-#if 0
+#if WHEN_RFC_COMPLIANT
     {
         input.append("POST /\r\n", 7);
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(true, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0,memcmp("POST /\r\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(3, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("POST", &output.buf[output.req.m_start], (output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_POST), output.method_);
-        CPPUNIT_ASSERT_EQUAL(5, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(5, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start], (output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = true,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 3,
+            .method = HttpRequestMethod(Http::METHOD_POST),
+            .uriStart = 5,
+            .uriEnd = 5,
+            .uri = "/",
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 #endif
-
     // RFC 1945 and 2616 : HTTP/1.0 request
     {
         input.append("GET / HTTP/1.0\r\n", 16);
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/1.0\r\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(13, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.0", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,0), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 13,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,0)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // RFC 2616 : HTTP/1.1 request
     {
         input.append("GET / HTTP/1.1\r\n", 16);
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/1.1\r\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(13, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 13,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // RFC 2616 : future version full-request
     {
         input.append("GET / HTTP/1.2\r\n", 16);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/1.2\r\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(13, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.2", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,2), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 13,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,2)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // RFC 2616 : future version full-request
@@ -174,211 +226,212 @@ testHttp1Parser::testParseRequestLineProtocols()
         // IETF HTTPbis WG has made this two-digits format invalid.
         // it gets treated same as HTTP/0.9 for now
         input.append("GET / HTTP/10.12\r\n", 18);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scHttpVersionNotSupported, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/10.12\r\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(15, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/10.12", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,10,12), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scHttpVersionNotSupported,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 15,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,10,12)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
-    // This stage of the parser does not yet accept non-HTTP protocol names.
+    // unknown non-HTTP protocol names
     {
-        // violations mode treats them as HTTP/0.9 requests!
+        // XXX: violations mode treats them as HTTP/0.9 requests! which is wrong.
+#if !USE_HTTP_VIOLATIONS
         input.append("GET / FOO/1.0\n", 14);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-#if USE_HTTP_VIOLATIONS
-        CPPUNIT_ASSERT_EQUAL(true, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(12, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/ FOO/1.0", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9), output.msgProtocol_);
-#else
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scHttpVersionNotSupported, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scHttpVersionNotSupported,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 12,
+            .version = AnyP::ProtocolVersion()
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
 #endif
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / FOO/1.0\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(12, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("FOO/1.0", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        input.reset();
     }
 
     // no version
     {
         input.append("GET / HTTP/\n", 12);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scHttpVersionNotSupported, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(10, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,0), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scHttpVersionNotSupported,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 10,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,0)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // no major version
     {
         input.append("GET / HTTP/.1\n", 14);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scHttpVersionNotSupported, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(12, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,0), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scHttpVersionNotSupported,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 12,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,0)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // no version dot
     {
         input.append("GET / HTTP/11\n", 14);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scHttpVersionNotSupported, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/11\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(12, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/11", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,0), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scHttpVersionNotSupported,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 12,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,0)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // negative major version (bug 3062)
     {
         input.append("GET / HTTP/-999999.1\n", 21);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scHttpVersionNotSupported, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/-999999.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(19, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/-999999.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,0), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scHttpVersionNotSupported,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 19,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,0)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // no minor version
     {
         input.append("GET / HTTP/1.\n", 14);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scHttpVersionNotSupported, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/1.\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(12, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,0), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scHttpVersionNotSupported,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 12,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,0)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // negative major version (bug 3062 corollary)
     {
         input.append("GET / HTTP/1.-999999\n", 21);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scHttpVersionNotSupported, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/1.-999999\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(19, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.-999999", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,0), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scHttpVersionNotSupported,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 19,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,0)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 }
 
@@ -388,86 +441,86 @@ testHttp1Parser::testParseRequestLineStrange()
     // ensure MemPools etc exist
     globalSetup();
 
-    MemBuf input;
+    SBuf input;
     Http1::RequestParser output;
-    input.init();
 
     // space padded URL
     {
         input.append("GET  /     HTTP/1.1\r\n", 21);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET  /     HTTP/1.1\r\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(5, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(5, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(11, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(18, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 5,
+            .uriEnd = 5,
+            .uri = "/",
+            .versionStart = 11,
+            .versionEnd = 18,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // whitespace inside URI. (nasty but happens)
+    // XXX: depends on tolerant parser...
     {
         input.append("GET /fo o/ HTTP/1.1\n", 20);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0,memcmp("GET /fo o/ HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(9, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/fo o/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(11, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(18, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 9,
+            .uri = "/fo o/",
+            .versionStart = 11,
+            .versionEnd = 18,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // additional data in buffer
     {
         input.append("GET /     HTTP/1.1\nboo!", 23);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-5, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET /     HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end); // strangeness generated by following RFC
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(10, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(17, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-5,
+            .suffixSz = 4, // strlen("boo!")
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 10,
+            .versionEnd = 17,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 }
 
@@ -477,110 +530,110 @@ testHttp1Parser::testParseRequestLineTerminators()
     // ensure MemPools etc exist
     globalSetup();
 
-    MemBuf input;
+    SBuf input;
     Http1::RequestParser output;
-    input.init();
 
     // alternative EOL sequence: NL-only
     {
         input.append("GET / HTTP/1.1\n", 15);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(13, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 13,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // alternative EOL sequence: double-NL-only
     {
         input.append("GET / HTTP/1.1\n\n", 16);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(true, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-2, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(13, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = true,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-2,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 13,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
-    // RELAXED alternative EOL sequence: multi-CR-NL
+    // alternative EOL sequence: multi-CR-NL
     {
         input.append("GET / HTTP/1.1\r\r\r\n", 18);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        Config.onoff.relaxed_header_parser = 1;
         // Being tolerant we can ignore and elide these apparently benign CR
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/1.1\r\r\r\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(6, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(13, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
-        Config.onoff.relaxed_header_parser = 0;
-    }
+        Config.onoff.relaxed_header_parser = 1;
+        struct resultSet expectRelaxed = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 13,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expectRelaxed);
 
-    // STRICT alternative EOL sequence: multi-CR-NL
-    {
-        input.append("GET / HTTP/1.1\r\r\r\n", 18);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
         // strict mode treats these as several bare-CR in the request line which is explicitly invalid.
         Config.onoff.relaxed_header_parser = 0;
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scBadRequest, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
-        input.reset();
+        struct resultSet expectStrict = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = -1,
+            .suffixSz = input.length(),
+            .methodStart =-1,
+            .methodEnd = -1,
+            .method = HttpRequestMethod(),
+            .uriStart = -1,
+            .uriEnd = -1,
+            .uri = NULL,
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expectStrict);
+        input.clear();
     }
 
     // space padded version
@@ -588,104 +641,27 @@ testHttp1Parser::testParseRequestLineTerminators()
         // RFC 1945 and 2616 specify version is followed by CRLF. No intermediary bytes.
         // NP: the terminal whitespace is a special case: invalid for even HTTP/0.9 with no version tag
         input.append("GET / HTTP/1.1 \n", 16);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scBadRequest, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/1.1 \n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(13, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/ HTTP/1.1", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
-        input.reset();
-    }
-
-    // incomplete line at various positions
-    {
-        input.append("GET", 3);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_FIRST, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scNone, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(), output.method_);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
-        input.reset();
-
-        input.append("GET ", 4);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_FIRST, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scNone, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(), output.method_);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
-        input.reset();
-
-        input.append("GET / HT", 8);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_FIRST, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scNone, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(), output.method_);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
-        input.reset();
-
-        input.append("GET / HTTP/1.1", 14);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_FIRST, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scNone, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(), output.method_);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 13,
+            .uri = "/ HTTP/1.1",
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 }
 
@@ -695,207 +671,264 @@ testHttp1Parser::testParseRequestLineMethods()
     // ensure MemPools etc exist
     globalSetup();
 
-    MemBuf input;
+    SBuf input;
     Http1::RequestParser output;
-    input.init();
 
     // RFC 2616 : . method
     {
         input.append(". / HTTP/1.1\n", 13);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp(". / HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp(".", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(".", NULL), output.method_);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(4, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(11, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 0,
+            .method = HttpRequestMethod("."),
+            .uriStart = 2,
+            .uriEnd = 2,
+            .uri = "/",
+            .versionStart = 4,
+            .versionEnd = 11,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // OPTIONS with * URL
     {
         input.append("OPTIONS * HTTP/1.1\n", 19);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("OPTIONS * HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(6, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("OPTIONS", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_OPTIONS), output.method_);
-        CPPUNIT_ASSERT_EQUAL(8, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(8, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("*", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(10, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(17, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 6,
+            .method = HttpRequestMethod(Http::METHOD_OPTIONS),
+            .uriStart = 8,
+            .uriEnd = 8,
+            .uri = "*",
+            .versionStart = 10,
+            .versionEnd = 17,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // unknown method
     {
         input.append("HELLOWORLD / HTTP/1.1\n", 22);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HELLOWORLD / HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(9, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HELLOWORLD", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod("HELLOWORLD",NULL), output.method_);
-        CPPUNIT_ASSERT_EQUAL(11, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(11, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(13, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(20, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 9,
+            .method = HttpRequestMethod("HELLOWORLD"),
+            .uriStart = 11,
+            .uriEnd = 11,
+            .uri = "/",
+            .versionStart = 13,
+            .versionEnd = 20,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // method-only
     {
         input.append("A\n", 2);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scBadRequest, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("A\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(), output.method_);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = -1,
+            .method = HttpRequestMethod(),
+            .uriStart = -1,
+            .uriEnd = -1,
+            .uri = NULL,
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
-    input.append("GET\n", 4);
     {
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scBadRequest, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(), output.method_);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
-        input.reset();
+        input.append("GET\n", 4);
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = -1,
+            .method = HttpRequestMethod(),
+            .uriStart = -1,
+            .uriEnd = -1,
+            .uri = NULL,
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
-    // RELAXED space padded method (in strict mode SP is reserved so invalid as a method byte)
+    // space padded method (in strict mode SP is reserved so invalid as a method byte)
     {
         input.append(" GET / HTTP/1.1\n", 16);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
+        // RELAXED mode Squid custom tolerance ignores SP
+#if USE_HTTP_VIOLATIONS
         Config.onoff.relaxed_header_parser = 1;
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET / HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(1, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(3, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(5, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(5, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(7, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(14, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
+        struct resultSet expectRelaxed = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0, // garbage collection consumes the SP
+            .msgEnd = (int)input.length()-2,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 13,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expectRelaxed);
+#endif
+
+        // STRICT mode obeys RFC syntax
         Config.onoff.relaxed_header_parser = 0;
+        struct resultSet expectStrict = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = -1,
+            .method = HttpRequestMethod(),
+            .uriStart = -1,
+            .uriEnd = -1,
+            .uri = NULL,
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expectStrict);
+        input.clear();
     }
 
-    // STRICT space padded method (in strict mode SP is reserved so invalid as a method byte)
+    // RFC 2616 defined tolerance: ignore empty line(s) prefix on messages
+#if WHEN_RFC_COMPLIANT
     {
-        input.append(" GET / HTTP/1.1\n", 16);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        Config.onoff.relaxed_header_parser = 0;
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scBadRequest, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp(" GET / HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(), output.method_);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_NONE,0,0), output.msgProtocol_);
-        input.reset();
+        input.append("\r\n\r\n\nGET / HTTP/1.1\r\n", 21);
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 5,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 5,
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 4,
+            .uri = "/",
+            .versionStart = 6,
+            .versionEnd = 13,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
+#endif
 
     // tab padded method (NP: tab is not SP so treated as any other binary)
-    // XXX: binary codes are non-compliant
     {
         input.append("\tGET / HTTP/1.1\n", 16);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("\tGET / HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(3, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("\tGET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(&output.buf[output.req.m_start],&output.buf[output.req.m_end+1]), output.method_);
-        CPPUNIT_ASSERT_EQUAL(5, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(5, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(7, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(14, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
+#if WHEN_RFC_COMPLIANT
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = -1,
+            .methodEnd = -1,
+            .method = HttpRequestMethod(),
+            .uriStart = -1,
+            .uriEnd = -1,
+            .uri = NULL,
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+#else // XXX: currently broken
+        struct resultSet expect = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0, // garbage collection consumes the SP
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 3,
+            .method = HttpRequestMethod(SBuf("\tGET")),
+            .uriStart = 5,
+            .uriEnd = 5,
+            .uri = "/",
+            .versionStart = 7,
+            .versionEnd = 14,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+#endif
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 }
 
@@ -905,229 +938,307 @@ testHttp1Parser::testParseRequestLineInvalid()
     // ensure MemPools etc exist
     globalSetup();
 
-    MemBuf input;
+    SBuf input;
     Http1::RequestParser output;
-    input.init();
 
     // no method (but in a form which is ambiguous with HTTP/0.9 simple-request)
     {
-        // XXX: Bug: HTTP/0.9 requires method to be "GET"
+        // XXX: HTTP/0.9 requires method to be "GET"
         input.append("/ HTTP/1.0\n", 11);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(true, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/ HTTP/1.0\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod("/",NULL), output.method_);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(9, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.0", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = true,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 0,
+            .method = HttpRequestMethod("/"),
+            .uriStart = 2,
+            .uriEnd = 9,
+            .uri = "HTTP/1.0",
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
-    // RELAXED no method (an invalid format)
+    // no method (an invalid format)
     {
         input.append(" / HTTP/1.0\n", 12);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        // BUG: When tolerantly ignoring SP prefix this case becomes ambiguous with HTTP/0.9 simple-request)
-        Config.onoff.relaxed_header_parser = 1;
-        CPPUNIT_ASSERT_EQUAL(true, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-//        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_DONE, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/ HTTP/1.0\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(1, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod("/",NULL), output.method_);
-        CPPUNIT_ASSERT_EQUAL(3, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(10, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.0", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9), output.msgProtocol_);
-        input.reset();
-        Config.onoff.relaxed_header_parser = 0;
-    }
 
-    // STRICT no method (an invalid format)
-    {
-        input.append(" / HTTP/1.0\n", 12);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        // When tolerantly ignoring SP prefix this case becomes ambiguous with HTTP/0.9 simple-request)
+        // XXX: squid custom tolerance consumes initial SP.
+        Config.onoff.relaxed_header_parser = 1;
+        struct resultSet expectRelaxed = {
+            .parsed = true,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-2,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 0,
+            .method = HttpRequestMethod("/"),
+            .uriStart = 2,
+            .uriEnd = 9,
+            .uri = "HTTP/1.0",
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expectRelaxed);
+
+        // STRICT detect as invalid
         Config.onoff.relaxed_header_parser = 0;
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scBadRequest, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp(" / HTTP/1.0\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(), output.method_);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_NONE,0,0), output.msgProtocol_);
-        input.reset();
+#if WHEN_RFC_COMPLIANT
+        // XXX: except Squid does not
+        struct resultSet expectStrict = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = -1,
+            .method = HttpRequestMethod(),
+            .uriStart = -1,
+            .uriEnd = -1,
+            .uri = NULL,
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+#else
+        struct resultSet expectStrict = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = -1,
+            .method = HttpRequestMethod(),
+            .uriStart = -1,
+            .uriEnd = -1,
+            .uri = NULL,
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+#endif
+        output.reset(input);
+        testResults(__LINE__, input, output, expectStrict);
+        input.clear();
     }
 
-    // binary code in method (strange but ...)
+    // binary code in method (invalid)
     {
         input.append("GET\x0B / HTTP/1.1\n", 16);
-        //printf("TEST: %d-%d/%d '%.*s'\n", output.req.start, output.req.end, input.contentSize(), 16, input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET\x0B / HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(3, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET\x0B", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-//        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod("GET\0x0B",NULL), output.method_);
-        CPPUNIT_ASSERT_EQUAL(5, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(5, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(7, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(14, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
+#if WHEN_RFC_COMPLIANT
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = -1,
+            .methodEnd = -1,
+            .method = HttpRequestMethod(),
+            .uriStart = -1,
+            .uriEnd = -1,
+            .uri = NULL,
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+#else
+        struct resultSet expect = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0, // garbage collection consumes the SP
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 3,
+            .method = HttpRequestMethod(SBuf("GET\x0B")),
+            .uriStart = 5,
+            .uriEnd = 5,
+            .uri = "/",
+            .versionStart = 7,
+            .versionEnd = 14,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+#endif
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // CR in method
     {
         // RFC 2616 sec 5.1 prohibits CR other than in terminator.
         input.append("GET\r / HTTP/1.1\r\n", 16);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scBadRequest, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(), output.method_);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = -1, // halt at the first \r
+            .suffixSz = input.length(),
+            .methodStart = -1,
+            .methodEnd = -1,
+            .method = HttpRequestMethod(),
+            .uriStart = -1,
+            .uriEnd = -1,
+            .uri = NULL,
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // binary code NUL! in method (strange but ...)
     {
         input.append("GET\0 / HTTP/1.1\n", 16);
-        //printf("TEST: %d-%d/%d '%.*s'\n", output.req.start, output.req.end, input.contentSize(), 16, input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(false, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET\0 / HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(3, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET\0", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-//        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod("GET\0",NULL), output.method_);
-        CPPUNIT_ASSERT_EQUAL(5, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(5, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("/", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(7, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(14, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.v_start],(output.req.v_end-output.req.v_start+1)));
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1), output.msgProtocol_);
-        input.reset();
+#if WHEN_RFC_COMPLIANT
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = -1, // halt at the \0
+            .suffixSz = input.length(),
+            .methodStart = -1,
+            .methodEnd = -1,
+            .method = HttpRequestMethod(),
+            .uriStart = -1,
+            .uriEnd = -1,
+            .uri = NULL,
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+#else
+        struct resultSet expect = {
+            .parsed = false,
+            .done = false,
+            .parserState = Http1::HTTP_PARSE_MIME,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 3,
+            .method = HttpRequestMethod(SBuf("GET\0",4)),
+            .uriStart = 5,
+            .uriEnd = 5,
+            .uri = "/",
+            .versionStart = 7,
+            .versionEnd = 14,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+#endif
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
-    // no URL (grammer otherwise correct)
+    // no URL (grammer invalid, ambiguous with RFC 1945 HTTP/0.9 simple-request)
     {
         input.append("GET  HTTP/1.1\n", 14);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(true, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET  HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(5, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(12, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = true,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 5,
+            .uriEnd = 12,
+            .uri = "HTTP/1.1",
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // no URL (grammer invalid, ambiguous with RFC 1945 HTTP/0.9 simple-request)
     {
         input.append("GET HTTP/1.1\n", 13);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(true, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scOkay, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET HTTP/1.1\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("GET", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_GET), output.method_);
-        CPPUNIT_ASSERT_EQUAL(4, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(11, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("HTTP/1.1", &output.buf[output.req.u_start],(output.req.u_end-output.req.u_start+1)));
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = true,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scOkay,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = 0,
+            .methodStart = 0,
+            .methodEnd = 2,
+            .method = HttpRequestMethod(Http::METHOD_GET),
+            .uriStart = 4,
+            .uriEnd = 11,
+            .uri = "HTTP/1.1",
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9)
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // binary line
     {
         input.append("\xB\xC\xE\xF\n", 5);
-        //printf("TEST: binary-line\n");
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scBadRequest, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("\xB\xC\xE\xF\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(), output.method_);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = -1,
+            .method = HttpRequestMethod(),
+            .uriStart = -1,
+            .uriEnd = -1,
+            .uri = NULL,
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // mixed whitespace line
@@ -1135,24 +1246,27 @@ testHttp1Parser::testParseRequestLineInvalid()
         // We accept non-space binary bytes for method so first \t shows up as that
         // but remaining space and tabs are skipped searching for URI-start
         input.append("\t \t \t\n", 6);
-        //printf("TEST: mixed whitespace\n");
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scBadRequest, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL((int)input.contentSize()-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("\t \t \t\n", &output.buf[output.req.start],(output.req.end-output.req.start+1)));
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(0, memcmp("\t", &output.buf[output.req.m_start],(output.req.m_end-output.req.m_start+1)));
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(&output.buf[output.req.m_start],&output.buf[output.req.m_end+1]), output.method_);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = (int)input.length()-1,
+            .suffixSz = input.length(),
+            .methodStart = 0,
+            .methodEnd = 0,
+            .method = HttpRequestMethod(SBuf("\t")),
+            .uriStart = -1,
+            .uriEnd = -1,
+            .uri = NULL,
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // mixed whitespace line with CR middle
@@ -1160,22 +1274,27 @@ testHttp1Parser::testParseRequestLineInvalid()
         // CR aborts on sight, so even initial \t method is not marked as above
         // (not when parsing clean with whole line available anyway)
         input.append("\t  \r \n", 6);
-        //printf("TEST: mixed whitespace with CR\n");
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(false, output.parse());
-        CPPUNIT_ASSERT_EQUAL(true, output.isDone());
-        CPPUNIT_ASSERT_EQUAL(Http::scBadRequest, output.request_parse_status);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
-        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(), output.method_);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
-        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
-        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .done = true,
+            .parserState = Http1::HTTP_PARSE_DONE,
+            .status = Http::scBadRequest,
+            .msgStart = 0,
+            .msgEnd = -1, // halt on the \r
+            .suffixSz = input.length(),
+            .methodStart = -1,
+            .methodEnd = -1,
+            .method = HttpRequestMethod(),
+            .uriStart = -1,
+            .uriEnd = -1,
+            .uri = NULL,
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion()
+        };
+        output.reset(input);
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 }
 
@@ -1186,60 +1305,94 @@ testHttp1Parser::testDripFeed()
     // extend the size of the buffer from 0 bytes to full request length
     // calling the parser repeatedly as visible data grows.
 
-    MemBuf mb;
-    mb.init(1024, 1024);
-    mb.append("            ", 12);
-    int garbageEnd = mb.contentSize();
-    mb.append("GET http://example.com/ HTTP/1.1\r\n", 34);
-    int reqLineEnd = mb.contentSize();
-    mb.append("Host: example.com\r\n\r\n", 21);
-    int mimeEnd = mb.contentSize();
-    mb.append("...", 3); // trailer to catch mime EOS errors.
+    SBuf data;
+    data.append("            ", 12);
+    SBuf::size_type garbageEnd = data.length();
+    data.append("GET http://example.com/ HTTP/1.1\r\n", 34);
+    SBuf::size_type reqLineEnd = data.length() - 1;
+    data.append("Host: example.com\r\n\r\n", 21);
+    SBuf::size_type mimeEnd = data.length() - 1;
+    data.append("...", 3); // trailer to catch mime EOS errors.
 
-    Http1::RequestParser hp(mb.content(), 0);
+    SBuf ioBuf; // begins empty
+    Http1::RequestParser hp(ioBuf);
 
     // only relaxed parser accepts the garbage whitespace
     Config.onoff.relaxed_header_parser = 1;
 
-    for (; hp.bufsiz <= mb.contentSize(); ++hp.bufsiz) {
-        bool parseResult = hp.parse();
-
-#if WHEN_TEST_DEBUG_IS_NEEDED
-        printf("%d/%d :: %d, %d, %d '%c'\n", hp.bufsiz, mb.contentSize(),
-               garbageEnd, reqLineEnd, parseResult,
-               mb.content()[hp.bufsiz]);
-#endif
-
-       // before end of garbage found its a moving offset.
-       if (hp.bufsiz <= garbageEnd) {
-            CPPUNIT_ASSERT_EQUAL(hp.bufsiz, (int)hp.parseOffset_);
-            CPPUNIT_ASSERT_EQUAL(false, hp.isDone());
-            CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_NEW, hp.parsingStage_);
-            continue;
+    // state of things we expect right now
+    struct resultSet expect = {
+        .parsed = false,
+        .done = false,
+        .parserState = Http1::HTTP_PARSE_NEW,
+        .status = Http::scBadRequest,
+        .msgStart = 0,
+        .msgEnd = -1,
+        .suffixSz = 0,
+        .methodStart = -1,
+        .methodEnd = -1,
+        .method = HttpRequestMethod(),
+        .uriStart = -1,
+        .uriEnd = -1,
+        .uri = NULL,
+        .versionStart = -1,
+        .versionEnd = -1,
+        .version = AnyP::ProtocolVersion()
+    };
+
+    Config.maxRequestHeaderSize = 1024; // large enough to hold the test data.
+
+    for (SBuf::size_type pos = 0; pos <= data.length(); ++pos) {
+
+        // simulate reading one more byte
+        ioBuf.append(data.substr(pos,1));
+
+        // sync the buffers like Squid does
+        hp.buf = ioBuf;
+
+        // when the garbage is passed we expect to start seeing first-line bytes
+        if (pos == garbageEnd) {
+            expect.parserState = Http1::HTTP_PARSE_FIRST;
+            expect.msgStart = 0;
         }
 
-       // before request line found, parse announces incomplete
-        if (hp.bufsiz < reqLineEnd) {
-            CPPUNIT_ASSERT_EQUAL(garbageEnd, (int)hp.parseOffset_);
-            CPPUNIT_ASSERT_EQUAL(false, parseResult);
-            CPPUNIT_ASSERT_EQUAL(false, hp.isDone());
-            CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_FIRST, hp.parsingStage_);
-            continue;
+        // all points after garbage start to see accumulated bytes looking for end of current section
+        if (pos >= garbageEnd)
+            expect.suffixSz = ioBuf.length();
+
+        // at end of request line expect to see method, URI, version details
+        // and switch to seeking Mime header section
+        if (pos == reqLineEnd) {
+            expect.parserState = Http1::HTTP_PARSE_MIME;
+            expect.suffixSz = 0;
+            expect.msgEnd = reqLineEnd-garbageEnd;
+            expect.status = Http::scOkay;
+            expect.methodStart = 0;
+            expect.methodEnd = 2;
+            expect.method = HttpRequestMethod(Http::METHOD_GET);
+            expect.uriStart = 4;
+            expect.uriEnd = 22;
+            expect.uri = "http://example.com/";
+            expect.versionStart = 24;
+            expect.versionEnd = 31;
+            expect.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1);
         }
 
-       // before request headers entirely found, parse announces incomplete
-        if (hp.bufsiz < mimeEnd) {
-            CPPUNIT_ASSERT_EQUAL(reqLineEnd, (int)hp.parseOffset_);
-            CPPUNIT_ASSERT_EQUAL(false, parseResult);
-            CPPUNIT_ASSERT_EQUAL(false, hp.isDone());
-            // TODO: add all the other usual tests for request-line details
-            CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_MIME, hp.parsingStage_);
-            continue;
+        // one mime header is done we are expectign a new request
+        // parse results say true and initial data is all gone from the buffer
+        if (pos == mimeEnd) {
+            expect.parsed = true;
+            expect.done = true;
+            expect.suffixSz = 0;
         }
 
-        // once request line is found (AND the following \n) current parser announces success
-        CPPUNIT_ASSERT_EQUAL(mimeEnd, (int)hp.parseOffset_);
-        CPPUNIT_ASSERT_EQUAL(true, parseResult);
-        CPPUNIT_ASSERT_EQUAL(true, hp.isDone());
+        testResults(__LINE__, ioBuf, hp, expect);
+
+        // sync the buffers like Squid does
+        ioBuf = hp.buf;
+
+        // Squid stops using the parser once it reports done.
+        if (hp.isDone())
+            break;
     }
 }
index 10d42b3f8b13b7512d32ff8e7ddcb51021241f35..eb7c87549a3d1d731f5fa4cc083ce1a1a7210d20 100644 (file)
@@ -19,10 +19,10 @@ void
 testHttpRequestMethod::testConstructCharStart()
 {
     /* parse an empty string -> Http::METHOD_NONE */
-    CPPUNIT_ASSERT(HttpRequestMethod(NULL,NULL) == Http::METHOD_NONE);
+    CPPUNIT_ASSERT(HttpRequestMethod(NULL) == Http::METHOD_NONE);
     /* parsing a literal should work */
-    CPPUNIT_ASSERT(HttpRequestMethod("GET", NULL) == Http::METHOD_GET);
-    CPPUNIT_ASSERT(HttpRequestMethod("QWERTY", NULL) == Http::METHOD_OTHER);
+    CPPUNIT_ASSERT(HttpRequestMethod("GET") == Http::METHOD_GET);
+    CPPUNIT_ASSERT(HttpRequestMethod("QWERTY") == Http::METHOD_OTHER);
 }
 
 /*
@@ -33,12 +33,12 @@ testHttpRequestMethod::testConstructCharStartEnd()
 {
     char const * buffer;
     /* parse an empty string -> Http::METHOD_NONE */
-    CPPUNIT_ASSERT(HttpRequestMethod(NULL, NULL) == Http::METHOD_NONE);
+    CPPUNIT_ASSERT(HttpRequestMethod(NULL) == Http::METHOD_NONE);
     /* parsing a literal should work */
-    CPPUNIT_ASSERT(HttpRequestMethod("GET", NULL) == Http::METHOD_GET);
+    CPPUNIT_ASSERT(HttpRequestMethod("GET") == Http::METHOD_GET);
     /* parsing with an explicit end should work */
     buffer = "POSTPLUS";
-    CPPUNIT_ASSERT(HttpRequestMethod(buffer, buffer + 4) == Http::METHOD_POST);
+    CPPUNIT_ASSERT(HttpRequestMethod(SBuf(buffer, 4)) == Http::METHOD_POST);
 }
 
 /*
@@ -84,15 +84,15 @@ testHttpRequestMethod::testImage()
 {
     // relaxed RFC-compliance parse HTTP methods are upgraded to correct case
     Config.onoff.relaxed_header_parser = 1;
-    CPPUNIT_ASSERT_EQUAL(SBuf("POST"), HttpRequestMethod("POST",NULL).image());
-    CPPUNIT_ASSERT_EQUAL(SBuf("POST"), HttpRequestMethod("pOsT",NULL).image());
-    CPPUNIT_ASSERT_EQUAL(SBuf("POST"), HttpRequestMethod("post",NULL).image());
+    CPPUNIT_ASSERT_EQUAL(SBuf("POST"), HttpRequestMethod("POST").image());
+    CPPUNIT_ASSERT_EQUAL(SBuf("POST"), HttpRequestMethod("pOsT").image());
+    CPPUNIT_ASSERT_EQUAL(SBuf("POST"), HttpRequestMethod("post").image());
 
     // strict RFC-compliance parse HTTP methods are case sensitive
     Config.onoff.relaxed_header_parser = 0;
-    CPPUNIT_ASSERT_EQUAL(SBuf("POST"), HttpRequestMethod("POST",NULL).image());
-    CPPUNIT_ASSERT_EQUAL(SBuf("pOsT"), HttpRequestMethod("pOsT",NULL).image());
-    CPPUNIT_ASSERT_EQUAL(SBuf("post"), HttpRequestMethod("post",NULL).image());
+    CPPUNIT_ASSERT_EQUAL(SBuf("POST"), HttpRequestMethod("POST").image());
+    CPPUNIT_ASSERT_EQUAL(SBuf("pOsT"), HttpRequestMethod("pOsT").image());
+    CPPUNIT_ASSERT_EQUAL(SBuf("post"), HttpRequestMethod("post").image());
 }
 
 /*
@@ -129,12 +129,12 @@ testHttpRequestMethod::testStream()
     // relaxed RFC-compliance parse HTTP methods are upgraded to correct case
     Config.onoff.relaxed_header_parser = 1;
     std::ostringstream buffer;
-    buffer << HttpRequestMethod("get", NULL);
+    buffer << HttpRequestMethod("get");
     CPPUNIT_ASSERT_EQUAL(String("GET"), String(buffer.str().c_str()));
 
     // strict RFC-compliance parse HTTP methods are case sensitive
     Config.onoff.relaxed_header_parser = 0;
     std::ostringstream buffer2;
-    buffer2 << HttpRequestMethod("get", NULL);
+    buffer2 << HttpRequestMethod("get");
     CPPUNIT_ASSERT_EQUAL(String("get"), String(buffer2.str().c_str()));
 }