[squid-dev] [PATCH] HTTP Parser upgrade

Amos Jeffries squid3 at treenet.co.nz
Sat Oct 11 20:23:03 UTC 2014


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Attached the patch this time :-)

On 12/10/2014 9:17 a.m., Amos Jeffries wrote:
> Minor chages since the last patch.
> 
> * updates to cope with trunk changes.
> 
> * some RFC 7230 compliance polish
> 
> Amos
> 
> On 7/06/2014 4:55 a.m., Amos Jeffries wrote:
>> Latest version of the Parser-NG project branch. Since last
>> message I have dropped the copy-constructor related pieces.
> 
>> Polygraph testing on the branch showed almost no performance 
>> impact (+0.04ms/req, -0.03% request rate). Nevertheless a half 
>> dozen points of added memory reallocate or data copy are marked
>> for future removal as the Parser-NG and SBuf improvements
>> continue.
> 
>> Coadvisor testing of these changes shows a gain of 3 compliance 
>> cases for HTTPbis tests and no unexpected regressions against
>> RFC 2616 (one HTTPbis gain is claimed as a RFC2616 regression by 
>> coadvisor still).
> 
> 
>> To recap, this project so far:
> 
> 
>> Rename the HttpParser class as Http1::RequestParser and move it 
>> into the Http::One:: namespace. A base Http1::Parser class is
>> also added from which Http1::ReplyParser will also be created in
>> future work.
> 
>> The parser API - is updated to process both the request-line and 
>> HTTP mime headers, returning a incomplete parse result until the 
>> entire header portion of the message has been received. - now 
>> contains accessor methods for retrieving the method, URI,
>> protocol, mime headers block as separate SBuf and some metrics
>> about those. - the old request_offsets structure and similar
>> offset details internal to the parsing are no longer exposed.
> 
>> The parser is now incremental. A parser object must be created
>> (or clean() method called) for each new message. Code using the
>> parser should pass their I/O buffer to the parse(SBuf&) method
>> and retrieve an updated copy of it afterwards via the
>> remaining() method.
> 
>> Much of the code from client_side.cc parseHttpRequest() and also 
>> the header-field code from mime_headers.cc has been moved into
>> the parser classes. The client-side code now simply runs the
>> main parse() method then uses its accessors to retrieve and
>> process the parse results if it returns success.
> 
>> The getHeaderField() method imported from mime_headers.cc has
>> been converted to simpler logics based on Tokenizer class and
>> resolving a number of bugs in its matching output.
> 
>> A unit test for incremental parsing has been added to 
>> testHttp1Parser.
> 
>> The HttpRequestMethod class is moved into the Http namespace and
>>  library to reduce dependencies on the parser class outside the 
>> library.
> 
>> The HttpHeader API has been tweaked slighty to accept
>> start+length pair instead of start/end pointers.
> 
>> Amos
> 
> 
> _______________________________________________ squid-dev mailing
> list squid-dev at lists.squid-cache.org 
> http://lists.squid-cache.org/listinfo/squid-dev
> 

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.22 (MingW32)

iQEcBAEBAgAGBQJUOZGmAAoJELJo5wb/XPRjQD0H/Rgck1ImvreTboLkfIeaalog
di8nnuma4SrYl6DbuO9JoILmXYH/oiYDvDGsQ1V/cf7GT4OyUK3XQan3jamhDpIG
eBuCbbFSId7ngM9nzkdG+CwwqDxOpXm+TxUpeIpRihqh9QbmQLaYfk8zm6nCYLEO
dqUADk6Hp1rJ5L2G56VXZjHJXcWM9d8z8kwacOjwqXgV+rdcYdTI6HLrTksqRzhd
Wh9Wp8JVYi3Vxkb1rLc1LwfhFwUw5cFr+SYzmGCPijbXVsHuvHowO1p0Hs43+cjM
vbTNRsYPQ8i0CeC4/KZOm6P0A70Qu1Lh3eD/r6w7HOFikAN8DN2xEmcFKdefNSU=
=LSva
-----END PGP SIGNATURE-----
-------------- next part --------------
=== modified file 'configure.ac'
--- configure.ac	2014-10-02 12:07:26 +0000
+++ configure.ac	2014-10-05 10:43:33 +0000
@@ -3766,40 +3766,41 @@
 	src/base/Makefile
 	src/acl/Makefile
 	src/clients/Makefile
 	src/servers/Makefile
 	src/fs/Makefile
 	src/repl/Makefile
 	src/auth/Makefile
 	src/auth/basic/Makefile
 	src/auth/digest/Makefile
 	src/auth/negotiate/Makefile
 	src/auth/ntlm/Makefile
 	src/adaptation/Makefile
 	src/adaptation/icap/Makefile
 	src/adaptation/ecap/Makefile
 	src/comm/Makefile
 	src/esi/Makefile
 	src/eui/Makefile
 	src/format/Makefile
 	src/helper/Makefile
 	src/http/Makefile
+	src/http/one/Makefile
 	src/icmp/Makefile
 	src/ident/Makefile
 	src/ip/Makefile
 	src/log/Makefile
 	src/ipc/Makefile
 	src/ssl/Makefile
 	src/mgr/Makefile
 	src/parser/Makefile
 	src/snmp/Makefile
 	contrib/Makefile
 	icons/Makefile
 	errors/Makefile
 	test-suite/Makefile
 	doc/Makefile
 	doc/manuals/Makefile
 	helpers/Makefile
 	helpers/basic_auth/Makefile
 	helpers/basic_auth/DB/Makefile
 	helpers/basic_auth/fake/Makefile
 	helpers/basic_auth/getpwnam/Makefile

=== modified file 'src/AccessLogEntry.h'
--- src/AccessLogEntry.h	2014-09-29 05:13:17 +0000
+++ src/AccessLogEntry.h	2014-10-05 10:43:33 +0000
@@ -1,38 +1,38 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #ifndef SQUID_HTTPACCESSLOGENTRY_H
 #define SQUID_HTTPACCESSLOGENTRY_H
 
 #include "anyp/PortCfg.h"
 #include "base/RefCount.h"
 #include "comm/Connection.h"
 #include "HierarchyLogEntry.h"
 #include "http/ProtocolVersion.h"
 #include "HttpHeader.h"
-#include "HttpRequestMethod.h"
+#include "http/RequestMethod.h"
 #include "icp_opcode.h"
 #include "ip/Address.h"
 #include "LogTags.h"
 #include "MessageSizes.h"
 #include "Notes.h"
 #if ICAP_CLIENT
 #include "adaptation/icap/Elements.h"
 #endif
 #if USE_OPENSSL
 #include "ssl/gadgets.h"
 #endif
 
 /* forward decls */
 class HttpReply;
 class HttpRequest;
 class CustomLog;
 
 class AccessLogEntry: public RefCountable
 {
 

=== modified file 'src/HttpHeader.cc'
--- src/HttpHeader.cc	2014-09-13 13:59:43 +0000
+++ src/HttpHeader.cc	2014-09-14 12:23:03 +0000
@@ -564,188 +564,189 @@
         /* deny bad guys (ok to check for HDR_OTHER) here */
 
         if (denied_mask && CBIT_TEST(*denied_mask, e->id))
             continue;
 
         debugs(55, 7, "Updating header '" << HeadersAttrs[e->id].name << "' in cached entry");
 
         addEntry(e->clone());
     }
 }
 
 /* just handy in parsing: resets and returns false */
 int
 HttpHeader::reset()
 {
     clean();
     return 0;
 }
 
 int
-HttpHeader::parse(const char *header_start, const char *header_end)
+HttpHeader::parse(const char *header_start, size_t hdrLen)
 {
     const char *field_ptr = header_start;
+    const char *header_end = header_start + hdrLen; // XXX: remove
     HttpHeaderEntry *e, *e2;
     int warnOnError = (Config.onoff.relaxed_header_parser <= 0 ? DBG_IMPORTANT : 2);
 
     PROF_start(HttpHeaderParse);
 
     assert(header_start && header_end);
-    debugs(55, 7, "parsing hdr: (" << this << ")" << std::endl << getStringPrefix(header_start, header_end));
+    debugs(55, 7, "parsing hdr: (" << this << ")" << std::endl << getStringPrefix(header_start, hdrLen));
     ++ HttpHeaderStats[owner].parsedCount;
 
     char *nulpos;
-    if ((nulpos = (char*)memchr(header_start, '\0', header_end - header_start))) {
+    if ((nulpos = (char*)memchr(header_start, '\0', hdrLen))) {
         debugs(55, DBG_IMPORTANT, "WARNING: HTTP header contains NULL characters {" <<
-               getStringPrefix(header_start, nulpos) << "}\nNULL\n{" << getStringPrefix(nulpos+1, header_end));
+               getStringPrefix(header_start, nulpos-header_start) << "}\nNULL\n{" << getStringPrefix(nulpos+1, hdrLen-(nulpos-header_start)-1));
         PROF_stop(HttpHeaderParse);
         return reset();
     }
 
     /* common format headers are "<name>:[ws]<value>" lines delimited by <CRLF>.
      * continuation lines start with a (single) space or tab */
     while (field_ptr < header_end) {
         const char *field_start = field_ptr;
         const char *field_end;
 
         do {
             const char *this_line = field_ptr;
             field_ptr = (const char *)memchr(field_ptr, '\n', header_end - field_ptr);
 
             if (!field_ptr) {
                 // missing <LF>
                 PROF_stop(HttpHeaderParse);
                 return reset();
             }
 
             field_end = field_ptr;
 
             ++field_ptr;	/* Move to next line */
 
             if (field_end > this_line && field_end[-1] == '\r') {
                 --field_end;	/* Ignore CR LF */
 
                 if (owner == hoRequest && field_end > this_line) {
                     bool cr_only = true;
                     for (const char *p = this_line; p < field_end && cr_only; ++p) {
                         if (*p != '\r')
                             cr_only = false;
                     }
                     if (cr_only) {
                         debugs(55, DBG_IMPORTANT, "SECURITY WARNING: Rejecting HTTP request with a CR+ "
                                "header field to prevent request smuggling attacks: {" <<
-                               getStringPrefix(header_start, header_end) << "}");
+                               getStringPrefix(header_start, hdrLen) << "}");
                         PROF_stop(HttpHeaderParse);
                         return reset();
                     }
                 }
             }
 
             /* Barf on stray CR characters */
             if (memchr(this_line, '\r', field_end - this_line)) {
                 debugs(55, warnOnError, "WARNING: suspicious CR characters in HTTP header {" <<
-                       getStringPrefix(field_start, field_end) << "}");
+                       getStringPrefix(field_start, field_end-field_start) << "}");
 
                 if (Config.onoff.relaxed_header_parser) {
                     char *p = (char *) this_line;	/* XXX Warning! This destroys original header content and violates specifications somewhat */
 
                     while ((p = (char *)memchr(p, '\r', field_end - p)) != NULL) {
                         *p = ' ';
                         ++p;
                     }
                 } else {
                     PROF_stop(HttpHeaderParse);
                     return reset();
                 }
             }
 
             if (this_line + 1 == field_end && this_line > field_start) {
                 debugs(55, warnOnError, "WARNING: Blank continuation line in HTTP header {" <<
-                       getStringPrefix(header_start, header_end) << "}");
+                       getStringPrefix(header_start, hdrLen) << "}");
                 PROF_stop(HttpHeaderParse);
                 return reset();
             }
         } while (field_ptr < header_end && (*field_ptr == ' ' || *field_ptr == '\t'));
 
         if (field_start == field_end) {
             if (field_ptr < header_end) {
                 debugs(55, warnOnError, "WARNING: unparseable HTTP header field near {" <<
-                       getStringPrefix(field_start, header_end) << "}");
+                       getStringPrefix(field_start, hdrLen-(field_start-header_start)) << "}");
                 PROF_stop(HttpHeaderParse);
                 return reset();
             }
 
             break;		/* terminating blank line */
         }
 
         if ((e = HttpHeaderEntry::parse(field_start, field_end)) == NULL) {
             debugs(55, warnOnError, "WARNING: unparseable HTTP header field {" <<
-                   getStringPrefix(field_start, field_end) << "}");
-            debugs(55, warnOnError, " in {" << getStringPrefix(header_start, header_end) << "}");
+                   getStringPrefix(field_start, field_end-field_start) << "}");
+            debugs(55, warnOnError, " in {" << getStringPrefix(header_start, hdrLen) << "}");
 
             if (Config.onoff.relaxed_header_parser)
                 continue;
 
             PROF_stop(HttpHeaderParse);
             return reset();
         }
 
         if (e->id == HDR_CONTENT_LENGTH && (e2 = findEntry(e->id)) != NULL) {
             if (e->value != e2->value) {
                 int64_t l1, l2;
                 debugs(55, warnOnError, "WARNING: found two conflicting content-length headers in {" <<
-                       getStringPrefix(header_start, header_end) << "}");
+                       getStringPrefix(header_start, hdrLen) << "}");
 
                 if (!Config.onoff.relaxed_header_parser) {
                     delete e;
                     PROF_stop(HttpHeaderParse);
                     return reset();
                 }
 
                 if (!httpHeaderParseOffset(e->value.termedBuf(), &l1)) {
                     debugs(55, DBG_IMPORTANT, "WARNING: Unparseable content-length '" << e->value << "'");
                     delete e;
                     continue;
                 } else if (!httpHeaderParseOffset(e2->value.termedBuf(), &l2)) {
                     debugs(55, DBG_IMPORTANT, "WARNING: Unparseable content-length '" << e2->value << "'");
                     delById(e2->id);
                 } else if (l1 > l2) {
                     delById(e2->id);
                 } else {
                     delete e;
                     continue;
                 }
             } else {
                 debugs(55, warnOnError, "NOTICE: found double content-length header");
                 delete e;
 
                 if (Config.onoff.relaxed_header_parser)
                     continue;
 
                 PROF_stop(HttpHeaderParse);
                 return reset();
             }
         }
 
         if (e->id == HDR_OTHER && stringHasWhitespace(e->name.termedBuf())) {
             debugs(55, warnOnError, "WARNING: found whitespace in HTTP header name {" <<
-                   getStringPrefix(field_start, field_end) << "}");
+                   getStringPrefix(field_start, field_end-field_start) << "}");
 
             if (!Config.onoff.relaxed_header_parser) {
                 delete e;
                 PROF_stop(HttpHeaderParse);
                 return reset();
             }
         }
 
         addEntry(e);
     }
 
     if (chunked()) {
         // RFC 2616 section 4.4: ignore Content-Length with Transfer-Encoding
         delById(HDR_CONTENT_LENGTH);
     }
 
     PROF_stop(HttpHeaderParse);
     return 1;			/* even if no fields where found, it is a valid header */
 }
 
@@ -1614,52 +1615,52 @@
     const char *name_end = (const char *)memchr(field_start, ':', field_end - field_start);
     int name_len = name_end ? name_end - field_start :0;
     const char *value_start = field_start + name_len + 1;	/* skip ':' */
     /* note: value_end == field_end */
 
     ++ HeaderEntryParsedCount;
 
     /* do we have a valid field name within this field? */
 
     if (!name_len || name_end > field_end)
         return NULL;
 
     if (name_len > 65534) {
         /* String must be LESS THAN 64K and it adds a terminating NULL */
         debugs(55, DBG_IMPORTANT, "WARNING: ignoring header name of " << name_len << " bytes");
         return NULL;
     }
 
     if (Config.onoff.relaxed_header_parser && xisspace(field_start[name_len - 1])) {
         debugs(55, Config.onoff.relaxed_header_parser <= 0 ? 1 : 2,
-               "NOTICE: Whitespace after header name in '" << getStringPrefix(field_start, field_end) << "'");
+               "NOTICE: Whitespace after header name in '" << getStringPrefix(field_start, field_end-field_start) << "'");
 
         while (name_len > 0 && xisspace(field_start[name_len - 1]))
             --name_len;
 
         if (!name_len)
             return NULL;
     }
 
     /* now we know we can parse it */
 
-    debugs(55, 9, "parsing HttpHeaderEntry: near '" <<  getStringPrefix(field_start, field_end) << "'");
+    debugs(55, 9, "parsing HttpHeaderEntry: near '" <<  getStringPrefix(field_start, field_end-field_start) << "'");
 
     /* is it a "known" field? */
     http_hdr_type id = httpHeaderIdByName(field_start, name_len, Headers, HDR_ENUM_END);
 
     String name;
 
     String value;
 
     if (id < 0)
         id = HDR_OTHER;
 
     assert_eid(id);
 
     /* set field name */
     if (id == HDR_OTHER)
         name.limitInit(field_start, name_len);
     else
         name = Headers[id].name;
 
     /* trim field value */

=== modified file 'src/HttpHeader.h'
--- src/HttpHeader.h	2014-09-13 13:59:43 +0000
+++ src/HttpHeader.h	2014-09-14 12:23:03 +0000
@@ -1,158 +1,48 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #ifndef SQUID_HTTPHEADER_H
 #define SQUID_HTTPHEADER_H
 
+#include "http/RegisteredHeaders.h"
 /* because we pass a spec by value */
 #include "HttpHeaderMask.h"
 #include "MemPool.h"
 #include "SquidString.h"
 
 #include <vector>
 
 /* class forward declarations */
 class HttpHdrCc;
 class HttpHdrContRange;
 class HttpHdrRange;
 class HttpHdrSc;
 class Packer;
 class StoreEntry;
 class SBuf;
 
-/* constant attributes of http header fields */
-
-/// recognized or "known" header fields; and the RFC which defines them (or not)
-/// http://www.iana.org/assignments/message-headers/message-headers.xhtml
-typedef enum {
-    HDR_BAD_HDR = -1,
-    HDR_ACCEPT = 0,                     /**< RFC 7231 */
-    HDR_ACCEPT_CHARSET,                 /**< RFC 7231 */
-    HDR_ACCEPT_ENCODING,                /**< RFC 7231 */
-    /*HDR_ACCEPT_FEATURES,*/            /* RFC 2295 */
-    HDR_ACCEPT_LANGUAGE,                /**< RFC 7231 */
-    HDR_ACCEPT_RANGES,                  /**< RFC 7233 */
-    HDR_AGE,                            /**< RFC 7234 */
-    HDR_ALLOW,                          /**< RFC 7231 */
-    HDR_AUTHENTICATION_INFO,            /**< RFC 2617 */
-    HDR_AUTHORIZATION,                  /**< RFC 7235, 4559 */
-    HDR_CACHE_CONTROL,                  /**< RFC 7234 */
-    HDR_CONNECTION,                     /**< RFC 7230 */
-    HDR_CONTENT_BASE,                   /**< obsoleted RFC 2068 */
-    HDR_CONTENT_DISPOSITION,            /**< RFC 2183, 6266 */
-    HDR_CONTENT_ENCODING,               /**< RFC 7231 */
-    HDR_CONTENT_LANGUAGE,               /**< RFC 7231 */
-    HDR_CONTENT_LENGTH,                 /**< RFC 7230 */
-    HDR_CONTENT_LOCATION,               /**< RFC 7231 */
-    HDR_CONTENT_MD5,                    /**< deprecated, RFC 2616 */
-    HDR_CONTENT_RANGE,                  /**< RFC 7233 */
-    HDR_CONTENT_TYPE,                   /**< RFC 7231 */
-    HDR_COOKIE,                         /**< RFC 6265 header we may need to erase */
-    HDR_COOKIE2,                        /**< obsolete RFC 2965 header we may need to erase */
-    HDR_DATE,                           /**< RFC 7231 */
-    /*HDR_DAV,*/                        /* RFC 2518 */
-    /*HDR_DEPTH,*/                      /* RFC 2518 */
-    /*HDR_DERIVED_FROM,*/               /* deprecated RFC 2068 */
-    /*HDR_DESTINATION,*/                /* RFC 2518 */
-    HDR_ETAG,                           /**< RFC 7232 */
-    HDR_EXPECT,                         /**< RFC 7231 */
-    HDR_EXPIRES,                        /**< RFC 7234 */
-    HDR_FORWARDED,                      /**< RFC 7239 */
-    HDR_FROM,                           /**< RFC 7231 */
-    HDR_HOST,                           /**< RFC 7230 */
-    HDR_HTTP2_SETTINGS,                 /**< HTTP/2.0 upgrade header. see draft-ietf-httpbis-http2-13 */
-    /*HDR_IF,*/                         /* RFC 2518 */
-    HDR_IF_MATCH,                       /**< RFC 7232 */
-    HDR_IF_MODIFIED_SINCE,              /**< RFC 7232 */
-    HDR_IF_NONE_MATCH,                  /**< RFC 7232 */
-    HDR_IF_RANGE,                       /**< RFC 7233 */
-    HDR_IF_UNMODIFIED_SINCE,            /**< RFC 7232 */
-    HDR_KEEP_ALIVE,                     /**< obsoleted RFC 2068 header we may need to erase */
-    HDR_KEY,                            /**< experimental RFC Draft draft-fielding-http-key-02 */
-    HDR_LAST_MODIFIED,                  /**< RFC 7232 */
-    HDR_LINK,                           /**< RFC 5988 */
-    HDR_LOCATION,                       /**< RFC 7231 */
-    /*HDR_LOCK_TOKEN,*/                 /* RFC 2518 */
-    HDR_MAX_FORWARDS,                   /**< RFC 7231 */
-    HDR_MIME_VERSION,                   /**< RFC 2045, 7231 */
-    HDR_NEGOTIATE,                      /**< experimental RFC 2295. Why only this one from 2295? */
-    /*HDR_OVERWRITE,*/                  /* RFC 2518 */
-    HDR_ORIGIN,                         /* CORS Draft specification (see http://www.w3.org/TR/cors/) */
-    HDR_PRAGMA,                         /**< RFC 7234 */
-    HDR_PROXY_AUTHENTICATE,             /**< RFC 7235 */
-    HDR_PROXY_AUTHENTICATION_INFO,      /**< RFC 2617 */
-    HDR_PROXY_AUTHORIZATION,            /**< RFC 7235 */
-    HDR_PROXY_CONNECTION,               /**< obsolete Netscape header we may need to erase. */
-    HDR_PROXY_SUPPORT,                  /**< RFC 4559 */
-    HDR_PUBLIC,                         /**<  RFC 2068 */
-    HDR_RANGE,                          /**< RFC 7233 */
-    HDR_REFERER,                        /**< RFC 7231 */
-    HDR_REQUEST_RANGE,                  /**< some clients use this, sigh */
-    HDR_RETRY_AFTER,                    /**< RFC 7231 */
-    HDR_SERVER,                         /**< RFC 7231 */
-    HDR_SET_COOKIE,                     /**< RFC 6265 header we may need to erase */
-    HDR_SET_COOKIE2,                    /**< obsoleted RFC 2965 header we may need to erase */
-    /*HDR_STATUS_URI,*/                 /* RFC 2518 */
-    /*HDR_TCN,*/                        /* experimental RFC 2295 */
-    HDR_TE,                             /**< RFC 7230 */
-    /*HDR_TIMEOUT,*/                    /* RFC 2518 */
-    HDR_TITLE,                          /* obsolete draft suggested header */
-    HDR_TRAILER,                        /**< RFC 7230 */
-    HDR_TRANSFER_ENCODING,              /**< RFC 7230 */
-    HDR_TRANSLATE,                      /**< IIS custom header we may need to erase */
-    HDR_UNLESS_MODIFIED_SINCE,          /**< IIS custom header we may need to erase */
-    HDR_UPGRADE,                        /**< RFC 7230 */
-    HDR_USER_AGENT,                     /**< RFC 7231 */
-    /*HDR_VARIANT_VARY,*/               /* experimental RFC 2295 */
-    HDR_VARY,                           /**< RFC 7231 */
-    HDR_VIA,                            /**< RFC 7230 */
-    HDR_WARNING,                        /**< RFC 7234 */
-    HDR_WWW_AUTHENTICATE,               /**< RFC 7235, 4559 */
-    HDR_X_CACHE,                        /**< Squid custom header */
-    HDR_X_CACHE_LOOKUP,                 /**< Squid custom header. temporary hack that became de-facto. TODO remove */
-    HDR_X_FORWARDED_FOR,                /**< obsolete Squid custom header, RFC 7239 */
-    HDR_X_REQUEST_URI,                  /**< Squid custom header appended if ADD_X_REQUEST_URI is defined */
-    HDR_X_SQUID_ERROR,                  /**< Squid custom header on generated error responses */
-#if X_ACCELERATOR_VARY
-    HDR_X_ACCELERATOR_VARY,             /**< obsolete Squid custom header. */
-#endif
-#if USE_ADAPTATION
-    HDR_X_NEXT_SERVICES,                /**< Squid custom ICAP header */
-#endif
-    HDR_SURROGATE_CAPABILITY,           /**< Edge Side Includes (ESI) header */
-    HDR_SURROGATE_CONTROL,              /**< Edge Side Includes (ESI) header */
-    HDR_FRONT_END_HTTPS,                /**< MS Exchange custom header we may have to add */
-    HDR_FTP_COMMAND,                    /**< Internal header for FTP command */
-    HDR_FTP_ARGUMENTS,                  /**< Internal header for FTP command arguments */
-    HDR_FTP_PRE,                        /**< Internal header containing leading FTP control response lines */
-    HDR_FTP_STATUS,                     /**< Internal header for FTP reply status */
-    HDR_FTP_REASON,                     /**< Internal header for FTP reply reason */
-    HDR_OTHER,                          /**< internal tag value for "unknown" headers */
-    HDR_ENUM_END
-} http_hdr_type;
-
 /** possible types for http header fields */
 typedef enum {
     ftInvalid = HDR_ENUM_END,	/**< to catch nasty errors with hdr_id<->fld_type clashes */
     ftInt,
     ftInt64,
     ftStr,
     ftDate_1123,
     ftETag,
     ftPCc,
     ftPContRange,
     ftPRange,
     ftPSc,
     ftDate_1123_or_ETag
 } field_type;
 
 /** Possible owners of http header */
 typedef enum {
     hoNone =0,
 #if USE_HTCP
     hoHtcpReply,
@@ -202,41 +92,41 @@
 class ETag;
 class TimeOrTag;
 
 class HttpHeader
 {
 
 public:
     HttpHeader();
     explicit HttpHeader(const http_hdr_owner_type owner);
     HttpHeader(const HttpHeader &other);
     ~HttpHeader();
 
     HttpHeader &operator =(const HttpHeader &other);
 
     /* Interface functions */
     void clean();
     void append(const HttpHeader * src);
     void update (HttpHeader const *fresh, HttpHeaderMask const *denied_mask);
     void compact();
     int reset();
-    int parse(const char *header_start, const char *header_end);
+    int parse(const char *header_start, size_t len);
     void packInto(Packer * p, bool mask_sensitive_info=false) const;
     HttpHeaderEntry *getEntry(HttpHeaderPos * pos) const;
     HttpHeaderEntry *findEntry(http_hdr_type id) const;
     int delByName(const char *name);
     int delById(http_hdr_type id);
     void delAt(HttpHeaderPos pos, int &headers_deleted);
     void refreshMask();
     void addEntry(HttpHeaderEntry * e);
     void insertEntry(HttpHeaderEntry * e);
     String getList(http_hdr_type id) const;
     bool getList(http_hdr_type id, String *s) const;
     String getStrOrList(http_hdr_type id) const;
     String getByName(const char *name) const;
     /// sets value and returns true iff a [possibly empty] named field is there
     bool getByNameIfPresent(const char *name, String &value) const;
     String getByNameListMember(const char *name, const char *member, const char separator) const;
     String getListMember(http_hdr_type id, const char *member, const char separator) const;
     int has(http_hdr_type id) const;
     void putInt(http_hdr_type id, int number);
     void putInt64(http_hdr_type id, int64_t number);

=== modified file 'src/HttpHeaderTools.cc'
--- src/HttpHeaderTools.cc	2014-09-13 13:59:43 +0000
+++ src/HttpHeaderTools.cc	2014-09-14 12:23:03 +0000
@@ -144,46 +144,45 @@
 
 #if USE_HTTP_VIOLATIONS
     if (hdr->has(HDR_PROXY_CONNECTION))
         list = hdr->getList(HDR_PROXY_CONNECTION);
     else
 #endif
         if (hdr->has(HDR_CONNECTION))
             list = hdr->getList(HDR_CONNECTION);
         else
             return 0;
 
     res = strListIsMember(&list, directive, ',');
 
     list.clean();
 
     return res;
 }
 
 /** handy to printf prefixes of potentially very long buffers */
 const char *
-getStringPrefix(const char *str, const char *end)
+getStringPrefix(const char *str, size_t sz)
 {
 #define SHORT_PREFIX_SIZE 512
     LOCAL_ARRAY(char, buf, SHORT_PREFIX_SIZE);
-    const int sz = 1 + (end ? end - str : strlen(str));
-    xstrncpy(buf, str, (sz > SHORT_PREFIX_SIZE) ? SHORT_PREFIX_SIZE : sz);
+    xstrncpy(buf, str, (sz+1 > SHORT_PREFIX_SIZE) ? SHORT_PREFIX_SIZE : sz);
     return buf;
 }
 
 /**
  * parses an int field, complains if soemthing went wrong, returns true on
  * success
  */
 int
 httpHeaderParseInt(const char *start, int *value)
 {
     assert(value);
     *value = atoi(start);
 
     if (!*value && !xisdigit(*start)) {
         debugs(66, 2, "failed to parse an int header field near '" << start << "'");
         return 0;
     }
 
     return 1;
 }

=== modified file 'src/HttpHeaderTools.h'
--- src/HttpHeaderTools.h	2014-09-13 13:59:43 +0000
+++ src/HttpHeaderTools.h	2014-09-14 12:23:03 +0000
@@ -107,25 +107,25 @@
     Format::Format *valueFormat;
 
     /// internal ID for "known" headers or HDR_OTHER
     http_hdr_type fieldId;
 
     /// whether fieldValue may contain macros
     bool quoted;
 };
 
 int httpHeaderParseOffset(const char *start, int64_t * off);
 
 HttpHeaderFieldInfo *httpHeaderBuildFieldsInfo(const HttpHeaderFieldAttrs * attrs, int count);
 void httpHeaderDestroyFieldsInfo(HttpHeaderFieldInfo * info, int count);
 http_hdr_type httpHeaderIdByName(const char *name, size_t name_len, const HttpHeaderFieldInfo * attrs, int end);
 http_hdr_type httpHeaderIdByNameDef(const char *name, int name_len);
 const char *httpHeaderNameById(int id);
 int httpHeaderHasConnDir(const HttpHeader * hdr, const char *directive);
 int httpHeaderParseInt(const char *start, int *val);
 void httpHeaderPutStrf(HttpHeader * hdr, http_hdr_type id, const char *fmt,...) PRINTF_FORMAT_ARG3;
 
-const char *getStringPrefix(const char *str, const char *end);
+const char *getStringPrefix(const char *str, size_t len);
 
 void httpHdrMangleList(HttpHeader *, HttpRequest *, int req_or_rep);
 
 #endif

=== modified file 'src/HttpMsg.cc'
--- src/HttpMsg.cc	2014-09-13 13:59:43 +0000
+++ src/HttpMsg.cc	2014-10-11 10:21:16 +0000
@@ -18,41 +18,41 @@
 #include "SquidConfig.h"
 
 HttpMsg::HttpMsg(http_hdr_owner_type owner): header(owner),
         cache_control(NULL), hdr_sz(0), content_length(0),
         pstate(psReadyToParseStartLine)
 {}
 
 HttpMsg::~HttpMsg()
 {
     assert(!body_pipe);
 }
 
 HttpMsgParseState &operator++ (HttpMsgParseState &aState)
 {
     int tmp = (int)aState;
     aState = (HttpMsgParseState)(++tmp);
     return aState;
 }
 
 /* find end of headers */
-int
+static int
 httpMsgIsolateHeaders(const char **parse_start, int l, const char **blk_start, const char **blk_end)
 {
     /*
      * parse_start points to the first line of HTTP message *headers*,
      * not including the request or status lines
      */
     size_t end = headersEnd(*parse_start, l);
     int nnl;
 
     if (end) {
         *blk_start = *parse_start;
         *blk_end = *parse_start + end - 1;
         /*
          * leave blk_end pointing to the first character after the
          * first newline which terminates the headers
          */
         assert(**blk_end == '\n');
 
         while (*(*blk_end - 1) == '\r')
             --(*blk_end);
@@ -238,48 +238,49 @@
             PROF_stop(HttpMsg_httpMsgParseStep);
             return httpMsgParseError();
         }
 
         *parse_end_ptr = parse_start;
 
         hdr_sz = *parse_end_ptr - buf;
         parse_len = parse_len - hdr_sz;
 
         ++pstate;
     }
 
     /*
      * XXX This code uses parse_start; but if we're incrementally parsing then
      * this code might not actually be given parse_start at the right spot (just
      * after headers.) Grr.
      */
     if (pstate == psReadyToParseHeaders) {
         if (!httpMsgIsolateHeaders(&parse_start, parse_len, &blk_start, &blk_end)) {
             if (atEnd) {
-                blk_start = parse_start, blk_end = blk_start + strlen(blk_start);
+                blk_start = parse_start;
+                blk_end = blk_start + strlen(blk_start);
             } else {
                 PROF_stop(HttpMsg_httpMsgParseStep);
                 return 0;
             }
         }
 
-        if (!header.parse(blk_start, blk_end)) {
+        if (!header.parse(blk_start, blk_end-blk_start)) {
             PROF_stop(HttpMsg_httpMsgParseStep);
             return httpMsgParseError();
         }
 
         hdrCacheInit();
 
         *parse_end_ptr = parse_start;
 
         hdr_sz = *parse_end_ptr - buf;
 
         ++pstate;
     }
 
     PROF_stop(HttpMsg_httpMsgParseStep);
     return 1;
 }
 
 /* handy: resets and returns -1 */
 int
 HttpMsg::httpMsgParseError()

=== modified file 'src/HttpMsg.h'
--- src/HttpMsg.h	2014-09-13 13:59:43 +0000
+++ src/HttpMsg.h	2014-09-14 12:23:03 +0000
@@ -1,37 +1,37 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #ifndef SQUID_HTTPMSG_H
 #define SQUID_HTTPMSG_H
 
 #include "base/Lock.h"
 #include "BodyPipe.h"
+#include "http/forward.h"
 #include "http/ProtocolVersion.h"
 #include "http/StatusCode.h"
 #include "HttpHeader.h"
-#include "HttpRequestMethod.h"
 
 /// common parts of HttpRequest and HttpReply
 class HttpMsg : public RefCountable
 {
 
 public:
     typedef RefCount<HttpMsg> Pointer;
 
     HttpMsg(http_hdr_owner_type owner);
     virtual ~HttpMsg();
 
     virtual void reset() = 0; // will have body when http*Clean()s are gone
 
     void packInto(Packer * p, bool full_uri) const;
 
     ///< produce a message copy, except for a few connection-specific settings
     virtual HttpMsg *clone() const = 0; ///< \todo rename: not a true copy?
 
     /// [re]sets Content-Length header and cached value
     void setContentLength(int64_t clen);
@@ -81,26 +81,24 @@
 
     virtual bool inheritProperties(const HttpMsg *aMsg) = 0;
 
 protected:
     /**
      * Validate the message start line is syntactically correct.
      * Set HTTP error status according to problems found.
      *
      * \retval true   Status line has no serious problems.
      * \retval false  Status line has a serious problem. Correct response is indicated by error.
      */
     virtual bool sanityCheckStartLine(MemBuf *buf, const size_t hdr_len, Http::StatusCode *error) = 0;
 
     virtual void packFirstLineInto(Packer * p, bool full_uri) const = 0;
 
     virtual bool parseFirstLine(const char *blk_start, const char *blk_end) = 0;
 
     virtual void hdrCacheInit();
 };
 
-int httpMsgIsolateHeaders(const char **parse_start, int len, const char **blk_start, const char **blk_end);
-
 #define HTTPMSGUNLOCK(a) if (a) { if ((a)->unlock() == 0) delete (a); (a)=NULL; }
 #define HTTPMSGLOCK(a) (a)->lock()
 
 #endif /* SQUID_HTTPMSG_H */

=== modified file 'src/HttpRequest.cc'
--- src/HttpRequest.cc	2014-09-13 13:59:43 +0000
+++ src/HttpRequest.cc	2014-09-14 12:23:03 +0000
@@ -1,40 +1,41 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 73    HTTP Request */
 
 #include "squid.h"
 #include "AccessLogEntry.h"
 #include "acl/AclSizeLimit.h"
 #include "acl/FilledChecklist.h"
 #include "client_side.h"
 #include "DnsLookupDetails.h"
 #include "err_detail_type.h"
 #include "globals.h"
 #include "gopher.h"
 #include "http.h"
+#include "http/one/RequestParser.h"
 #include "HttpHdrCc.h"
 #include "HttpHeaderRange.h"
 #include "HttpRequest.h"
 #include "log/Config.h"
 #include "MemBuf.h"
 #include "SquidConfig.h"
 #include "Store.h"
 #include "URL.h"
 
 #if USE_AUTH
 #include "auth/UserRequest.h"
 #endif
 #if ICAP_CLIENT
 #include "adaptation/icap/icap_log.h"
 #endif
 
 HttpRequest::HttpRequest() :
         HttpMsg(hoRequest)
 {
     init();
@@ -263,105 +264,107 @@
  * Checks the first line of an HTTP request is valid
  * currently just checks the request method is present.
  *
  * NP: Other errors are left for detection later in the parse.
  */
 bool
 HttpRequest::sanityCheckStartLine(MemBuf *buf, const size_t hdr_len, Http::StatusCode *error)
 {
     // content is long enough to possibly hold a reply
     // 2 being magic size of a 1-byte request method plus space delimiter
     if ( buf->contentSize() < 2 ) {
         // this is ony a real error if the headers apparently complete.
         if (hdr_len > 0) {
             debugs(58, 3, HERE << "Too large request header (" << hdr_len << " bytes)");
             *error = Http::scInvalidHeader;
         }
         return false;
     }
 
     /* 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;
     }
 
     return true;
 }
 
 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); // XXX: SBuf ctor allocates and data-copies. performance regression.
+    method = HttpRequestMethod(m);
 
     if (method == Http::METHOD_NONE)
         return false;
 
-    start = t + strspn(t, w_space);
+    start = t + strspn(t, w_space); // XXX: breaks if whitespace exists in URL
 
     const char *ver = findTrailingHTTPVersion(start, end);
 
     if (ver) {
         end = ver - 1;
 
         while (xisspace(*end)) // find prev non-space
             --end;
 
         ++end;                 // back to space
 
         if (2 != sscanf(ver + 5, "%d.%d", &http_ver.major, &http_ver.minor)) {
             debugs(73, DBG_IMPORTANT, "parseRequestLine: Invalid HTTP identifier.");
             return false;
         }
     } else {
         http_ver.major = 0;
         http_ver.minor = 9;
     }
 
     if (end < start)   // missing URI
         return false;
 
     char save = *end;
 
     * (char *) end = '\0';     // temp terminate URI, XXX dangerous?
 
     HttpRequest *tmp = urlParse(method, (char *) start, this);
 
     * (char *) end = save;
 
     if (NULL == tmp)
         return false;
 
     return true;
 }
 
-int
-HttpRequest::parseHeader(const char *parse_start, int len)
+bool
+HttpRequest::parseHeader(Http1::RequestParser &hp)
 {
-    const char *blk_start, *blk_end;
-
-    if (!httpMsgIsolateHeaders(&parse_start, len, &blk_start, &blk_end))
-        return 0;
+    // HTTP/1 message contains "zero or more header fields"
+    // zero does not need parsing
+    if (!hp.headerBlockSize())
+        return true;
 
-    int result = header.parse(blk_start, blk_end);
+    // XXX: c_str() reallocates. performance regression.
+    const bool result = header.parse(hp.mimeHeader().c_str(), hp.headerBlockSize());
 
     if (result)
         hdrCacheInit();
 
     return result;
 }
 
 /* swaps out request using httpRequestPack */
 void
 HttpRequest::swapOut(StoreEntry * e)
 {
     Packer p;
     assert(e);
     packerToStoreInit(&p, e);
     pack(&p);
     packerClean(&p);
 }
 
 /* packs request-line and headers, appends <crlf> terminator */
 void

=== modified file 'src/HttpRequest.h'
--- src/HttpRequest.h	2014-09-13 13:59:43 +0000
+++ src/HttpRequest.h	2014-09-14 12:23:03 +0000
@@ -1,37 +1,37 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #ifndef SQUID_HTTPREQUEST_H
 #define SQUID_HTTPREQUEST_H
 
 #include "base/CbcPointer.h"
 #include "Debug.h"
 #include "err_type.h"
 #include "HierarchyLogEntry.h"
 #include "HttpMsg.h"
-#include "HttpRequestMethod.h"
+#include "http/RequestMethod.h"
 #include "Notes.h"
 #include "RequestFlags.h"
 #include "URL.h"
 
 #if USE_AUTH
 #include "auth/UserRequest.h"
 #endif
 #if USE_ADAPTATION
 #include "adaptation/History.h"
 #endif
 #if ICAP_CLIENT
 #include "adaptation/icap/History.h"
 #endif
 #if USE_SQUID_EUI
 #include "eui/Eui48.h"
 #include "eui/Eui64.h"
 #endif
 
 class ConnStateData;
 
@@ -192,41 +192,41 @@
     String extacl_user;		/* User name returned by extacl lookup */
 
     String extacl_passwd;	/* Password returned by extacl lookup */
 
     String extacl_log;		/* String to be used for access.log purposes */
 
     String extacl_message;	/* String to be used for error page purposes */
 
 #if FOLLOW_X_FORWARDED_FOR
     String x_forwarded_for_iterator; /* XXX a list of IP addresses */
 #endif /* FOLLOW_X_FORWARDED_FOR */
 
     /// A strong etag of the cached entry. Used for refreshing that entry.
     String etag;
 
 public:
     bool multipartRangeRequest() const;
 
     bool parseFirstLine(const char *start, const char *end);
 
-    int parseHeader(const char *parse_start, int len);
+    bool parseHeader(Http1::RequestParser &hp); // TODO move this function to the parser
 
     virtual bool expectingBody(const HttpRequestMethod& unused, int64_t&) const;
 
     bool bodyNibbled() const; // the request has a [partially] consumed body
 
     int prefixLen();
 
     void swapOut(StoreEntry * e);
 
     void pack(Packer * p);
 
     static void httpRequestPack(void *obj, Packer *p);
 
     static HttpRequest * CreateFromUrlAndMethod(char * url, const HttpRequestMethod& method);
 
     static HttpRequest * CreateFromUrl(char * url);
 
     ConnStateData *pinnedConnection();
 
     /**

=== modified file 'src/Makefile.am'
--- src/Makefile.am	2014-10-04 09:27:01 +0000
+++ src/Makefile.am	2014-10-05 10:44:11 +0000
@@ -386,50 +386,46 @@
 	HttpHdrRange.cc \
 	HttpHdrSc.cc \
 	HttpHdrSc.h \
 	HttpHdrScTarget.cc \
 	HttpHdrScTarget.h \
 	HttpHdrContRange.cc \
 	HttpHdrContRange.h \
 	HttpHeaderStat.h \
 	HttpHeader.h \
 	HttpHeader.cc \
 	HttpHeaderMask.h \
 	HttpHeaderRange.h \
 	HttpHeaderFieldInfo.h \
 	HttpHeaderTools.h \
 	HttpHeaderTools.cc \
 	HttpBody.h \
 	HttpBody.cc \
 	HttpControlMsg.h \
 	HttpMsg.cc \
 	HttpMsg.h \
-	HttpParser.cc \
-	HttpParser.h \
 	HttpReply.cc \
 	HttpReply.h \
 	RequestFlags.h \
 	RequestFlags.cc \
 	HttpRequest.cc \
 	HttpRequest.h \
-	HttpRequestMethod.cc \
-	HttpRequestMethod.h \
 	ICP.h \
 	icp_opcode.h \
 	icp_v2.cc \
 	icp_v3.cc \
 	int.h \
 	int.cc \
 	internal.h \
 	internal.cc \
 	$(IPC_SOURCE) \
 	ipcache.cc \
 	ipcache.h \
 	$(LEAKFINDERSOURCE) \
 	SquidList.h \
 	SquidList.cc \
 	LogTags.h \
 	lookup_t.h \
 	main.cc \
 	MasterXaction.cc \
 	MasterXaction.h \
 	Mem.h \
@@ -599,69 +595,69 @@
 	icp_opcode.cc \
 	LogTags.cc \
 	lookup_t.cc \
 	repl_modules.cc \
 	swap_log_op.cc
 
 CLEANFILES += $(BUILT_SOURCES)
 
 nodist_squid_SOURCES = \
 	$(DISKIO_GEN_SOURCE) \
 	$(BUILT_SOURCES)
 
 squid_LDADD = \
 	$(AUTH_ACL_LIBS) \
 	ident/libident.la \
 	acl/libacls.la \
 	acl/libstate.la \
 	$(AUTH_LIBS) \
 	$(DISK_LIBS) \
 	acl/libapi.la \
+	clients/libclients.la \
+	servers/libservers.la \
+	ftp/libftp.la \
+	helper/libhelper.la \
+	http/libsquid-http.la \
+	parser/libsquid-parser.la \
 	base/libbase.la \
 	libsquid.la \
 	ip/libip.la \
 	fs/libfs.la \
 	$(SSL_LIBS) \
 	ipc/libipc.la \
 	mgr/libmgr.la \
 	anyp/libanyp.la \
 	comm/libcomm.la \
 	eui/libeui.la \
-	helper/libhelper.la \
-	http/libsquid-http.la \
 	icmp/libicmp.la icmp/libicmp-core.la \
 	log/liblog.la \
 	format/libformat.la \
-	clients/libclients.la \
-	servers/libservers.la \
-	ftp/libftp.la \
 	$(XTRA_OBJS) \
 	$(DISK_LINKOBJS) \
 	$(REPL_OBJS) \
 	$(DISK_OS_LIBS) \
 	$(NETTLELIB) \
 	$(CRYPTLIB) \
 	$(REGEXLIB) \
 	$(ADAPTATION_LIBS) \
 	$(ESI_LIBS) \
 	$(SNMP_LIBS) \
-	parser/libsquid-parser.la \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(SSLLIB) \
 	$(EPOLL_LIBS) \
 	$(MINGW_LIBS) \
 	$(KRB5LIBS) \
 	$(COMPAT_LIB) \
 	$(XTRA_LIBS)
 squid_DEPENDENCIES = \
 	$(DISK_LIBS) \
 	$(DISK_LINKOBJS) \
 	$(REPL_OBJS) \
 	$(ADAPTATION_LIBS) \
 	$(ESI_LOCAL_LIBS) \
 	$(SSL_LIBS) \
 	$(AUTH_ACL_LIBS) \
 	ident/libident.la \
 	acl/libacls.la \
 	eui/libeui.la \
@@ -728,41 +724,41 @@
 	int.h \
 	int.cc \
 	Mem.h \
 	mem.cc \
 	MemBuf.cc \
 	MemBuf.cci \
 	MemBuf.h \
 	Parsing.h \
 	store_key_md5.h \
 	store_key_md5.cc \
 	tests/stub_StoreMeta.cc \
 	StoreMetaUnpacker.cc \
 	String.cc \
 	SquidNew.cc \
 	tests/stub_time.cc \
 	ufsdump.cc \
 	dlink.h \
 	dlink.cc \
 	helper/ChildConfig.h \
 	tests/stub_HelperChildConfig.cc \
-	HttpRequestMethod.cc \
+	http/RequestMethod.cc \
 	RemovalPolicy.cc \
 	$(WIN32_SOURCE) \
 	fd.h \
 	tests/stub_fd.cc
 ufsdump_LDADD = \
 	ident/libident.la \
 	acl/libacls.la \
 	eui/libeui.la \
 	acl/libstate.la \
 	acl/libapi.la \
 	base/libbase.la \
 	libsquid.la \
 	ip/libip.la \
 	fs/libfs.la \
 	ipc/libipc.la \
 	mgr/libmgr.la \
 	$(XTRA_OBJS) \
 	$(REPL_OBJS) \
 	$(NETTLELIB) \
 	$(CRYPTLIB) \
@@ -1056,41 +1052,41 @@
 
 test_tools.cc: $(top_srcdir)/test-suite/test_tools.cc
 	cp $(top_srcdir)/test-suite/test_tools.cc .
 
 # stock tools for unit tests - library independent versions of dlink_list 
 # etc.
 # globals.cc is needed by test_tools.cc.
 # Neither of these should be disted from here.
 TESTSOURCES= \
 	tests/STUB.h \
 	test_tools.cc \
 	globals.cc
 
 check_PROGRAMS+=\
 	tests/testBoilerplate \
 	tests/testCacheManager \
 	tests/testDiskIO \
 	tests/testEvent \
 	tests/testEventLoop \
 	tests/test_http_range \
-	tests/testHttpParser \
+	tests/testHttp1Parser \
 	tests/testHttpReply \
 	tests/testHttpRequest \
 	tests/testStore \
 	tests/testString \
 	tests/testURL \
 	tests/testSBuf \
 	tests/testSBufList \
 	tests/testConfigParser \
 	tests/testStatHist
 
 if HAVE_FS_ROCK
 check_PROGRAMS += tests/testRock
 endif
 if HAVE_FS_UFS
 check_PROGRAMS += tests/testUfs
 endif
 
 ## NP: required to run the above list. check_PROGRAMS only builds the binaries...
 TESTS += $(check_PROGRAMS)
 
@@ -1194,40 +1190,41 @@
 	tests/stub_StatHist.cc \
 	repl_modules.h \
 	tests/stub_store.cc \
 	tests/stub_store_stats.cc \
 	tools.h \
 	tests/stub_tools.cc \
 	tests/stub_HttpRequest.cc \
 	tests/testHttpReply.cc \
 	tests/testHttpReply.h \
 	tests/testMain.cc \
 	tests/stub_time.cc \
 	url.cc \
 	wordlist.h \
 	wordlist.cc
 nodist_tests_testHttpReply_SOURCES=\
 	$(TESTSOURCES)
 tests_testHttpReply_LDFLAGS = $(LIBADD_DL)
 tests_testHttpReply_LDADD=\
 	CommCalls.o \
 	http/libsquid-http.la \
+	parser/libsquid-parser.la \
 	acl/libacls.la \
 	acl/libapi.la \
 	acl/libstate.la \
 	anyp/libanyp.la \
 	ip/libip.la \
 	base/libbase.la \
 	ipc/libipc.la \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(SQUID_CPPUNIT_LIBS) \
 	$(SQUID_CPPUNIT_LA) \
 	$(NETTLELIB) \
 	$(SSLLIB) \
 	$(COMPAT_LIB) \
 	$(XTRA_LIBS)
 tests_testHttpReply_DEPENDENCIES= $(SQUID_CPPUNIT_LA)
 
 tests_testACLMaxUserIP_SOURCES= \
 	cbdata.cc \
@@ -1241,41 +1238,40 @@
 	event.cc \
 	fatal.h \
 	tests/stub_fatal.cc \
 	FileMap.h \
 	filemap.cc \
 	HttpBody.cc \
 	HttpHeader.h \
 	HttpHeader.cc \
 	HttpHeaderFieldInfo.h \
 	HttpHeaderTools.h \
 	HttpHeaderTools.cc \
 	HttpHdrContRange.cc \
 	HttpHdrRange.cc \
 	HttpHeaderFieldStat.h \
 	HttpHdrCc.h \
 	HttpHdrCc.cc \
 	HttpHdrCc.cci \
 	HttpHdrSc.cc \
 	HttpHdrScTarget.cc \
 	HttpMsg.cc \
-	HttpRequestMethod.cc \
 	int.h \
 	int.cc \
 	MasterXaction.cc \
 	MasterXaction.h \
 	Notes.cc \
 	Notes.h \
 	SquidList.h \
 	SquidList.cc \
 	mem_node.cc \
 	Packer.cc \
 	Parsing.cc \
 	SquidMath.cc \
 	StatCounters.cc \
 	StatCounters.h \
 	StatHist.h \
 	StrList.h \
 	StrList.cc \
 	tests/stub_StatHist.cc \
 	stmem.cc \
 	$(SBUF_SOURCE) \
@@ -1330,40 +1326,41 @@
 	tests/stub_tools.cc \
 	tests/stub_cache_manager.cc \
 	tests/stub_UdsOp.cc \
 	tests/testACLMaxUserIP.cc \
 	tests/testACLMaxUserIP.h \
 	tests/testMain.cc \
 	tests/stub_time.cc \
 	url.cc \
 	URL.h \
 	Mem.h \
 	tests/stub_mem.cc \
 	MemBuf.cc \
 	wordlist.h \
 	wordlist.cc
 nodist_tests_testACLMaxUserIP_SOURCES= \
 	$(TESTSOURCES)
 tests_testACLMaxUserIP_LDADD= \
 	libsquid.la \
 	helper/libhelper.la \
 	http/libsquid-http.la \
+	parser/libsquid-parser.la \
 	$(AUTH_ACL_LIBS) \
 	ident/libident.la \
 	acl/libacls.la \
 	eui/libeui.la \
 	acl/libstate.la \
 	acl/libapi.la \
 	anyp/libanyp.la \
 	base/libbase.la \
 	ip/libip.la \
 	ipc/libipc.la \
 	mgr/libmgr.la \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(DISK_OS_LIBS) \
 	$(NETTLELIB) \
 	$(REGEXLIB) \
 	$(SQUID_CPPUNIT_LIBS) \
 	$(SSLLIB) \
 	$(COMPAT_LIB) \
@@ -1381,46 +1378,43 @@
 	tests/stub_debug.cc \
 	tests/stub_time.cc
 nodist_tests_testBoilerplate_SOURCES = \
 	tests/stub_cbdata.cc \
 	tests/stub_MemBuf.cc \
 	$(TESTSOURCES)
 tests_testBoilerplate_LDADD= \
 	$(SQUID_CPPUNIT_LIBS) \
 	$(SSLLIB) \
 	base/libbase.la \
 	$(COMPAT_LIB) \
 	$(XTRA_LIBS)
 tests_testBoilerplate_LDFLAGS = $(LIBADD_DL)
 tests_testBoilerplate_DEPENDENCIES = \
 	$(SQUID_CPPUNIT_LA)
 
 ## Tests of the CacheManager module.
 tests_testCacheManager_SOURCES = \
 	AccessLogEntry.cc \
 	debug.cc \
-	HttpParser.cc \
-	HttpParser.h \
 	RequestFlags.h \
 	RequestFlags.cc \
 	HttpRequest.cc \
-	HttpRequestMethod.cc \
 	Mem.h \
 	tests/stub_mem.cc \
 	String.cc \
 	tests/testCacheManager.cc \
 	tests/testCacheManager.h \
 	tests/testMain.cc \
 	tests/stub_main_cc.cc \
 	tests/stub_ipc_Forwarder.cc \
 	tests/stub_store_stats.cc \
 	tests/stub_EventLoop.cc \
 	time.cc \
 	BodyPipe.cc \
 	cache_manager.cc \
 	cache_cf.h \
 	AuthReg.h \
 	YesNoNone.h \
 	YesNoNone.cc \
 	RefreshPattern.h \
 	cache_cf.cc \
 	CacheDigest.h \
@@ -1583,48 +1577,48 @@
 	$(UNLINKDSOURCE) \
 	url.cc \
 	urn.h \
 	urn.cc \
 	wccp2.h \
 	tests/stub_wccp2.cc \
 	whois.h \
 	tests/stub_whois.cc \
 	FadingCounter.cc \
 	$(WIN32_SOURCE) \
 	wordlist.h \
 	wordlist.cc
 nodist_tests_testCacheManager_SOURCES = \
 	$(BUILT_SOURCES) \
 	$(DISKIO_GEN_SOURCE)
 # comm.cc only requires comm/libcomm.la until fdc_table is dead.
 tests_testCacheManager_LDADD = \
 	libsquid.la \
 	clients/libclients.la \
 	servers/libservers.la \
+	ftp/libftp.la \
 	helper/libhelper.la \
 	http/libsquid-http.la \
-	ftp/libftp.la \
+	parser/libsquid-parser.la \
 	ident/libident.la \
 	acl/libacls.la \
 	acl/libstate.la \
 	acl/libapi.la \
-	parser/libsquid-parser.la \
 	base/libbase.la \
 	ip/libip.la \
 	fs/libfs.la \
 	comm/libcomm.la \
 	eui/libeui.la \
 	icmp/libicmp.la icmp/libicmp-core.la \
 	log/liblog.la \
 	format/libformat.la \
 	$(REPL_OBJS) \
 	$(DISK_LIBS) \
 	$(DISK_OS_LIBS) \
 	$(ADAPTATION_LIBS) \
 	$(ESI_LIBS) \
 	$(SSL_LIBS) \
 	anyp/libanyp.la \
 	ipc/libipc.la \
 	mgr/libmgr.la \
 	$(SNMP_LIBS) \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
@@ -1666,41 +1660,40 @@
 	fde.cc \
 	FileMap.h \
 	filemap.cc \
 	HttpBody.h \
 	HttpBody.cc \
 	HttpHeaderFieldStat.h \
 	HttpHdrCc.h \
 	HttpHdrCc.cc \
 	HttpHdrCc.cci \
 	HttpHdrContRange.cc \
 	HttpHdrSc.cc \
 	HttpHdrScTarget.cc \
 	HttpHdrRange.cc \
 	HttpHeaderFieldInfo.h \
 	HttpHeaderTools.h \
 	HttpHeaderTools.cc \
 	HttpHeader.h \
 	HttpHeader.cc \
 	HttpMsg.cc \
 	HttpReply.cc \
-	HttpRequestMethod.cc \
 	int.h \
 	int.cc \
 	SquidList.h \
 	SquidList.cc \
 	MasterXaction.cc \
 	MasterXaction.h \
 	MemBuf.cc \
 	MemObject.cc \
 	mem_node.cc \
 	Mem.h \
 	tests/stub_mem.cc \
 	Notes.h \
 	Notes.cc \
 	Packer.cc \
 	Parsing.cc \
 	refresh.h \
 	refresh.cc \
 	RemovalPolicy.cc \
 	RequestFlags.h \
 	RequestFlags.cc \
@@ -1773,40 +1766,41 @@
 	tests/testMain.cc \
 	tests/testStoreSupport.cc \
 	tests/testStoreSupport.h \
 	tests/stub_time.cc \
 	$(UNLINKDSOURCE) \
 	url.cc \
 	$(WIN32_SOURCE) \
 	wordlist.h \
 	wordlist.cc \
 	tools.h \
 	tests/stub_tools.cc
 nodist_tests_testDiskIO_SOURCES= \
 	$(TESTSOURCES) \
 	$(DISKIO_GEN_SOURCE) \
 	SquidMath.cc \
 	SquidMath.h \
 	swap_log_op.cc
 tests_testDiskIO_LDADD = \
 	libsquid.la \
 	http/libsquid-http.la \
+	parser/libsquid-parser.la \
 	SquidConfig.o \
 	CommCalls.o \
 	DnsLookupDetails.o \
 	ident/libident.la \
 	acl/libacls.la \
 	acl/libstate.la \
 	comm/libcomm.la \
 	ip/libip.la \
 	fs/libfs.la \
 	ipc/libipc.la \
 	$(REPL_OBJS) \
 	$(DISK_LIBS) \
 	$(DISK_OS_LIBS) \
 	acl/libapi.la \
 	anyp/libanyp.la \
 	mgr/libmgr.la \
 	$(SSL_LIBS) \
 	ipc/libipc.la \
 	base/libbase.la \
 	$(top_builddir)/lib/libmisccontainers.la \
@@ -1889,49 +1883,46 @@
 	helper.cc \
 	hier_code.h \
 	$(HTCPSOURCE) \
 	http.cc \
 	HttpBody.h \
 	HttpBody.cc \
 	HttpHeader.h \
 	HttpHeader.cc \
 	HttpHeaderFieldInfo.h \
 	HttpHeaderTools.h \
 	HttpHeaderTools.cc \
 	HttpHeaderFieldStat.h \
 	HttpHdrCc.h \
 	HttpHdrCc.cc \
 	HttpHdrCc.cci \
 	HttpHdrContRange.cc \
 	HttpHdrRange.cc \
 	HttpHdrSc.cc \
 	HttpHdrScTarget.cc \
 	HttpMsg.cc \
-	HttpParser.cc \
-	HttpParser.h \
 	HttpReply.cc \
 	PeerPoolMgr.h \
 	PeerPoolMgr.cc \
 	RequestFlags.h \
 	RequestFlags.cc \
 	HttpRequest.cc \
-	HttpRequestMethod.cc \
 	icp_v2.cc \
 	icp_v3.cc \
 	$(IPC_SOURCE) \
 	ipcache.cc \
 	int.h \
 	int.cc \
 	internal.h \
 	internal.cc \
 	SquidList.h \
 	SquidList.cc \
 	MasterXaction.cc \
 	MasterXaction.h \
 	Mem.h \
 	tests/stub_mem.cc \
 	mem_node.cc \
 	MemBuf.cc \
 	MemObject.cc \
 	mime.h \
 	mime.cc \
 	mime_header.h \
@@ -2015,48 +2006,48 @@
 	tests/stub_tunnel.cc \
 	MemStore.cc \
 	$(UNLINKDSOURCE) \
 	url.cc \
 	urn.h \
 	urn.cc \
 	wccp2.h \
 	tests/stub_wccp2.cc \
 	whois.h \
 	tests/stub_whois.cc \
 	$(WIN32_SOURCE) \
 	wordlist.h \
 	wordlist.cc
 nodist_tests_testEvent_SOURCES = \
 	$(BUILT_SOURCES) \
 	$(DISKIO_GEN_SOURCE)
 tests_testEvent_LDADD = \
 	libsquid.la \
 	clients/libclients.la \
 	servers/libservers.la \
+	ftp/libftp.la \
 	helper/libhelper.la \
 	http/libsquid-http.la \
-	ftp/libftp.la \
+	parser/libsquid-parser.la \
 	ident/libident.la \
 	acl/libacls.la \
 	acl/libstate.la \
 	acl/libapi.la \
-	parser/libsquid-parser.la \
 	base/libbase.la \
 	ip/libip.la \
 	fs/libfs.la \
 	anyp/libanyp.la \
 	icmp/libicmp.la icmp/libicmp-core.la \
 	comm/libcomm.la \
 	log/liblog.la \
 	format/libformat.la \
 	$(REPL_OBJS) \
 	$(ADAPTATION_LIBS) \
 	$(ESI_LIBS) \
 	$(SSL_LIBS) \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(DISK_LIBS) \
 	$(DISK_OS_LIBS) \
 	ipc/libipc.la \
 	mgr/libmgr.la \
 	$(SNMP_LIBS) \
@@ -2137,49 +2128,46 @@
 	helper.cc \
 	hier_code.h \
 	$(HTCPSOURCE) \
 	http.cc \
 	HttpBody.h \
 	HttpBody.cc \
 	HttpHeader.h \
 	HttpHeader.cc \
 	HttpHeaderFieldInfo.h \
 	HttpHeaderTools.h \
 	HttpHeaderTools.cc \
 	HttpHeaderFieldStat.h \
 	HttpHdrCc.h \
 	HttpHdrCc.cc \
 	HttpHdrCc.cci \
 	HttpHdrContRange.cc \
 	HttpHdrRange.cc \
 	HttpHdrSc.cc \
 	HttpHdrScTarget.cc \
 	HttpMsg.cc \
-	HttpParser.cc \
-	HttpParser.h \
 	HttpReply.cc \
 	PeerPoolMgr.h \
 	PeerPoolMgr.cc \
 	RequestFlags.h \
 	RequestFlags.cc \
 	HttpRequest.cc \
-	HttpRequestMethod.cc \
 	icp_v2.cc \
 	icp_v3.cc \
 	$(IPC_SOURCE) \
 	ipcache.cc \
 	int.h \
 	int.cc \
 	internal.h \
 	internal.cc \
 	SquidList.h \
 	SquidList.cc \
 	MasterXaction.cc \
 	MasterXaction.h \
 	MemBuf.cc \
 	MemObject.cc \
 	Mem.h \
 	tests/stub_mem.cc \
 	mem_node.cc \
 	mime.h \
 	mime.cc \
 	mime_header.h \
@@ -2263,48 +2251,48 @@
 	tests/stub_tunnel.cc \
 	MemStore.cc \
 	$(UNLINKDSOURCE) \
 	url.cc \
 	urn.h \
 	urn.cc \
 	wccp2.h \
 	tests/stub_wccp2.cc \
 	whois.h \
 	tests/stub_whois.cc \
 	$(WIN32_SOURCE) \
 	wordlist.h \
 	wordlist.cc
 nodist_tests_testEventLoop_SOURCES = \
 	$(BUILT_SOURCES) \
 	$(DISKIO_GEN_SOURCE)
 tests_testEventLoop_LDADD = \
 	libsquid.la \
 	clients/libclients.la \
 	servers/libservers.la \
+	ftp/libftp.la \
 	helper/libhelper.la \
 	http/libsquid-http.la \
-	ftp/libftp.la \
+	parser/libsquid-parser.la \
 	ident/libident.la \
 	acl/libacls.la \
 	acl/libstate.la \
 	acl/libapi.la \
-	parser/libsquid-parser.la \
 	base/libbase.la \
 	ip/libip.la \
 	fs/libfs.la \
 	anyp/libanyp.la \
 	icmp/libicmp.la icmp/libicmp-core.la \
 	comm/libcomm.la \
 	log/liblog.la \
 	format/libformat.la \
 	$(REPL_OBJS) \
 	$(ADAPTATION_LIBS) \
 	$(ESI_LIBS) \
 	$(SSL_LIBS) \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(DISK_LIBS) \
 	$(DISK_OS_LIBS) \
 	ipc/libipc.la \
 	mgr/libmgr.la \
 	$(SNMP_LIBS) \
@@ -2381,49 +2369,46 @@
 	helper.cc \
 	hier_code.h \
 	$(HTCPSOURCE) \
 	http.cc \
 	HttpBody.h \
 	HttpBody.cc \
 	HttpHeaderFieldStat.h \
 	HttpHdrCc.h \
 	HttpHdrCc.cc \
 	HttpHdrCc.cci \
 	HttpHdrContRange.cc \
 	HttpHdrRange.cc \
 	HttpHdrSc.cc \
 	HttpHdrScTarget.cc \
 	HttpHeader.h \
 	HttpHeader.cc \
 	HttpHeaderFieldInfo.h \
 	HttpHeaderTools.h \
 	HttpHeaderTools.cc \
 	HttpMsg.cc \
-	HttpParser.cc \
-	HttpParser.h \
 	HttpReply.cc \
 	PeerPoolMgr.h \
 	PeerPoolMgr.cc \
 	RequestFlags.h \
 	RequestFlags.cc \
 	HttpRequest.cc \
-	HttpRequestMethod.cc \
 	icp_v2.cc \
 	icp_v3.cc \
 	int.h \
 	int.cc \
 	internal.h \
 	internal.cc \
 	$(IPC_SOURCE) \
 	ipcache.cc \
 	SquidList.h \
 	SquidList.cc \
 	MasterXaction.cc \
 	MasterXaction.h \
 	MemBuf.cc \
 	MemObject.cc \
 	Mem.h \
 	tests/stub_mem.cc \
 	mem_node.cc \
 	mime.h \
 	mime.cc \
 	mime_header.h \
@@ -2504,136 +2489,136 @@
 	tools.cc \
 	tests/stub_tunnel.cc \
 	$(UNLINKDSOURCE) \
 	url.cc \
 	urn.h \
 	urn.cc \
 	wccp2.h \
 	tests/stub_wccp2.cc \
 	whois.h \
 	tests/stub_whois.cc \
 	$(WIN32_SOURCE) \
 	wordlist.h \
 	wordlist.cc
 nodist_tests_test_http_range_SOURCES = \
 	$(BUILT_SOURCES) \
 	$(DISKIO_GEN_SOURCE)
 tests_test_http_range_LDADD = \
 	libsquid.la \
 	clients/libclients.la \
 	servers/libservers.la \
+	ftp/libftp.la \
 	helper/libhelper.la \
 	http/libsquid-http.la \
-	ftp/libftp.la \
+	parser/libsquid-parser.la \
 	ident/libident.la \
 	acl/libacls.la \
 	acl/libstate.la \
 	acl/libapi.la \
 	parser/libsquid-parser.la \
 	ip/libip.la \
 	fs/libfs.la \
 	anyp/libanyp.la \
 	icmp/libicmp.la icmp/libicmp-core.la \
 	comm/libcomm.la \
 	log/liblog.la \
 	format/libformat.la \
 	$(REPL_OBJS) \
 	$(DISK_LIBS) \
 	$(DISK_OS_LIBS) \
 	$(ADAPTATION_LIBS) \
 	$(ESI_LIBS) \
 	$(SSL_LIBS) \
 	ipc/libipc.la \
 	base/libbase.la \
 	mgr/libmgr.la \
 	$(SNMP_LIBS) \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(NETTLELIB) \
 	$(REGEXLIB) \
 	$(SQUID_CPPUNIT_LIBS) \
 	$(SQUID_CPPUNIT_LA) \
 	$(SSLLIB) \
 	$(KRB5LIBS) \
 	$(COMPAT_LIB) \
 	$(XTRA_LIBS)
 tests_test_http_range_LDFLAGS = $(LIBADD_DL)
 tests_test_http_range_DEPENDENCIES = \
 	$(SQUID_CPPUNIT_LA)
 
-tests_testHttpParser_SOURCES = \
+tests_testHttp1Parser_SOURCES = \
 	Debug.h \
-	HttpParser.cc \
-	HttpParser.h \
 	MemBuf.cc \
 	MemBuf.h \
 	tests/stub_MemObject.cc \
 	Mem.h \
 	tests/stub_mem.cc \
+	mime_header.cc \
+	mime_header.h \
 	String.cc \
 	cache_cf.h \
 	YesNoNone.h \
 	$(SBUF_SOURCE) \
 	tests/stub_SBufDetailedStats.cc \
 	tests/stub_cache_cf.cc \
 	tests/stub_cache_manager.cc \
 	tests/stub_comm.cc \
 	tests/stub_cbdata.cc \
 	tests/stub_debug.cc \
 	tests/stub_event.cc \
 	tests/stub_HelperChildConfig.cc \
 	tests/stub_stmem.cc \
 	tests/stub_store.cc \
 	tests/stub_store_stats.cc \
 	tools.h \
 	tests/stub_tools.cc \
-	tests/testHttpParser.cc \
-	tests/testHttpParser.h \
+	tests/testHttp1Parser.cc \
+	tests/testHttp1Parser.h \
 	tests/testMain.cc \
 	tests/stub_time.cc \
 	wordlist.h \
 	wordlist.cc
-nodist_tests_testHttpParser_SOURCES = \
+nodist_tests_testHttp1Parser_SOURCES = \
 	$(TESTSOURCES)
-tests_testHttpParser_LDADD= \
+tests_testHttp1Parser_LDADD= \
 	http/libsquid-http.la \
+	parser/libsquid-parser.la \
+	anyp/libanyp.la \
 	SquidConfig.o \
 	base/libbase.la \
 	ip/libip.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(SQUID_CPPUNIT_LIBS) \
 	$(COMPAT_LIB) \
 	$(XTRA_LIBS)
-tests_testHttpParser_LDFLAGS = $(LIBADD_DL)
-tests_testHttpParser_DEPENDENCIES = \
+tests_testHttp1Parser_LDFLAGS = $(LIBADD_DL)
+tests_testHttp1Parser_DEPENDENCIES = \
 	$(SQUID_CPPUNIT_LA)
 
 ## Tests of the HttpRequest module.
 tests_testHttpRequest_SOURCES = \
 	AccessLogEntry.cc \
-	HttpParser.cc \
-	HttpParser.h \
 	RequestFlags.h \
 	RequestFlags.cc \
 	HttpRequest.cc \
-	HttpRequestMethod.cc \
 	Mem.h \
 	tests/stub_mem.cc \
 	String.cc \
 	tests/testHttpRequest.h \
 	tests/testHttpRequest.cc \
 	tests/testHttpRequestMethod.h \
 	tests/testHttpRequestMethod.cc \
 	tests/testMain.cc \
 	tests/stub_DiskIOModule.cc \
 	tests/stub_libauth.cc \
 	tests/stub_main_cc.cc \
 	tests/stub_ipc_Forwarder.cc \
 	tests/stub_libeui.cc \
 	tests/stub_store_stats.cc \
 	tests/stub_EventLoop.cc \
 	time.cc \
 	BodyPipe.cc \
 	cache_manager.cc \
 	cache_cf.h \
 	AuthReg.h \
@@ -2796,58 +2781,59 @@
 	MemStore.cc \
 	url.cc \
 	urn.h \
 	urn.cc \
 	wccp2.h \
 	tests/stub_wccp2.cc \
 	whois.h \
 	tests/stub_whois.cc \
 	FadingCounter.cc \
 	$(WIN32_SOURCE) \
 	wordlist.h \
 	wordlist.cc
 nodist_tests_testHttpRequest_SOURCES = \
 	$(BUILT_SOURCES)
 tests_testHttpRequest_LDADD = \
 	libsquid.la \
 	clients/libclients.la \
 	servers/libservers.la \
 	helper/libhelper.la \
 	ftp/libftp.la \
+	http/libsquid-http.la \
 	ident/libident.la \
 	acl/libacls.la \
 	acl/libstate.la \
 	acl/libapi.la \
 	parser/libsquid-parser.la \
 	ip/libip.la \
 	fs/libfs.la \
 	$(SSL_LIBS) \
 	ipc/libipc.la \
+	parser/libsquid-parser.la \
 	base/libbase.la \
 	mgr/libmgr.la \
 	anyp/libanyp.la \
 	$(SNMP_LIBS) \
 	icmp/libicmp.la icmp/libicmp-core.la \
 	comm/libcomm.la \
 	log/liblog.la \
 	format/libformat.la \
-	http/libsquid-http.la \
 	$(REPL_OBJS) \
 	$(ADAPTATION_LIBS) \
 	$(ESI_LIBS) \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(DISK_OS_LIBS) \
 	$(NETTLELIB) \
 	$(REGEXLIB) \
 	$(SQUID_CPPUNIT_LIBS) \
 	$(SQUID_CPPUNIT_LA) \
 	$(SSLLIB) \
 	$(KRB5LIBS) \
 	$(COMPAT_LIB) \
 	$(XTRA_LIBS)
 tests_testHttpRequest_LDFLAGS = $(LIBADD_DL)
 tests_testHttpRequest_DEPENDENCIES = \
 	$(REPL_OBJS) \
 	$(SQUID_CPPUNIT_LA)
 
@@ -2870,41 +2856,40 @@
 	event.cc \
 	EventLoop.cc \
 	fatal.h \
 	tests/stub_fatal.cc \
 	FileMap.h \
 	filemap.cc \
 	HttpHeaderFieldStat.h \
 	HttpHdrCc.h \
 	HttpHdrCc.cc \
 	HttpHdrCc.cci \
 	HttpHdrContRange.cc \
 	HttpHdrRange.cc \
 	HttpHdrSc.cc \
 	HttpHdrScTarget.cc \
 	HttpHeaderFieldInfo.h \
 	HttpHeaderTools.h \
 	HttpHeaderTools.cc \
 	HttpHeader.h \
 	HttpHeader.cc \
 	HttpMsg.cc \
-	HttpRequestMethod.cc \
 	RequestFlags.cc \
 	RequestFlags.h \
 	int.h \
 	int.cc \
 	SquidList.h \
 	SquidList.cc \
 	MasterXaction.cc \
 	MasterXaction.h \
 	Mem.h \
 	tests/stub_mem.cc \
 	mem_node.cc \
 	MemBuf.cc \
 	MemObject.cc \
 	Notes.h \
 	Notes.cc \
 	Packer.cc \
 	Parsing.cc \
 	RemovalPolicy.cc \
 	refresh.h \
 	refresh.cc \
@@ -2983,40 +2968,41 @@
 	tests/testStoreHashIndex.cc \
 	tests/testStoreHashIndex.h \
 	tests/testStoreSupport.cc \
 	tests/testStoreSupport.h \
 	tests/TestSwapDir.cc \
 	tests/TestSwapDir.h \
 	tests/stub_time.cc \
 	url.cc \
 	wordlist.h \
 	wordlist.cc
 
 nodist_tests_testStore_SOURCES= \
 	$(TESTSOURCES) \
 	SquidMath.cc \
 	SquidMath.h \
 	swap_log_op.cc
 
 tests_testStore_LDADD= \
 	libsquid.la \
 	http/libsquid-http.la \
+	parser/libsquid-parser.la \
 	ident/libident.la \
 	acl/libacls.la \
 	acl/libstate.la \
 	acl/libapi.la \
 	base/libbase.la \
 	ip/libip.la \
 	fs/libfs.la \
 	mgr/libmgr.la \
 	ipc/libipc.la \
 	anyp/libanyp.la \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(NETTLELIB) \
 	$(REGEXLIB) \
 	$(SQUID_CPPUNIT_LIBS) \
 	$(SSLLIB) \
 	CommCalls.o \
 	DnsLookupDetails.o \
 	$(COMPAT_LIB) \
@@ -3138,41 +3124,40 @@
 	StoreIOState.cc \
 	StoreMetaUnpacker.cc \
 	$(STOREMETA_SOURCE) \
 	StoreFileSystem.cc \
 	store_io.cc \
 	store_swapout.cc \
 	store_swapmeta.cc \
 	$(UNLINKDSOURCE) \
 	$(WIN32_SOURCE) \
 	event.cc \
 	$(DELAY_POOL_SOURCE) \
 	CacheDigest.h \
 	tests/stub_CacheDigest.cc \
 	ConfigParser.cc \
 	EventLoop.cc \
 	HttpMsg.cc \
 	RemovalPolicy.cc \
 	store_dir.cc \
 	repl_modules.h \
 	store.cc \
-	HttpRequestMethod.cc \
 	store_key_md5.h \
 	store_key_md5.cc \
 	Parsing.cc \
 	ConfigOption.cc \
 	SwapDir.cc \
 	tests/stub_acl.cc \
 	cache_cf.h \
 	YesNoNone.h \
 	tests/stub_cache_cf.cc \
 	tests/stub_helper.cc \
 	cbdata.cc \
 	$(SBUF_SOURCE) \
 	SBufDetailedStats.h \
 	tests/stub_SBufDetailedStats.cc \
 	String.cc \
 	tests/stub_debug.cc \
 	tests/stub_client_side_request.cc \
 	tests/stub_http.cc \
 	tests/stub_libauth.cc \
 	mem_node.cc \
@@ -3212,40 +3197,41 @@
 	refresh.h \
 	refresh.cc \
 	tests/stub_store_client.cc \
 	tools.h \
 	tests/stub_tools.cc \
 	tests/testStoreSupport.cc \
 	tests/testStoreSupport.h \
 	time.cc \
 	wordlist.h \
 	wordlist.cc \
 	$(DISKIO_SOURCE)
 
 nodist_tests_testUfs_SOURCES = \
 	$(TESTSOURCES) \
 	$(DISKIO_GEN_SOURCE) \
 	SquidMath.cc \
 	SquidMath.h \
 	swap_log_op.cc
 tests_testUfs_LDADD = \
 	http/libsquid-http.la \
+	parser/libsquid-parser.la \
 	CommCalls.o \
 	DnsLookupDetails.o \
 	ident/libident.la \
 	acl/libacls.la \
 	acl/libstate.la \
 	acl/libapi.la \
 	libsquid.la \
 	ip/libip.la \
 	fs/libfs.la \
 	mgr/libmgr.la \
 	$(REPL_OBJS) \
 	acl/libacls.la \
 	$(DISK_LIBS) \
 	$(DISK_OS_LIBS) \
 	acl/libapi.la \
 	$(SSL_LIBS) \
 	ipc/libipc.la \
 	comm/libcomm.la \
 	anyp/libanyp.la \
 	base/libbase.la \
@@ -3296,41 +3282,40 @@
 	fd.cc \
 	fde.h \
 	fde.cc \
 	FileMap.h \
 	filemap.cc \
 	HttpHeaderFieldStat.h \
 	HttpBody.h \
 	HttpBody.cc \
 	HttpHdrCc.cc \
 	HttpHdrContRange.cc \
 	HttpHdrRange.cc \
 	HttpHdrSc.cc \
 	HttpHdrScTarget.cc \
 	HttpHeader.h \
 	HttpHeader.cc \
 	HttpHeaderFieldInfo.h \
 	HttpHeaderTools.h \
 	HttpHeaderTools.cc \
 	HttpMsg.cc \
 	HttpReply.cc \
-	HttpRequestMethod.cc \
 	int.h \
 	int.cc \
 	SquidList.h \
 	SquidList.cc \
 	MasterXaction.cc \
 	MasterXaction.h \
 	Mem.h \
 	mem.cc \
 	MemBuf.cc \
 	MemObject.cc \
 	mem_node.cc \
 	Notes.h \
 	Notes.cc \
 	Packer.cc \
 	Parsing.cc \
 	RemovalPolicy.cc \
 	RequestFlags.cc \
 	RequestFlags.h \
 	StatCounters.h \
 	StatCounters.cc \
@@ -3397,40 +3382,41 @@
 	store_rebuild.h \
 	tests/stub_store_rebuild.cc \
 	tests/stub_store_stats.cc \
 	tools.h \
 	tests/stub_tools.cc \
 	time.cc \
 	url.cc \
 	wordlist.h \
 	wordlist.cc \
 	$(DELAY_POOL_SOURCE) \
 	$(DISKIO_SOURCE) \
 	$(UNLINKDSOURCE)
 nodist_tests_testRock_SOURCES = \
 	$(DISKIO_GEN_SOURCE) \
 	swap_log_op.cc \
 	SquidMath.cc \
 	SquidMath.h \
 	$(TESTSOURCES)
 tests_testRock_LDADD = \
 	http/libsquid-http.la \
+	parser/libsquid-parser.la \
 	libsquid.la \
 	comm/libcomm.la \
 	ip/libip.la \
 	fs/libfs.la \
 	$(COMMON_LIBS) \
 	$(REPL_OBJS) \
 	$(DISK_LIBS) \
 	$(DISK_OS_LIBS) \
 	acl/libacls.la \
 	acl/libapi.la \
 	acl/libstate.la \
 	anyp/libanyp.la \
 	eui/libeui.la \
 	$(SSL_LIBS) \
 	ipc/libipc.la \
 	base/libbase.la \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(NETTLELIB) \
@@ -3505,49 +3491,46 @@
 	helper.cc \
 	hier_code.h \
 	$(HTCPSOURCE) \
 	http.cc \
 	HttpBody.h \
 	HttpBody.cc \
 	HttpHeaderFieldStat.h \
 	HttpHdrCc.h \
 	HttpHdrCc.cc \
 	HttpHdrCc.cci \
 	HttpHdrContRange.cc \
 	HttpHdrRange.cc \
 	HttpHdrSc.cc \
 	HttpHdrScTarget.cc \
 	HttpHeader.h \
 	HttpHeader.cc \
 	HttpHeaderFieldInfo.h \
 	HttpHeaderTools.h \
 	HttpHeaderTools.cc \
 	HttpMsg.cc \
-	HttpParser.cc \
-	HttpParser.h \
 	HttpReply.cc \
 	PeerPoolMgr.h \
 	PeerPoolMgr.cc \
 	RequestFlags.h \
 	RequestFlags.cc \
 	HttpRequest.cc \
-	HttpRequestMethod.cc \
 	icp_v2.cc \
 	icp_v3.cc \
 	$(IPC_SOURCE) \
 	ipcache.cc \
 	int.h \
 	int.cc \
 	internal.h \
 	internal.cc \
 	SquidList.h \
 	SquidList.cc \
 	MasterXaction.cc \
 	MasterXaction.h \
 	multicast.h \
 	multicast.cc \
 	Mem.h \
 	tests/stub_mem.cc \
 	mem_node.cc \
 	MemBuf.cc \
 	MemObject.cc \
 	mime.h \
@@ -3636,47 +3619,47 @@
 	url.cc \
 	urn.h \
 	urn.cc \
 	wccp2.h \
 	tests/stub_wccp2.cc \
 	whois.h \
 	tests/stub_whois.cc \
 	FadingCounter.cc \
 	$(WIN32_SOURCE) \
 	wordlist.h \
 	wordlist.cc
 nodist_tests_testURL_SOURCES = \
 	$(BUILT_SOURCES)
 tests_testURL_LDADD = \
 	libsquid.la \
 	clients/libclients.la \
 	servers/libservers.la \
 	helper/libhelper.la \
 	http/libsquid-http.la \
 	ftp/libftp.la \
+	parser/libsquid-parser.la \
 	anyp/libanyp.la \
 	ident/libident.la \
 	acl/libacls.la \
 	eui/libeui.la \
 	acl/libstate.la \
 	acl/libapi.la \
-	parser/libsquid-parser.la \
 	base/libbase.la \
 	ip/libip.la \
 	fs/libfs.la \
 	$(SSL_LIBS) \
 	ipc/libipc.la \
 	mgr/libmgr.la \
 	$(SNMP_LIBS) \
 	icmp/libicmp.la icmp/libicmp-core.la \
 	comm/libcomm.la \
 	log/liblog.la \
 	$(DISK_OS_LIBS) \
 	format/libformat.la \
 	$(REGEXLIB) \
 	$(REPL_OBJS) \
 	$(ADAPTATION_LIBS) \
 	$(ESI_LIBS) \
 	$(top_builddir)/lib/libmisccontainers.la \
 	$(top_builddir)/lib/libmiscencoding.la \
 	$(top_builddir)/lib/libmiscutil.la \
 	$(NETTLELIB) \

=== modified file 'src/MemObject.h'
--- src/MemObject.h	2014-09-13 13:59:43 +0000
+++ src/MemObject.h	2014-09-14 12:23:03 +0000
@@ -1,34 +1,34 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #ifndef SQUID_MEMOBJECT_H
 #define SQUID_MEMOBJECT_H
 
 #include "CommRead.h"
 #include "dlink.h"
-#include "HttpRequestMethod.h"
+#include "http/RequestMethod.h"
 #include "RemovalPolicy.h"
 #include "stmem.h"
 #include "StoreIOBuffer.h"
 #include "StoreIOState.h"
 
 #if USE_DELAY_POOLS
 #include "DelayId.h"
 #endif
 
 typedef void STMCB (void *data, StoreIOBuffer wroteBuffer);
 
 class store_client;
 class HttpRequest;
 class HttpReply;
 
 class MemObject
 {
 
 public:
     static size_t inUseCount();

=== modified file 'src/Store.h'
--- src/Store.h	2014-09-13 13:59:43 +0000
+++ src/Store.h	2014-09-14 12:23:03 +0000
@@ -1,42 +1,43 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #ifndef SQUID_STORE_H
 #define SQUID_STORE_H
 
 /**
  \defgroup StoreAPI  Store API
  \ingroup FileSystems
  */
 
 #include "base/RefCount.h"
 #include "comm/forward.h"
 #include "CommRead.h"
 #include "hash.h"
+#include "http/forward.h"
+#include "http/RequestMethod.h"
 #include "HttpReply.h"
-#include "HttpRequestMethod.h"
 #include "MemObject.h"
 #include "Range.h"
 #include "RemovalPolicy.h"
 #include "StoreIOBuffer.h"
 #include "StoreStats.h"
 
 #if USE_SQUID_ESI
 #include "esi/Element.h"
 #endif
 
 #include <ostream>
 
 class AsyncCall;
 class HttpRequest;
 class Packer;
 class RequestFlags;
 class StoreClient;
 class StoreSearch;
 class SwapDir;
 

=== modified file 'src/acl/Method.h'
--- src/acl/Method.h	2014-09-13 13:59:43 +0000
+++ src/acl/Method.h	2014-09-14 12:23:03 +0000
@@ -1,34 +1,34 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #ifndef SQUID_ACLMETHOD_H
 #define SQUID_ACLMETHOD_H
 
 #include "acl/Strategised.h"
 #include "acl/Strategy.h"
-#include "HttpRequestMethod.h"
+#include "http/RequestMethod.h"
 
 /// \ingroup ACLAPI
 class ACLMethodStrategy : public ACLStrategy<HttpRequestMethod>
 {
 
 public:
     virtual int match (ACLData<MatchType> * &, ACLFilledChecklist *, ACLFlags &);
     virtual bool requiresRequest() const {return true;}
 
     static ACLMethodStrategy *Instance();
 
     /**
      * Not implemented to prevent copies of the instance.
      \par
      * Not private to prevent brain dead g+++ warnings about
      * private constructors with no friends
      */
     ACLMethodStrategy(ACLMethodStrategy const &);
 
 private:

=== modified file 'src/acl/MethodData.cc'
--- src/acl/MethodData.cc	2014-09-13 13:59:43 +0000
+++ src/acl/MethodData.cc	2014-09-14 12:23:03 +0000
@@ -1,35 +1,35 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 28    Access Control */
 
 #include "squid.h"
 #include "acl/Checklist.h"
 #include "acl/MethodData.h"
 #include "cache_cf.h"
-#include "HttpRequestMethod.h"
+#include "http/RequestMethod.h"
 
 int ACLMethodData::ThePurgeCount = 0;
 
 ACLMethodData::ACLMethodData() : values (NULL)
 {}
 
 ACLMethodData::ACLMethodData(ACLMethodData const &old) : values (NULL)
 {
     assert (!old.values);
 }
 
 ACLMethodData::~ACLMethodData()
 {
     if (values)
         delete values;
 }
 
 /// todo make this a pass-by-reference now that HTTPRequestMethods a full class?
 bool
 ACLMethodData::match(HttpRequestMethod toFind)
@@ -48,40 +48,40 @@
 {
     SBufList sl;
     CbDataList<HttpRequestMethod> *data = values;
 
     while (data != NULL) {
         sl.push_back(data->element.image());
         data = data->next;
     }
 
     return sl;
 }
 
 void
 ACLMethodData::parse()
 {
     CbDataList<HttpRequestMethod> **Tail;
     char *t = NULL;
 
     for (Tail = &values; *Tail; Tail = &((*Tail)->next));
     while ((t = strtokFile())) {
-        CbDataList<HttpRequestMethod> *q = new CbDataList<HttpRequestMethod> (HttpRequestMethod(t, NULL));
+        CbDataList<HttpRequestMethod> *q = new CbDataList<HttpRequestMethod> (HttpRequestMethod(t));
         if (q->element == Http::METHOD_PURGE)
             ++ThePurgeCount; // configuration code wants to know
         *(Tail) = q;
         Tail = &q->next;
     }
 }
 
 bool
 ACLMethodData::empty() const
 {
     return values == NULL;
 }
 
 ACLData<HttpRequestMethod> *
 ACLMethodData::clone() const
 {
     assert (!values);
     return new ACLMethodData(*this);
 }

=== modified file 'src/acl/MethodData.h'
--- src/acl/MethodData.h	2014-09-13 13:59:43 +0000
+++ src/acl/MethodData.h	2014-09-14 12:23:03 +0000
@@ -1,35 +1,35 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #ifndef SQUID_ACLMETHODDATA_H
 #define SQUID_ACLMETHODDATA_H
 
 #include "acl/Acl.h"
 #include "acl/Data.h"
 #include "CbDataList.h"
-#include "HttpRequestMethod.h"
+#include "http/RequestMethod.h"
 
 /// \ingroup ACLAPI
 class ACLMethodData : public ACLData<HttpRequestMethod>
 {
 
 public:
     MEMPROXY_CLASS(ACLMethodData);
 
     ACLMethodData();
     ACLMethodData(ACLMethodData const &);
     ACLMethodData &operator= (ACLMethodData const &);
     virtual ~ACLMethodData();
     bool match(HttpRequestMethod);
     virtual SBufList dump() const;
     void parse();
     bool empty() const;
     virtual ACLData<HttpRequestMethod> *clone() const;
 
     CbDataList<HttpRequestMethod> *values;
 

=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc	2014-09-13 13:59:43 +0000
+++ src/cache_cf.cc	2014-09-14 12:23:03 +0000
@@ -16,41 +16,40 @@
 #include "acl/AclSizeLimit.h"
 #include "acl/Gadgets.h"
 #include "acl/MethodData.h"
 #include "acl/Tree.h"
 #include "anyp/PortCfg.h"
 #include "anyp/UriScheme.h"
 #include "AuthReg.h"
 #include "base/RunnersRegistry.h"
 #include "cache_cf.h"
 #include "CachePeer.h"
 #include "CachePeerDomainList.h"
 #include "ConfigParser.h"
 #include "CpuAffinityMap.h"
 #include "DiskIO/DiskIOModule.h"
 #include "eui/Config.h"
 #include "ExternalACL.h"
 #include "format/Format.h"
 #include "ftp/Elements.h"
 #include "globals.h"
 #include "HttpHeaderTools.h"
-#include "HttpRequestMethod.h"
 #include "ident/Config.h"
 #include "ip/Intercept.h"
 #include "ip/QosConfig.h"
 #include "ip/tools.h"
 #include "ipc/Kids.h"
 #include "log/Config.h"
 #include "log/CustomLog.h"
 #include "Mem.h"
 #include "MemBuf.h"
 #include "mgr/ActionPasswordList.h"
 #include "mgr/Registration.h"
 #include "neighbors.h"
 #include "NeighborTypeDomainList.h"
 #include "Parsing.h"
 #include "pconn.h"
 #include "PeerDigest.h"
 #include "PeerPoolMgr.h"
 #include "RefreshPattern.h"
 #include "rfc1738.h"
 #include "SBufList.h"

=== modified file 'src/client_side.cc'
--- src/client_side.cc	2014-10-07 14:11:12 +0000
+++ src/client_side.cc	2014-10-11 10:26:17 +0000
@@ -69,40 +69,41 @@
 #include "client_side_reply.h"
 #include "client_side_request.h"
 #include "ClientRequestContext.h"
 #include "clientStream.h"
 #include "comm.h"
 #include "comm/Connection.h"
 #include "comm/Loops.h"
 #include "comm/Read.h"
 #include "comm/TcpAcceptor.h"
 #include "comm/Write.h"
 #include "CommCalls.h"
 #include "errorpage.h"
 #include "fd.h"
 #include "fde.h"
 #include "fqdncache.h"
 #include "FwdState.h"
 #include "globals.h"
 #include "helper.h"
 #include "helper/Reply.h"
 #include "http.h"
+#include "http/one/RequestParser.h"
 #include "HttpHdrContRange.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "ident/Config.h"
 #include "ident/Ident.h"
 #include "internal.h"
 #include "ipc/FdNotes.h"
 #include "ipc/StartListening.h"
 #include "log/access_log.h"
 #include "Mem.h"
 #include "MemBuf.h"
 #include "MemObject.h"
 #include "mime_header.h"
 #include "parser/Tokenizer.h"
 #include "profiler/Profiler.h"
 #include "rfc1738.h"
 #include "servers/forward.h"
 #include "SquidConfig.h"
 #include "SquidTime.h"
@@ -181,41 +182,40 @@
 #endif
 static CTCB clientLifetimeTimeout;
 #if USE_IDENT
 static IDCB clientIdentDone;
 #endif
 static int clientIsContentLengthValid(HttpRequest * r);
 static int clientIsRequestBodyTooLargeForPolicy(int64_t bodyLength);
 
 static void clientUpdateStatHistCounters(LogTags logType, int svc_time);
 static void clientUpdateStatCounters(LogTags logType);
 static void clientUpdateHierCounters(HierarchyLogEntry *);
 static bool clientPingHasFinished(ping_data const *aPing);
 void prepareLogWithRequestDetails(HttpRequest *, AccessLogEntry::Pointer &);
 #ifndef PURIFY
 static bool connIsUsable(ConnStateData * conn);
 #endif
 static void ClientSocketContextPushDeferredIfNeeded(ClientSocketContext::Pointer deferredRequest, ConnStateData * conn);
 static void clientUpdateSocketStats(LogTags logType, size_t size);
 
 char *skipLeadingSpace(char *aString);
-static void connNoteUseOfBuffer(ConnStateData* conn, size_t byteCount);
 
 clientStreamNode *
 ClientSocketContext::getTail() const
 {
     if (http->client_stream.tail)
         return (clientStreamNode *)http->client_stream.tail->data;
 
     return NULL;
 }
 
 clientStreamNode *
 ClientSocketContext::getClientReplyContext() const
 {
     return (clientStreamNode *)http->client_stream.tail->prev->data;
 }
 
 ConnStateData *
 ClientSocketContext::getConn() const
 {
     return http->getConn();
@@ -1979,359 +1979,307 @@
             char *tmp_uri = static_cast<char*>(xmalloc(strlen(uri) + 1));
             char *q = tmp_uri;
             t = uri;
             while (*t) {
                 if (!xisspace(*t)) {
                     *q = *t;
                     ++q;
                 }
                 ++t;
             }
             *q = '\0';
             http->log_uri = xstrndup(rfc1738_escape_unescaped(tmp_uri), MAX_URL);
             xfree(tmp_uri);
         }
         break;
         }
     }
 }
 
 static void
-prepareAcceleratedURL(ConnStateData * conn, ClientHttpRequest *http, char *url, const char *req_hdr)
+prepareAcceleratedURL(ConnStateData * conn, ClientHttpRequest *http, const Http1::RequestParserPointer &hp)
 {
     int vhost = conn->port->vhost;
     int vport = conn->port->vport;
-    char *host;
-    char ipbuf[MAX_IPSTRLEN];
+    static char ipbuf[MAX_IPSTRLEN];
 
     http->flags.accel = true;
 
     /* BUG: Squid cannot deal with '*' URLs (RFC2616 5.1.2) */
 
-    if (strncasecmp(url, "cache_object://", 15) == 0)
+    static const SBuf cache_object("cache_object://");
+    if (hp->requestUri().startsWith(cache_object))
         return; /* already in good shape */
 
-    if (*url != '/') {
+    // XXX: re-use proper URL parser for this
+    SBuf url = hp->requestUri(); // use full provided URI if we abort
+    do { // use a loop so we can break out of it
+        ::Parser::Tokenizer tok(url);
+        if (tok.remaining()[0] == '/')
+            break;
+
         if (conn->port->vhost)
             return; /* already in good shape */
 
-        /* else we need to ignore the host name */
-        url = strstr(url, "//");
+        // skip the URI scheme
+        static const CharacterSet uriScheme = CharacterSet("URI-scheme","+-.") + CharacterSet::ALPHA + CharacterSet::DIGIT;
+        static const SBuf uriSchemeEnd("://");
+        if (!tok.skipAll(uriScheme) || !tok.skip(uriSchemeEnd))
+            break;
 
-#if SHOULD_REJECT_UNKNOWN_URLS
+        // skip the authority segment
+        // RFC 3986 complex nested ABNF for "authority" boils down to this:
+        static const CharacterSet authority = CharacterSet("authority","-._~%:@[]!$&'()*+,;=") +
+             CharacterSet::HEXDIG + CharacterSet::ALPHA + CharacterSet::DIGIT;
+        if (!tok.skipAll(authority))
+            break;
 
-        if (!url) {
-            hp->request_parse_status = Http::scBadRequest;
-            return conn->abortRequestParsing("error:invalid-request");
-        }
-#endif
+        static const SBuf slashUri("/");
+        const SBuf t = tok.remaining();
+        if (t.isEmpty())
+            url = slashUri;
+        else if (t[0]=='/') // looks like path
+            url = t;
+        else if (t[0]=='?' || t[0]=='#') { // looks like query or fragment. fix '/'
+            url = slashUri;
+            url.append(t);
+        } // else do nothing. invalid path
 
-        if (url)
-            url = strchr(url + 2, '/');
+    } while(false);
 
-        if (!url)
-            url = (char *) "/";
+#if SHOULD_REJECT_UNKNOWN_URLS
+    // reject URI which are not well-formed even after the processing above
+    if (url[0] != '/') {
+        hp->request_parse_status = Http::scBadRequest;
+        return conn->abortRequestParsing("error:invalid-request");
     }
+#endif
 
     if (vport < 0)
         vport = http->getConn()->clientConnection->local.port();
 
     const bool switchedToHttps = conn->switchedToHttps();
     const bool tryHostHeader = vhost || switchedToHttps;
-    if (tryHostHeader && (host = mime_get_header(req_hdr, "Host")) != NULL) {
+    char *host = NULL;
+    if (tryHostHeader && (host = hp->getHeaderField("Host"))) {
         debugs(33, 5, "ACCEL VHOST REWRITE: vhost=" << host << " + vport=" << vport);
         char thost[256];
         if (vport > 0) {
             thost[0] = '\0';
             char *t = NULL;
             if (host[strlen(host)] != ']' && (t = strrchr(host,':')) != NULL) {
                 strncpy(thost, host, (t-host));
                 snprintf(thost+(t-host), sizeof(thost)-(t-host), ":%d", vport);
                 host = thost;
             } else if (!t) {
                 snprintf(thost, sizeof(thost), "%s:%d",host, vport);
                 host = thost;
             }
         } // else nothing to alter port-wise.
-        int url_sz = strlen(url) + 32 + Config.appendDomainLen +
-                     strlen(host);
+        const int url_sz = hp->requestUri().length() + 32 + Config.appendDomainLen + strlen(host);
         http->uri = (char *)xcalloc(url_sz, 1);
         const char *protocol = switchedToHttps ?
                                "https" : AnyP::UriScheme(conn->port->transport.protocol).c_str();
-        snprintf(http->uri, url_sz, "%s://%s%s", protocol, host, url);
-        debugs(33, 5, "ACCEL VHOST REWRITE: '" << http->uri << "'");
+        snprintf(http->uri, url_sz, "%s://%s" SQUIDSBUFPH, protocol, host, SQUIDSBUFPRINT(url));
+        debugs(33, 5, "ACCEL VHOST REWRITE: " << http->uri);
     } else if (conn->port->defaultsite /* && !vhost */) {
         debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: defaultsite=" << conn->port->defaultsite << " + vport=" << vport);
-        int url_sz = strlen(url) + 32 + Config.appendDomainLen +
+        const int url_sz = hp->requestUri().length() + 32 + Config.appendDomainLen +
                      strlen(conn->port->defaultsite);
         http->uri = (char *)xcalloc(url_sz, 1);
         char vportStr[32];
         vportStr[0] = '\0';
         if (vport > 0) {
             snprintf(vportStr, sizeof(vportStr),":%d",vport);
         }
-        snprintf(http->uri, url_sz, "%s://%s%s%s",
-                 AnyP::UriScheme(conn->port->transport.protocol).c_str(), conn->port->defaultsite, vportStr, url);
-        debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: '" << http->uri <<"'");
+        snprintf(http->uri, url_sz, "%s://%s%s" SQUIDSBUFPH,
+                 AnyP::UriScheme(conn->port->transport.protocol).c_str(), conn->port->defaultsite, vportStr, SQUIDSBUFPRINT(url));
+        debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: " << http->uri);
     } else if (vport > 0 /* && (!vhost || no Host:) */) {
         debugs(33, 5, "ACCEL VPORT REWRITE: *_port IP + vport=" << vport);
         /* Put the local socket IP address as the hostname, with whatever vport we found  */
-        int url_sz = strlen(url) + 32 + Config.appendDomainLen;
+        const int url_sz = hp->requestUri().length() + 32 + Config.appendDomainLen;
         http->uri = (char *)xcalloc(url_sz, 1);
         http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN);
-        snprintf(http->uri, url_sz, "%s://%s:%d%s",
+        snprintf(http->uri, url_sz, "%s://%s:%d" SQUIDSBUFPH,
                  AnyP::UriScheme(conn->port->transport.protocol).c_str(),
-                 ipbuf, vport, url);
-        debugs(33, 5, "ACCEL VPORT REWRITE: '" << http->uri << "'");
+                 ipbuf, vport, SQUIDSBUFPRINT(url));
+        debugs(33, 5, "ACCEL VPORT REWRITE: " << http->uri);
     }
 }
 
 static void
-prepareTransparentURL(ConnStateData * conn, ClientHttpRequest *http, char *url, const char *req_hdr)
+prepareTransparentURL(ConnStateData * conn, ClientHttpRequest *http, const Http1::RequestParserPointer &hp)
 {
-    char *host;
-    char ipbuf[MAX_IPSTRLEN];
+    static char ipbuf[MAX_IPSTRLEN];
 
-    if (*url != '/')
+    // TODO Must() on URI !empty when the parser supports throw. For now avoid assert().
+    if (!hp->requestUri().isEmpty() && hp->requestUri()[0] != '/')
         return; /* already in good shape */
 
     /* BUG: Squid cannot deal with '*' URLs (RFC2616 5.1.2) */
 
-    if ((host = mime_get_header(req_hdr, "Host")) != NULL) {
-        int url_sz = strlen(url) + 32 + Config.appendDomainLen +
+    if (const char *host = hp->getHeaderField("Host")) {
+        const int url_sz = hp->requestUri().length() + 32 + Config.appendDomainLen +
                      strlen(host);
         http->uri = (char *)xcalloc(url_sz, 1);
-        snprintf(http->uri, url_sz, "%s://%s%s", AnyP::UriScheme(conn->port->transport.protocol).c_str(), host, url);
-        debugs(33, 5, "TRANSPARENT HOST REWRITE: '" << http->uri <<"'");
+        snprintf(http->uri, url_sz, "%s://%s" SQUIDSBUFPH,
+             AnyP::UriScheme(conn->port->transport.protocol).c_str(), host, SQUIDSBUFPRINT(hp->requestUri()));
+        debugs(33, 5, "TRANSPARENT HOST REWRITE: " << http->uri);
     } else {
         /* Put the local socket IP address as the hostname.  */
-        int url_sz = strlen(url) + 32 + Config.appendDomainLen;
+        const int url_sz = hp->requestUri().length() + 32 + Config.appendDomainLen;
         http->uri = (char *)xcalloc(url_sz, 1);
         http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN);
-        snprintf(http->uri, url_sz, "%s://%s:%d%s",
+        snprintf(http->uri, url_sz, "%s://%s:%d" SQUIDSBUFPH,
                  AnyP::UriScheme(http->getConn()->port->transport.protocol).c_str(),
-                 ipbuf, http->getConn()->clientConnection->local.port(), url);
-        debugs(33, 5, "TRANSPARENT REWRITE: '" << http->uri << "'");
+                 ipbuf, http->getConn()->clientConnection->local.port(), SQUIDSBUFPRINT(hp->requestUri()));
+        debugs(33, 5, "TRANSPARENT REWRITE: " << http->uri);
     }
 }
 
 /** Parse an HTTP request
  *
  *  \note Sets result->flags.parsed_ok to 0 if failed to parse the request,
  *          to 1 if the request was correctly parsed.
  *  \param[in] csd a ConnStateData. The caller must make sure it is not null
- *  \param[in] hp an HttpParser
+ *  \param[in] hp an Http1::RequestParser
  *  \param[out] mehtod_p will be set as a side-effect of the parsing.
  *          Pointed-to value will be set to Http::METHOD_NONE in case of
  *          parsing failure
  *  \param[out] http_ver will be set as a side-effect of the parsing
  *  \return NULL on incomplete requests,
  *          a ClientSocketContext structure on success or failure.
  */
 ClientSocketContext *
-parseHttpRequest(ConnStateData *csd, HttpParser *hp, HttpRequestMethod * method_p, Http::ProtocolVersion *http_ver)
+parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
 {
-    char *req_hdr = NULL;
-    char *end;
-    size_t req_sz;
-    ClientHttpRequest *http;
-    ClientSocketContext *result;
-    StoreIOBuffer tempBuffer;
-    int r;
-
-    /* pre-set these values to make aborting simpler */
-    *method_p = Http::METHOD_NONE;
-
-    /* NP: don't be tempted to move this down or remove again.
-     * It's the only DDoS protection old-String has against long URL */
-    if ( hp->bufsiz <= 0) {
-        debugs(33, 5, "Incomplete request, waiting for end of request line");
-        return NULL;
-    } else if ( (size_t)hp->bufsiz >= Config.maxRequestHeaderSize && headersEnd(hp->buf, Config.maxRequestHeaderSize) == 0) {
-        debugs(33, 5, "parseHttpRequest: Too large request");
-        hp->request_parse_status = Http::scHeaderTooLarge;
-        return csd->abortRequestParsing("error:request-too-large");
-    }
-
-    /* Attempt to parse the first line; this'll define the method, url, version and header begin */
-    r = HttpParserParseReqLine(hp);
-
-    if (r == 0) {
-        debugs(33, 5, "Incomplete request, waiting for end of request line");
-        return NULL;
-    }
-
-    if (r == -1) {
-        return csd->abortRequestParsing("error:invalid-request");
-    }
+    /* Attempt to parse the first line; this will define where the method, url, version and header begin */
+    {
+        const bool parsedOk = hp->parse(csd->in.buf);
 
-    /* Request line is valid here .. */
-    *http_ver = Http::ProtocolVersion(hp->req.v_maj, hp->req.v_min);
+        // sync the buffers after parsing.
+        csd->in.buf = hp->remaining();
 
-    /* This call scans the entire request, not just the headers */
-    if (hp->req.v_maj > 0) {
-        if ((req_sz = headersEnd(hp->buf, hp->bufsiz)) == 0) {
-            debugs(33, 5, "Incomplete request, waiting for end of headers");
+        if (hp->needsMoreData()) {
+            debugs(33, 5, "Incomplete request, waiting for end of request line");
             return NULL;
         }
-    } else {
-        debugs(33, 3, "parseHttpRequest: Missing HTTP identifier");
-        req_sz = HttpParserReqSz(hp);
-    }
-
-    /* We know the whole request is in hp->buf now */
-
-    assert(req_sz <= (size_t) hp->bufsiz);
-
-    /* Will the following be true with HTTP/0.9 requests? probably not .. */
-    /* So the rest of the code will need to deal with '0'-byte headers (ie, none, so don't try parsing em) */
-    assert(req_sz > 0);
 
-    hp->hdr_end = req_sz - 1;
+        if (!parsedOk) {
+            if (hp->request_parse_status == Http::scRequestHeaderFieldsTooLarge || hp->request_parse_status == Http::scUriTooLong)
+                return csd->abortRequestParsing("error:request-too-large");
 
-    hp->hdr_start = hp->req.end + 1;
-
-    /* Enforce max_request_size */
-    if (req_sz >= Config.maxRequestHeaderSize) {
-        debugs(33, 5, "parseHttpRequest: Too large request");
-        hp->request_parse_status = Http::scHeaderTooLarge;
-        return csd->abortRequestParsing("error:request-too-large");
+            return csd->abortRequestParsing("error:invalid-request");
+        }
     }
 
-    /* Set method_p */
-    *method_p = HttpRequestMethod(&hp->buf[hp->req.m_start], &hp->buf[hp->req.m_end]+1);
+    /* We know the whole request is in parser now */
+    debugs(11, 2, "HTTP Client " << csd->clientConnection);
+    debugs(11, 2, "HTTP Client REQUEST:\n---------\n" <<
+           hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol() << "\n" <<
+           hp->mimeHeader() <<
+           "\n----------");
 
     /* deny CONNECT via accelerated ports */
-    if (*method_p == Http::METHOD_CONNECT && csd->port != NULL && csd->port->flags.accelSurrogate) {
+    if (hp->method() == Http::METHOD_CONNECT && csd->port != NULL && csd->port->flags.accelSurrogate) {
         debugs(33, DBG_IMPORTANT, "WARNING: CONNECT method received on " << csd->port->transport.protocol << " Accelerator port " << csd->port->s.port());
-        /* XXX need a way to say "this many character length string" */
-        debugs(33, DBG_IMPORTANT, "WARNING: for request: " << hp->buf);
+        debugs(33, DBG_IMPORTANT, "WARNING: for request: " << hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol());
         hp->request_parse_status = Http::scMethodNotAllowed;
         return csd->abortRequestParsing("error:method-not-allowed");
     }
 
-    if (*method_p == Http::METHOD_NONE) {
-        /* XXX need a way to say "this many character length string" */
-        debugs(33, DBG_IMPORTANT, "clientParseRequestMethod: Unsupported method in request '" << hp->buf << "'");
+    if (hp->method() == Http::METHOD_NONE) {
+        debugs(33, DBG_IMPORTANT, "WARNING: Unsupported method: " << hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol());
         hp->request_parse_status = Http::scMethodNotAllowed;
         return csd->abortRequestParsing("error:unsupported-request-method");
     }
 
-    /*
-     * Process headers after request line
-     * TODO: Use httpRequestParse here.
-     */
-    /* XXX this code should be modified to take a const char * later! */
-    req_hdr = (char *) hp->buf + hp->req.end + 1;
-
-    debugs(33, 3, "parseHttpRequest: req_hdr = {" << req_hdr << "}");
-
-    end = (char *) hp->buf + hp->hdr_end;
-
-    debugs(33, 3, "parseHttpRequest: end = {" << end << "}");
-
-    debugs(33, 3, "parseHttpRequest: prefix_sz = " <<
-           (int) HttpParserRequestLen(hp) << ", req_line_sz = " <<
-           HttpParserReqSz(hp));
+    // Process headers after request line
+    debugs(33, 3, "complete request received. " <<
+           "prefix_sz = " << hp->messageHeaderSize() <<
+           ", request-line-size=" << hp->firstLineSize() <<
+           ", mime-header-size=" << hp->headerBlockSize() <<
+           ", mime header block:\n" << hp->mimeHeader() << "\n----------");
 
     /* Ok, all headers are received */
-    http = new ClientHttpRequest(csd);
+    ClientHttpRequest *http = new ClientHttpRequest(csd);
 
-    http->req_sz = HttpParserRequestLen(hp);
-    result = new ClientSocketContext(csd->clientConnection, http);
+    http->req_sz = hp->messageHeaderSize();
+    ClientSocketContext *result = new ClientSocketContext(csd->clientConnection, http);
+
+    StoreIOBuffer tempBuffer;
     tempBuffer.data = result->reqbuf;
     tempBuffer.length = HTTP_REQBUF_SZ;
 
     ClientStreamData newServer = new clientReplyContext(http);
     ClientStreamData newClient = result;
     clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach,
                      clientReplyStatus, newServer, clientSocketRecipient,
                      clientSocketDetach, newClient, tempBuffer);
 
-    debugs(33, 5, "parseHttpRequest: Request Header is\n" <<(hp->buf) + hp->hdr_start);
-
     /* set url */
-    /*
-     * XXX this should eventually not use a malloc'ed buffer; the transformation code
-     * below needs to be modified to not expect a mutable nul-terminated string.
-     */
-    char *url = (char *)xmalloc(hp->req.u_end - hp->req.u_start + 16);
-
-    memcpy(url, hp->buf + hp->req.u_start, hp->req.u_end - hp->req.u_start + 1);
-
-    url[hp->req.u_end - hp->req.u_start + 1] = '\0';
-
-#if THIS_VIOLATES_HTTP_SPECS_ON_URL_TRANSFORMATION
-
-    if ((t = strchr(url, '#')))	/* remove HTML anchors */
-        *t = '\0';
-
-#endif
+    // XXX: c_str() does re-allocate but here replaces explicit malloc/free.
+    // when internalCheck() accepts SBuf removing this will be a net gain for performance.
+    const char *url = SBuf(hp->requestUri()).c_str();
 
     debugs(33,5, HERE << "repare absolute URL from " <<
            (csd->transparent()?"intercept":(csd->port->flags.accelSurrogate ? "accel":"")));
     /* Rewrite the URL in transparent or accelerator mode */
     /* NP: there are several cases to traverse here:
      *  - standard mode (forward proxy)
      *  - transparent mode (TPROXY)
      *  - transparent mode with failures
      *  - intercept mode (NAT)
      *  - intercept mode with failures
      *  - accelerator mode (reverse proxy)
      *  - internal URL
      *  - mixed combos of the above with internal URL
      *  - remote interception with PROXY protocol
      *  - remote reverse-proxy with PROXY protocol
      */
     if (csd->transparent()) {
         /* intercept or transparent mode, properly working with no failures */
-        prepareTransparentURL(csd, http, url, req_hdr);
+        prepareTransparentURL(csd, http, hp);
 
     } else if (internalCheck(url)) {
         /* internal URL mode */
         /* prepend our name & port */
         http->uri = xstrdup(internalLocalUri(NULL, url));
         // We just re-wrote the URL. Must replace the Host: header.
         //  But have not parsed there yet!! flag for local-only handling.
         http->flags.internal = true;
 
     } else if (csd->port->flags.accelSurrogate || csd->switchedToHttps()) {
         /* accelerator mode */
-        prepareAcceleratedURL(csd, http, url, req_hdr);
+        prepareAcceleratedURL(csd, http, hp);
     }
 
     if (!http->uri) {
         /* No special rewrites have been applied above, use the
          * requested url. may be rewritten later, so make extra room */
-        int url_sz = strlen(url) + Config.appendDomainLen + 5;
+        int url_sz = hp->requestUri().length() + Config.appendDomainLen + 5;
         http->uri = (char *)xcalloc(url_sz, 1);
         strcpy(http->uri, url);
     }
 
-    debugs(33, 5, "parseHttpRequest: Complete request received");
-
-    // XXX: crop this dump at the end of headers. No need for extras
-    debugs(11, 2, "HTTP Client " << csd->clientConnection);
-    debugs(11, 2, "HTTP Client REQUEST:\n---------\n" << (hp->buf) + hp->req.m_start << "\n----------");
-
     result->flags.parsed_ok = 1;
-    xfree(url);
     return result;
 }
 
 bool
 ConnStateData::In::maybeMakeSpaceAvailable()
 {
     if (buf.spaceSize() < 2) {
         const SBuf::size_type haveCapacity = buf.length() + buf.spaceSize();
         if (haveCapacity >= Config.maxRequestBufferSize) {
             debugs(33, 4, "request buffer full: client_request_buffer_max_size=" << Config.maxRequestBufferSize);
             return false;
         }
         if (haveCapacity == 0) {
             // haveCapacity is based on the SBuf visible window of the MemBlob buffer, which may fill up.
             // at which point bump the buffer back to default. This allocates a new MemBlob with any un-parsed bytes.
             buf.reserveCapacity(CLIENT_REQ_BUF_SZ);
         } else {
             const SBuf::size_type wantCapacity = min(static_cast<SBuf::size_type>(Config.maxRequestBufferSize), haveCapacity*2);
             buf.reserveCapacity(wantCapacity);
         }
@@ -2373,47 +2321,40 @@
             return 1;
         } else if (!Config.onoff.half_closed_clients) {
             /* admin doesn't want to support half-closed client sockets */
             debugs(33, 3, HERE << clientConnection << " aborted (half_closed_clients disabled)");
             notifyAllContexts(0); // no specific error implies abort
             return 1;
         }
     }
 
     return 0;
 }
 
 void
 ConnStateData::consumeInput(const size_t byteCount)
 {
     assert(byteCount > 0 && byteCount <= in.buf.length());
     in.buf.consume(byteCount);
     debugs(33, 5, "in.buf has " << in.buf.length() << " unused bytes");
 }
 
-// TODO: Remove when renaming ConnStateData
-void
-connNoteUseOfBuffer(ConnStateData* conn, size_t byteCount)
-{
-    conn->consumeInput(byteCount);
-}
-
 void
 ConnStateData::clientAfterReadingRequests()
 {
     // Were we expecting to read more request body from half-closed connection?
     if (mayNeedToReadMoreBody() && commIsHalfClosed(clientConnection->fd)) {
         debugs(33, 3, HERE << "truncated body: closing half-closed " << clientConnection);
         clientConnection->close();
         return;
     }
 
     if (flags.readMore)
         readSomeData();
 }
 
 void
 ConnStateData::quitAfterError(HttpRequest *request)
 {
     // From HTTP p.o.v., we do not have to close after every error detected
     // at the client-side, but many such errors do require closure and the
     // client-side code is bad at handling errors so we play it safe.
@@ -2499,149 +2440,150 @@
                 }
                 repContext->setReplyToError(request->method, err);
                 assert(context->http->out.offset == 0);
                 context->pullData();
                 return true;
             }
         }
     }
 
     return false;
 }
 #endif // USE_OPENSSL
 
 static void
 clientProcessRequestFinished(ConnStateData *conn, const HttpRequest::Pointer &request)
 {
     /*
      * DPW 2007-05-18
      * Moved the TCP_RESET feature from clientReplyContext::sendMoreData
      * to here because calling comm_reset_close() causes http to
-     * be freed and the above connNoteUseOfBuffer() would hit an
-     * assertion, not to mention that we were accessing freed memory.
+     * be freed before accessing.
      */
     if (request != NULL && request->flags.resetTcp && Comm::IsConnOpen(conn->clientConnection)) {
         debugs(33, 3, HERE << "Sending TCP RST on " << conn->clientConnection);
         conn->flags.readMore = false;
         comm_reset_close(conn->clientConnection);
     }
 }
 
 void
-clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *context, const HttpRequestMethod& method, Http::ProtocolVersion http_ver)
+clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp, ClientSocketContext *context)
 {
     ClientHttpRequest *http = context->http;
     HttpRequest::Pointer request;
-    bool notedUseOfBuffer = false;
     bool chunked = false;
     bool mustReplyToOptions = false;
     bool unsupportedTe = false;
     bool expectBody = false;
 
     // temporary hack to avoid splitting this huge function with sensitive code
-    const bool isFtp = !hp;
+    const bool isFtp = (hp == NULL);
+    const HttpRequestMethod &method = !isFtp ? hp->method() : Http::METHOD_NONE; // XXX: or should this be GET ?
     if (isFtp) {
         // In FTP, case, we already have the request parsed and checked, so we
         // only need to go through the final body/conn setup to doCallouts().
         assert(http->request);
         request = http->request;
-        notedUseOfBuffer = true;
     } else {
 
         if (context->flags.parsed_ok == 0) {
             clientStreamNode *node = context->getClientReplyContext();
-            debugs(33, 2, "clientProcessRequest: Invalid Request");
+            debugs(33, 2, "Invalid Request");
             conn->quitAfterError(NULL);
             // setLogUri should called before repContext->setReplyToError
-            setLogUri(http, http->uri,  true);
+            setLogUri(http, http->uri, true);
             clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
-            assert (repContext);
+            assert(repContext);
+
+            // determine which error page templates to use for specific parsing errors
+            err_type errPage = ERR_INVALID_REQ;
             switch (hp->request_parse_status) {
-            case Http::scHeaderTooLarge:
-                repContext->setReplyToError(ERR_TOO_BIG, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL);
+            case Http::scRequestHeaderFieldsTooLarge:
+                // fall through to next case
+            case Http::scUriTooLong:
+                errPage = ERR_TOO_BIG;
                 break;
             case Http::scMethodNotAllowed:
-                repContext->setReplyToError(ERR_UNSUP_REQ, Http::scMethodNotAllowed, method, http->uri,
-                                            conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL);
+                errPage = ERR_UNSUP_REQ;
+                break;
+            case Http::scHttpVersionNotSupported:
+                errPage = ERR_UNSUP_HTTPVERSION;
                 break;
             default:
-                repContext->setReplyToError(ERR_INVALID_REQ, hp->request_parse_status, method, http->uri,
-                                            conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL);
+                // use default ERR_INVALID_REQ set above.
+                break;
             }
+            repContext->setReplyToError(errPage, hp->request_parse_status, hp->method(), http->uri,
+                                        conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL);
             assert(context->http->out.offset == 0);
             context->pullData();
-            connNoteUseOfBuffer(conn, http->req_sz);
             return;
         }
 
-        if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, method)) == NULL) {
+        if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, hp->method())) == NULL) {
             clientStreamNode *node = context->getClientReplyContext();
             debugs(33, 5, "Invalid URL: " << http->uri);
             conn->quitAfterError(request.getRaw());
             // setLogUri should called before repContext->setReplyToError
-            setLogUri(http, http->uri,  true);
+            setLogUri(http, http->uri, true);
             clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
-            assert (repContext);
-            repContext->setReplyToError(ERR_INVALID_URL, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL);
+            assert(repContext);
+            repContext->setReplyToError(ERR_INVALID_URL, Http::scBadRequest, hp->method(), http->uri, conn->clientConnection->remote, NULL, NULL, NULL);
             assert(context->http->out.offset == 0);
             context->pullData();
-            connNoteUseOfBuffer(conn, http->req_sz);
             return;
         }
 
         /* RFC 2616 section 10.5.6 : handle unsupported HTTP major versions cleanly. */
         /* We currently only support 0.9, 1.0, 1.1 properly */
         /* TODO: move HTTP-specific processing into servers/HttpServer and such */
-        if ( (http_ver.major == 0 && http_ver.minor != 9) ||
-                (http_ver.major > 1) ) {
+        if ( (hp->messageProtocol().major == 0 && hp->messageProtocol().minor != 9) ||
+                (hp->messageProtocol().major > 1) ) {
 
             clientStreamNode *node = context->getClientReplyContext();
-            debugs(33, 5, "Unsupported HTTP version discovered. :\n" << HttpParserHdrBuf(hp));
+            debugs(33, 5, "Unsupported HTTP version discovered. :\n" << hp->messageProtocol());
             conn->quitAfterError(request.getRaw());
             // setLogUri should called before repContext->setReplyToError
             setLogUri(http, http->uri,  true);
             clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
             assert (repContext);
             repContext->setReplyToError(ERR_UNSUP_HTTPVERSION, Http::scHttpVersionNotSupported, method, http->uri,
-                                        conn->clientConnection->remote, NULL, HttpParserHdrBuf(hp), NULL);
+                                        conn->clientConnection->remote, NULL, NULL, NULL);
             assert(context->http->out.offset == 0);
             context->pullData();
-            connNoteUseOfBuffer(conn, http->req_sz);
             clientProcessRequestFinished(conn, request);
             return;
         }
 
         /* compile headers */
-        /* we should skip request line! */
-        /* XXX should actually know the damned buffer size here */
-        if (http_ver.major >= 1 && !request->parseHeader(HttpParserHdrBuf(hp), HttpParserHdrSz(hp))) {
+        if (hp->messageProtocol().major >= 1 && !request->parseHeader(*hp)) {
             clientStreamNode *node = context->getClientReplyContext();
-            debugs(33, 5, "Failed to parse request headers:\n" << HttpParserHdrBuf(hp));
+            debugs(33, 5, "Failed to parse request headers:\n" << hp->mimeHeader());
             conn->quitAfterError(request.getRaw());
             // setLogUri should called before repContext->setReplyToError
-            setLogUri(http, http->uri,  true);
+            setLogUri(http, http->uri, true);
             clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
-            assert (repContext);
-            repContext->setReplyToError(ERR_INVALID_REQ, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL);
+            assert(repContext);
+            repContext->setReplyToError(ERR_INVALID_REQ, Http::scBadRequest, hp->method(), http->uri, conn->clientConnection->remote, NULL, NULL, NULL);
             assert(context->http->out.offset == 0);
             context->pullData();
-            connNoteUseOfBuffer(conn, http->req_sz);
             clientProcessRequestFinished(conn, request);
             return;
         }
     }
 
     // Some blobs below are still HTTP-specific, but we would have to rewrite
     // this entire function to remove them from the FTP code path. Connection
     // setup and body_pipe preparation blobs are needed for FTP.
 
     request->clientConnectionManager = conn;
 
     request->flags.accelerated = http->flags.accel;
     request->flags.sslBumped=conn->switchedToHttps();
     request->flags.ignoreCc = conn->port->ignore_cc;
     // TODO: decouple http->flags.accel from request->flags.sslBumped
     request->flags.noDirect = (request->flags.accelerated && !request->flags.sslBumped) ?
                               !conn->port->allow_direct : 0;
 #if USE_AUTH
     if (request->flags.sslBumped) {
         if (conn->getAuth() != NULL)
@@ -2680,191 +2622,172 @@
             request->port = getMyPort();
             http->flags.internal = true;
         } else
             debugs(33, 2, "internal URL found: " << request->url.getScheme() << "://" << request->GetHost() <<
                    ':' << request->port << " (not this proxy)");
     }
 
     if (http->flags.internal)
         request->login[0] = '\0';
 
     request->flags.internal = http->flags.internal;
     setLogUri (http, urlCanonicalClean(request.getRaw()));
     request->client_addr = conn->clientConnection->remote; // XXX: remove reuest->client_addr member.
 #if FOLLOW_X_FORWARDED_FOR
     // indirect client gets stored here because it is an HTTP header result (from X-Forwarded-For:)
     // not a details about teh TCP connection itself
     request->indirect_client_addr = conn->clientConnection->remote;
 #endif /* FOLLOW_X_FORWARDED_FOR */
     request->my_addr = conn->clientConnection->local;
     request->myportname = conn->port->name;
-    request->http_ver = http_ver;
+
+    if (!isFtp) {
+        // XXX: for non-HTTP messages instantiate a different HttpMsg child type
+        // for now Squid only supports HTTP requests
+        const AnyP::ProtocolVersion &http_ver = hp->messageProtocol();
+        assert(request->http_ver.protocol == http_ver.protocol);
+        request->http_ver.major = http_ver.major;
+        request->http_ver.minor = http_ver.minor;
+    }
 
     // Link this HttpRequest to ConnStateData relatively early so the following complex handling can use it
     // TODO: this effectively obsoletes a lot of conn->FOO copying. That needs cleaning up later.
     request->clientConnectionManager = conn;
 
     if (request->header.chunked()) {
         chunked = true;
     } else if (request->header.has(HDR_TRANSFER_ENCODING)) {
         const String te = request->header.getList(HDR_TRANSFER_ENCODING);
         // HTTP/1.1 requires chunking to be the last encoding if there is one
         unsupportedTe = te.size() && te != "identity";
     } // else implied identity coding
 
     mustReplyToOptions = (method == Http::METHOD_OPTIONS) &&
                          (request->header.getInt64(HDR_MAX_FORWARDS) == 0);
     if (!urlCheckRequest(request.getRaw()) || mustReplyToOptions || unsupportedTe) {
         clientStreamNode *node = context->getClientReplyContext();
         conn->quitAfterError(request.getRaw());
         clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
         assert (repContext);
         repContext->setReplyToError(ERR_UNSUP_REQ, Http::scNotImplemented, request->method, NULL,
                                     conn->clientConnection->remote, request.getRaw(), NULL, NULL);
         assert(context->http->out.offset == 0);
         context->pullData();
-        connNoteUseOfBuffer(conn, http->req_sz);
         clientProcessRequestFinished(conn, request);
         return;
     }
 
     if (!chunked && !clientIsContentLengthValid(request.getRaw())) {
         clientStreamNode *node = context->getClientReplyContext();
         clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
         assert (repContext);
         conn->quitAfterError(request.getRaw());
         repContext->setReplyToError(ERR_INVALID_REQ,
                                     Http::scLengthRequired, request->method, NULL,
                                     conn->clientConnection->remote, request.getRaw(), NULL, NULL);
         assert(context->http->out.offset == 0);
         context->pullData();
-        connNoteUseOfBuffer(conn, http->req_sz);
         clientProcessRequestFinished(conn, request);
         return;
     }
 
     if (request->header.has(HDR_EXPECT)) {
         const String expect = request->header.getList(HDR_EXPECT);
         const bool supportedExpect = (expect.caseCmp("100-continue") == 0);
         if (!supportedExpect) {
             clientStreamNode *node = context->getClientReplyContext();
             clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
             assert (repContext);
             conn->quitAfterError(request.getRaw());
             repContext->setReplyToError(ERR_INVALID_REQ, Http::scExpectationFailed, request->method, http->uri,
                                         conn->clientConnection->remote, request.getRaw(), NULL, NULL);
             assert(context->http->out.offset == 0);
             context->pullData();
-            connNoteUseOfBuffer(conn, http->req_sz);
             clientProcessRequestFinished(conn, request);
             return;
         }
     }
 
     if (!isFtp) {
         http->request = request.getRaw();
         HTTPMSGLOCK(http->request);
     }
 
     clientSetKeepaliveFlag(http);
     // Let tunneling code be fully responsible for CONNECT requests
     if (http->request->method == Http::METHOD_CONNECT) {
         context->mayUseConnection(true);
         conn->flags.readMore = false;
-
-        // consume header early so that tunnel gets just the body
-        connNoteUseOfBuffer(conn, http->req_sz);
-        notedUseOfBuffer = true;
     }
 
 #if USE_OPENSSL
     if (conn->switchedToHttps() && conn->serveDelayedError(context)) {
-        if (!notedUseOfBuffer)
-            connNoteUseOfBuffer(conn, http->req_sz);
         clientProcessRequestFinished(conn, request);
         return;
     }
 #endif
 
     /* Do we expect a request-body? */
     expectBody = chunked || request->content_length > 0;
     if (!context->mayUseConnection() && expectBody) {
         request->body_pipe = conn->expectRequestBody(
                                  chunked ? -1 : request->content_length);
 
-        if (!isFtp) {
-            // consume header early so that body pipe gets just the body
-            connNoteUseOfBuffer(conn, http->req_sz);
-            notedUseOfBuffer = true;
-        }
-
         /* Is it too large? */
         if (!chunked && // if chunked, we will check as we accumulate
                 clientIsRequestBodyTooLargeForPolicy(request->content_length)) {
             clientStreamNode *node = context->getClientReplyContext();
             clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
             assert (repContext);
             conn->quitAfterError(request.getRaw());
             repContext->setReplyToError(ERR_TOO_BIG,
                                         Http::scPayloadTooLarge, Http::METHOD_NONE, NULL,
                                         conn->clientConnection->remote, http->request, NULL, NULL);
             assert(context->http->out.offset == 0);
             context->pullData();
             clientProcessRequestFinished(conn, request);
             return;
         }
 
         if (!isFtp) {
             // We may stop producing, comm_close, and/or call setReplyToError()
             // below, so quit on errors to avoid http->doCallouts()
             if (!conn->handleRequestBodyData()) {
                 clientProcessRequestFinished(conn, request);
                 return;
             }
 
             if (!request->body_pipe->productionEnded()) {
                 debugs(33, 5, "need more request body");
                 context->mayUseConnection(true);
                 assert(conn->flags.readMore);
             }
         }
     }
 
     http->calloutContext = new ClientRequestContext(http);
 
     http->doCallouts();
 
-    if (!notedUseOfBuffer)
-        connNoteUseOfBuffer(conn, http->req_sz);
-
     clientProcessRequestFinished(conn, request);
 }
 
-static void
-connStripBufferWhitespace (ConnStateData * conn)
-{
-    // XXX: kill this whole function.
-    while (!conn->in.buf.isEmpty() && xisspace(conn->in.buf.at(0))) {
-        conn->in.buf.consume(1);
-    }
-}
-
 int
 ConnStateData::pipelinePrefetchMax() const
 {
     return Config.pipeline_max_prefetch;
 }
 
 /**
  * Limit the number of concurrent requests.
  * \return true  when there are available position(s) in the pipeline queue for another request.
  * \return false when the pipeline queue is full or disabled.
  */
 bool
 ConnStateData::concurrentRequestQueueFilled() const
 {
     const int existingRequestCount = getConcurrentRequestCount();
 
     // default to the configured pipeline size.
     // add 1 because the head of pipeline is counted in concurrent requests and not prefetch queue
     const int concurrentRequestLimit = pipelinePrefetchMax() + 1;
 
@@ -3141,64 +3064,64 @@
         fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS);
 
     return true;
 }
 
 /**
  * Attempt to parse one or more requests from the input buffer.
  * Returns true after completing parsing of at least one request [header]. That
  * includes cases where parsing ended with an error (e.g., a huge request).
  */
 bool
 ConnStateData::clientParseRequests()
 {
     bool parsed_req = false;
 
     debugs(33, 5, HERE << clientConnection << ": attempting to parse");
 
     // Loop while we have read bytes that are not needed for producing the body
     // On errors, bodyPipe may become nil, but readMore will be cleared
     while (!in.buf.isEmpty() && !bodyPipe && flags.readMore) {
-        connStripBufferWhitespace(this);
 
         /* Don't try to parse if the buffer is empty */
         if (in.buf.isEmpty())
             break;
 
         /* Limit the number of concurrent requests */
         if (concurrentRequestQueueFilled())
             break;
 
         // try to parse the PROXY protocol header magic bytes
         if (needProxyProtocolHeader_ && !parseProxyProtocolHeader())
             break;
 
         Http::ProtocolVersion http_ver;
-        if (ClientSocketContext *context = parseOneRequest(http_ver)) {
+        if (ClientSocketContext *context = parseOneRequest()) {
             debugs(33, 5, clientConnection << ": done parsing a request");
+
             AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout",
                                              CommTimeoutCbPtrFun(clientLifetimeTimeout, context->http));
             commSetConnTimeout(clientConnection, Config.Timeout.lifetime, timeoutCall);
 
             context->registerWithConn();
 
-            processParsedRequest(context, http_ver);
+            processParsedRequest(context);
 
             parsed_req = true; // XXX: do we really need to parse everything right NOW ?
 
             if (context->mayUseConnection()) {
                 debugs(33, 3, HERE << "Not parsing new requests, as this request may need the connection");
                 break;
             }
         } else {
             debugs(33, 5, clientConnection << ": not enough request data: " <<
                    in.buf.length() << " < " << Config.maxRequestHeaderSize);
             Must(in.buf.length() < Config.maxRequestHeaderSize);
             break;
         }
     }
 
     /* XXX where to 'finish' the parsing pass? */
     return parsed_req;
 }
 
 void
@@ -3326,41 +3249,41 @@
 {
     assert(bodyPipe != NULL);
 
     size_t putSize = 0;
 
     if (in.bodyParser) { // chunked encoding
         if (const err_type error = handleChunkedRequestBody(putSize)) {
             abortChunkedRequestBody(error);
             return false;
         }
     } else { // identity encoding
         debugs(33,5, HERE << "handling plain request body for " << clientConnection);
         putSize = bodyPipe->putMoreData(in.buf.c_str(), in.buf.length());
         if (!bodyPipe->mayNeedMoreData()) {
             // BodyPipe will clear us automagically when we produced everything
             bodyPipe = NULL;
         }
     }
 
     if (putSize > 0)
-        connNoteUseOfBuffer(this, putSize);
+        consumeInput(putSize);
 
     if (!bodyPipe) {
         debugs(33,5, HERE << "produced entire request body for " << clientConnection);
 
         if (const char *reason = stoppedSending()) {
             /* we've finished reading like good clients,
              * now do the close that initiateClose initiated.
              */
             debugs(33, 3, HERE << "closing for earlier sending error: " << reason);
             clientConnection->close();
             return false;
         }
     }
 
     return true;
 }
 
 /// parses available chunked encoded body bytes, checks size, returns errors
 err_type
 ConnStateData::handleChunkedRequestBody(size_t &putSize)

=== modified file 'src/client_side.h'
--- src/client_side.h	2014-09-24 14:07:55 +0000
+++ src/client_side.h	2014-09-27 09:53:31 +0000
@@ -1,38 +1,38 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 33    Client-side Routines */
 
 #ifndef SQUID_CLIENTSIDE_H
 #define SQUID_CLIENTSIDE_H
 
 #include "clientStreamForward.h"
 #include "comm.h"
 #include "helper/forward.h"
 #include "HttpControlMsg.h"
-#include "HttpParser.h"
+#include "http/forward.h"
 #include "ipc/FdNotes.h"
 #include "SBuf.h"
 #if USE_AUTH
 #include "auth/UserRequest.h"
 #endif
 #if USE_OPENSSL
 #include "ssl/support.h"
 #endif
 
 class ConnStateData;
 class ClientHttpRequest;
 class clientStreamNode;
 class ChunkedCodingParser;
 namespace AnyP
 {
 class PortCfg;
 } // namespace Anyp
 
 /**
  * Badly named.
@@ -392,75 +392,78 @@
     /// remove no longer needed leading bytes from the input buffer
     void consumeInput(const size_t byteCount);
 
     /* TODO: Make the methods below (at least) non-public when possible. */
 
     /// stop parsing the request and create context for relaying error info
     ClientSocketContext *abortRequestParsing(const char *const errUri);
 
 protected:
     void startDechunkingRequest();
     void finishDechunkingRequest(bool withSuccess);
     void abortChunkedRequestBody(const err_type error);
     err_type handleChunkedRequestBody(size_t &putSize);
 
     void startPinnedConnectionMonitoring();
     void clientPinnedConnectionRead(const CommIoCbParams &io);
 
     /// parse input buffer prefix into a single transfer protocol request
     /// return NULL to request more header bytes (after checking any limits)
     /// use abortRequestParsing() to handle parsing errors w/o creating request
-    virtual ClientSocketContext *parseOneRequest(Http::ProtocolVersion &ver) = 0;
+    virtual ClientSocketContext *parseOneRequest() = 0;
 
     /// start processing a freshly parsed request
-    virtual void processParsedRequest(ClientSocketContext *context, const Http::ProtocolVersion &ver) = 0;
+    virtual void processParsedRequest(ClientSocketContext *context) = 0;
 
     /// returning N allows a pipeline of 1+N requests (see pipeline_prefetch)
     virtual int pipelinePrefetchMax() const;
 
     /// timeout to use when waiting for the next request
     virtual time_t idleTimeout() const = 0;
 
     BodyPipe::Pointer bodyPipe; ///< set when we are reading request body
 
 private:
     int connFinishedWithConn(int size);
     void clientAfterReadingRequests();
     bool concurrentRequestQueueFilled() const;
 
     void pinNewConnection(const Comm::ConnectionPointer &pinServer, HttpRequest *request, CachePeer *aPeer, bool auth);
 
     /* PROXY protocol functionality */
     bool proxyProtocolValidateClient();
     bool parseProxyProtocolHeader();
     bool parseProxy1p0();
     bool parseProxy2p0();
     bool proxyProtocolError(const char *reason);
 
     /// whether PROXY protocol header is still expected
     bool needProxyProtocolHeader_;
 
 #if USE_AUTH
     /// some user details that can be used to perform authentication on this connection
     Auth::UserRequest::Pointer auth_;
 #endif
 
+    /// the parser state for current HTTP/1.x input buffer processing
+    Http1::RequestParserPointer parser_;
+
 #if USE_OPENSSL
     bool switchedToHttps_;
     /// The SSL server host name appears in CONNECT request or the server ip address for the intercepted requests
     String sslConnectHostOrIp; ///< The SSL server host name as passed in the CONNECT request
     String sslCommonName; ///< CN name for SSL certificate generation
     String sslBumpCertKey; ///< Key to use to store/retrieve generated certificate
 
     /// HTTPS server cert. fetching state for bump-ssl-server-first
     Ssl::ServerBump *sslServerBump;
     Ssl::CertSignAlgorithm signAlgorithm; ///< The signing algorithm to use
 #endif
 
     /// the reason why we no longer write the response or nil
     const char *stoppedSending_;
     /// the reason why we no longer read the request or nil
     const char *stoppedReceiving_;
 
     AsyncCall::Pointer reader; ///< set when we are reading
 
     SBuf connectionTag_; ///< clt_conn_tag=Tag annotation for client connection
@@ -473,25 +476,25 @@
 int varyEvaluateMatch(StoreEntry * entry, HttpRequest * req);
 
 /// accept requests to a given port and inform subCall about them
 void clientStartListeningOn(AnyP::PortCfgPointer &port, const RefCount< CommCbFunPtrCallT<CommAcceptCbPtrFun> > &subCall, const Ipc::FdNoteId noteId);
 
 void clientOpenListenSockets(void);
 void clientConnectionsClose(void);
 void httpRequestFree(void *);
 
 /// decide whether to expect multiple requests on the corresponding connection
 void clientSetKeepaliveFlag(ClientHttpRequest *http);
 
 /* misplaced declaratrions of Stream callbacks provided/used by client side */
 SQUIDCEXTERN CSR clientGetMoreData;
 SQUIDCEXTERN CSS clientReplyStatus;
 SQUIDCEXTERN CSD clientReplyDetach;
 CSCB clientSocketRecipient;
 CSD clientSocketDetach;
 
 /* TODO: Move to HttpServer. Warning: Move requires large code nonchanges! */
-ClientSocketContext *parseHttpRequest(ConnStateData *, HttpParser *, HttpRequestMethod *, Http::ProtocolVersion *);
-void clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *context, const HttpRequestMethod& method, Http::ProtocolVersion http_ver);
-void clientPostHttpsAccept(ConnStateData *connState);
+ClientSocketContext *parseHttpRequest(ConnStateData *, const Http1::RequestParserPointer &);
+void clientProcessRequest(ConnStateData *, const Http1::RequestParserPointer &, ClientSocketContext *);
+void clientPostHttpsAccept(ConnStateData *);
 
 #endif /* SQUID_CLIENTSIDE_H */

=== modified file 'src/htcp.cc'
--- src/htcp.cc	2014-09-29 05:13:17 +0000
+++ src/htcp.cc	2014-10-05 10:43:33 +0000
@@ -132,55 +132,61 @@
     Countstr key_name;
     Countstr signature;
 };
 
 class htcpSpecifier : public StoreClient
 {
 
 public:
     MEMPROXY_CLASS(htcpSpecifier);
 
     void created (StoreEntry *newEntry);
     void checkHit();
     void checkedHit(StoreEntry *e);
 
     void setFrom(Ip::Address &from);
     void setDataHeader(htcpDataHeader *);
     const char *method;
     char *uri;
     char *version;
     char *req_hdrs;
+    size_t reqHdrsSz; ///< size of the req_hdrs content
     HttpRequest *request;
 
 private:
     HttpRequest *checkHitRequest;
 
     Ip::Address from; // was a ptr. return to such IFF needed. otherwise copy should do.
     htcpDataHeader *dhdr;
 };
 
 MEMPROXY_CLASS_INLINE(htcpSpecifier);
 
 struct _htcpDetail {
     char *resp_hdrs;
+    size_t respHdrsSz;
+
     char *entity_hdrs;
+    size_t entityHdrsSz;
+
     char *cache_hdrs;
+    size_t cacheHdrsSz;
 };
 
 struct _htcpStuff {
     int op;
     int rr;
     int f1;
     int response;
     int reason;
     uint32_t msg_id;
     htcpSpecifier S;
     htcpDetail D;
 };
 
 enum {
     HTCP_NOP,
     HTCP_TST,
     HTCP_MON,
     HTCP_SET,
     HTCP_CLR,
     HTCP_END
@@ -216,41 +222,41 @@
 };
 
 static void htcpIncomingConnectionOpened(const Comm::ConnectionPointer &conn, int errNo);
 static uint32_t msg_id_counter = 0;
 
 static Comm::ConnectionPointer htcpOutgoingConn = NULL;
 static Comm::ConnectionPointer htcpIncomingConn = NULL;
 #define N_QUERIED_KEYS 8192
 static uint32_t queried_id[N_QUERIED_KEYS];
 static cache_key queried_keys[N_QUERIED_KEYS][SQUID_MD5_DIGEST_LENGTH];
 
 static Ip::Address queried_addr[N_QUERIED_KEYS];
 static MemAllocator *htcpDetailPool = NULL;
 
 static int old_squid_format = 0;
 
 static ssize_t htcpBuildPacket(char *buf, size_t buflen, htcpStuff * stuff);
 static htcpSpecifier *htcpUnpackSpecifier(char *buf, int sz);
 static htcpDetail *htcpUnpackDetail(char *buf, int sz);
 static ssize_t htcpBuildAuth(char *buf, size_t buflen);
-static ssize_t htcpBuildCountstr(char *buf, size_t buflen, const char *s);
+static ssize_t htcpBuildCountstr(char *buf, size_t buflen, const char *s, size_t len);
 static ssize_t htcpBuildData(char *buf, size_t buflen, htcpStuff * stuff);
 static ssize_t htcpBuildDetail(char *buf, size_t buflen, htcpStuff * stuff);
 static ssize_t htcpBuildOpData(char *buf, size_t buflen, htcpStuff * stuff);
 static ssize_t htcpBuildSpecifier(char *buf, size_t buflen, htcpStuff * stuff);
 static ssize_t htcpBuildTstOpData(char *buf, size_t buflen, htcpStuff * stuff);
 static void htcpFreeSpecifier(htcpSpecifier * s);
 static void htcpFreeDetail(htcpDetail * s);
 
 static void htcpHandleMsg(char *buf, int sz, Ip::Address &from);
 
 static void htcpLogHtcp(Ip::Address &, int, LogTags, const char *);
 static void htcpHandleMon(htcpDataHeader *, char *buf, int sz, Ip::Address &from);
 
 static void htcpHandleNop(htcpDataHeader *, char *buf, int sz, Ip::Address &from);
 
 static void htcpHandleSet(htcpDataHeader *, char *buf, int sz, Ip::Address &from);
 
 static void htcpHandleTst(htcpDataHeader *, char *buf, int sz, Ip::Address &from);
 
 static void htcpRecv(int fd, void *data);
@@ -290,133 +296,126 @@
 
 /*
  * STUFF FOR SENDING HTCP MESSAGES
  */
 
 static ssize_t
 htcpBuildAuth(char *buf, size_t buflen)
 {
     htcpAuthHeader auth;
     size_t copy_sz = 0;
     assert(2 == sizeof(uint16_t));
     auth.length = htons(2);
     copy_sz += 2;
     if (buflen < copy_sz)
         return -1;
     memcpy(buf, &auth, copy_sz);
     return copy_sz;
 }
 
 static ssize_t
-htcpBuildCountstr(char *buf, size_t buflen, const char *s)
+htcpBuildCountstr(char *buf, size_t buflen, const char *s, size_t len)
 {
-    uint16_t length;
-    size_t len;
     int off = 0;
 
     if (buflen - off < 2)
         return -1;
 
-    if (s)
-        len = strlen(s);
-    else
-        len = 0;
-
     debugs(31, 3, "htcpBuildCountstr: LENGTH = " << len);
 
     debugs(31, 3, "htcpBuildCountstr: TEXT = {" << (s ? s : "<NULL>") << "}");
 
-    length = htons((uint16_t) len);
+    uint16_t length = htons((uint16_t) len);
 
     memcpy(buf + off, &length, 2);
 
     off += 2;
 
     if (buflen - off < len)
         return -1;
 
     if (len)
         memcpy(buf + off, s, len);
 
     off += len;
 
     return off;
 }
 
 static ssize_t
 htcpBuildSpecifier(char *buf, size_t buflen, htcpStuff * stuff)
 {
     ssize_t off = 0;
     ssize_t s;
-    s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.method);
+    s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.method, (stuff->S.method?strlen(stuff->S.method):0));
 
     if (s < 0)
         return s;
 
     off += s;
 
-    s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.uri);
+    s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.uri, (stuff->S.uri?strlen(stuff->S.uri):0));
 
     if (s < 0)
         return s;
 
     off += s;
 
-    s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.version);
+    s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.version, (stuff->S.version?strlen(stuff->S.version):0));
 
     if (s < 0)
         return s;
 
     off += s;
 
-    s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.req_hdrs);
+    s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.req_hdrs, stuff->S.reqHdrsSz);
 
     if (s < 0)
         return s;
 
     off += s;
 
     debugs(31, 3, "htcpBuildSpecifier: size " << off);
 
     return off;
 }
 
 static ssize_t
 htcpBuildDetail(char *buf, size_t buflen, htcpStuff * stuff)
 {
     ssize_t off = 0;
     ssize_t s;
-    s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.resp_hdrs);
+    s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.resp_hdrs, stuff->D.respHdrsSz);
 
     if (s < 0)
         return s;
 
     off += s;
 
-    s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.entity_hdrs);
+    s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.entity_hdrs, stuff->D.entityHdrsSz);
 
     if (s < 0)
         return s;
 
     off += s;
 
-    s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.cache_hdrs);
+    s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.cache_hdrs, stuff->D.cacheHdrsSz);
 
     if (s < 0)
         return s;
 
     off += s;
 
     return off;
 }
 
 static ssize_t
 htcpBuildTstOpData(char *buf, size_t buflen, htcpStuff * stuff)
 {
     switch (stuff->rr) {
 
     case RR_REQUEST:
         debugs(31, 3, "htcpBuildTstOpData: RR_REQUEST");
         return htcpBuildSpecifier(buf, buflen, stuff);
 
     case RR_RESPONSE:
         debugs(31, 3, "htcpBuildTstOpData: RR_RESPONSE");
@@ -611,40 +610,42 @@
 }
 
 static void
 htcpFreeSpecifier(htcpSpecifier * s)
 {
     HTTPMSGUNLOCK(s->request);
 
     delete s;
 }
 
 static void
 htcpFreeDetail(htcpDetail * d)
 {
     htcpDetailPool->freeOne(d);
 }
 
 /*
  * Unpack an HTCP SPECIFIER in place
  * This will overwrite any following AUTH block
  */
+// XXX: this needs to be turned into an Htcp1::Parser inheriting from Http1::RequestParser
+//   but with different first-line and block unpacking logic.
 static htcpSpecifier *
 htcpUnpackSpecifier(char *buf, int sz)
 {
     htcpSpecifier *s = new htcpSpecifier;
     HttpRequestMethod method;
 
     /* Find length of METHOD */
     uint16_t l = ntohs(*(uint16_t *) buf);
     sz -= 2;
     buf += 2;
 
     if (l > sz) {
         debugs(31, 3, "htcpUnpackSpecifier: failed to unpack METHOD");
         htcpFreeSpecifier(s);
         return NULL;
     }
 
     /* Set METHOD */
     s->method = buf;
     buf += l;
@@ -692,135 +693,133 @@
     debugs(31, 6, "htcpUnpackSpecifier: VERSION (" << l << "/" << sz << ") '" << s->version << "'");
 
     /* Find length of REQ-HDRS */
     l = ntohs(*(uint16_t *) buf);
     sz -= 2;
 
     if (l > sz) {
         debugs(31, 3, "htcpUnpackSpecifier: failed to unpack REQ-HDRS");
         htcpFreeSpecifier(s);
         return NULL;
     }
 
     /* Add terminating null to URI */
     *buf = '\0';
     buf += 2;
 
     /* Set REQ-HDRS */
     s->req_hdrs = buf;
     buf += l;
     sz -= l;
+    s->reqHdrsSz = l;
     debugs(31, 6, "htcpUnpackSpecifier: REQ-HDRS (" << l << "/" << sz << ") '" << s->req_hdrs << "'");
 
     debugs(31, 3, "htcpUnpackSpecifier: " << sz << " bytes left");
 
     /*
      * Add terminating null to REQ-HDRS. This is possible because we allocated
      * an extra byte when we received the packet. This will overwrite any following
      * AUTH block.
      */
     *buf = '\0';
 
     /*
      * 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);
 
     if (s->request)
         HTTPMSGLOCK(s->request);
 
     return s;
 }
 
 /*
  * Unpack an HTCP DETAIL in place
  * This will overwrite any following AUTH block
  */
 static htcpDetail *
 htcpUnpackDetail(char *buf, int sz)
 {
     htcpDetail *d = static_cast<htcpDetail *>(htcpDetailPool->alloc());
 
     /* Find length of RESP-HDRS */
     uint16_t l = ntohs(*(uint16_t *) buf);
     sz -= 2;
     buf += 2;
 
     if (l > sz) {
         debugs(31, 3, "htcpUnpackDetail: failed to unpack RESP_HDRS");
         htcpFreeDetail(d);
         return NULL;
     }
 
     /* Set RESP-HDRS */
     d->resp_hdrs = buf;
-
     buf += l;
-
+    d->respHdrsSz = l;
     sz -= l;
 
     /* Find length of ENTITY-HDRS */
     l = ntohs(*(uint16_t *) buf);
 
     sz -= 2;
 
     if (l > sz) {
         debugs(31, 3, "htcpUnpackDetail: failed to unpack ENTITY_HDRS");
         htcpFreeDetail(d);
         return NULL;
     }
 
     /* Add terminating null to RESP-HDRS */
     *buf = '\0';
 
     /* Set ENTITY-HDRS */
     buf += 2;
 
     d->entity_hdrs = buf;
-
     buf += l;
-
+    d->entityHdrsSz = l;
     sz -= l;
 
     /* Find length of CACHE-HDRS */
     l = ntohs(*(uint16_t *) buf);
 
     sz -= 2;
 
     if (l > sz) {
         debugs(31, 3, "htcpUnpackDetail: failed to unpack CACHE_HDRS");
         htcpFreeDetail(d);
         return NULL;
     }
 
     /* Add terminating null to ENTITY-HDRS */
     *buf = '\0';
 
     /* Set CACHE-HDRS */
     buf += 2;
 
     d->cache_hdrs = buf;
-
     buf += l;
-
+    d->cacheHdrsSz = l;
     sz -= l;
 
     debugs(31, 3, "htcpUnpackDetail: " << sz << " bytes left");
 
     /*
      * Add terminating null to CACHE-HDRS. This is possible because we allocated
      * an extra byte when we received the packet. This will overwrite any following
      * AUTH block.
      */
     *buf = '\0';
 
     return d;
 }
 
 static bool
 htcpAccessAllowed(acl_access * acl, htcpSpecifier * s, Ip::Address &from)
 {
     /* default deny if no access list present */
     if (!acl)
         return false;
@@ -838,95 +837,102 @@
     static char pkt[8192];
     HttpHeader hdr(hoHtcpReply);
     MemBuf mb;
     Packer p;
     ssize_t pktlen;
     memset(&stuff, '\0', sizeof(stuff));
     stuff.op = HTCP_TST;
     stuff.rr = RR_RESPONSE;
     stuff.f1 = 0;
     stuff.response = e ? 0 : 1;
     debugs(31, 3, "htcpTstReply: response = " << stuff.response);
     stuff.msg_id = dhdr->msg_id;
 
     if (spec) {
         mb.init();
         packerToMemInit(&p, &mb);
         stuff.S.method = spec->method;
         stuff.S.uri = spec->uri;
         stuff.S.version = spec->version;
         stuff.S.req_hdrs = spec->req_hdrs;
+        stuff.S.reqHdrsSz = spec->reqHdrsSz;
         if (e)
             hdr.putInt(HDR_AGE, (e->timestamp <= squid_curtime ? (squid_curtime - e->timestamp) : 0) );
         else
             hdr.putInt(HDR_AGE, 0);
         hdr.packInto(&p);
         stuff.D.resp_hdrs = xstrdup(mb.buf);
+        stuff.D.respHdrsSz = mb.contentSize();
         debugs(31, 3, "htcpTstReply: resp_hdrs = {" << stuff.D.resp_hdrs << "}");
         mb.reset();
         hdr.reset();
 
         if (e && e->expires > -1)
             hdr.putTime(HDR_EXPIRES, e->expires);
 
         if (e && e->lastmod > -1)
             hdr.putTime(HDR_LAST_MODIFIED, e->lastmod);
 
         hdr.packInto(&p);
 
         stuff.D.entity_hdrs = xstrdup(mb.buf);
+        stuff.D.entityHdrsSz = mb.contentSize();
 
         debugs(31, 3, "htcpTstReply: entity_hdrs = {" << stuff.D.entity_hdrs << "}");
 
         mb.reset();
 
         hdr.reset();
 
 #if USE_ICMP
         if (char *host = urlHostname(spec->uri)) {
             int rtt = 0;
             int hops = 0;
             int samp = 0;
             netdbHostData(host, &samp, &rtt, &hops);
 
             if (rtt || hops) {
                 char cto_buf[128];
                 snprintf(cto_buf, 128, "%s %d %f %d",
                          host, samp, 0.001 * rtt, hops);
                 hdr.putExt("Cache-to-Origin", cto_buf);
             }
         }
 #endif /* USE_ICMP */
 
         hdr.packInto(&p);
         stuff.D.cache_hdrs = xstrdup(mb.buf);
+        stuff.D.cacheHdrsSz = mb.contentSize();
         debugs(31, 3, "htcpTstReply: cache_hdrs = {" << stuff.D.cache_hdrs << "}");
         mb.clean();
         hdr.clean();
         packerClean(&p);
     }
 
     pktlen = htcpBuildPacket(pkt, sizeof(pkt), &stuff);
 
     safe_free(stuff.D.resp_hdrs);
+    stuff.D.respHdrsSz = 0;
     safe_free(stuff.D.entity_hdrs);
+    stuff.D.entityHdrsSz = 0;
     safe_free(stuff.D.cache_hdrs);
+    stuff.D.cacheHdrsSz = 0;
 
     if (!pktlen) {
         debugs(31, 3, "htcpTstReply: htcpBuildPacket() failed");
         return;
     }
 
     htcpSend(pkt, (int) pktlen, from);
 }
 
 static void
 
 htcpClrReply(htcpDataHeader * dhdr, int purgeSucceeded, Ip::Address &from)
 {
     htcpStuff stuff;
     static char pkt[8192];
     ssize_t pktlen;
 
     /* If dhdr->F1 == 0, no response desired */
 
     if (dhdr->F1 == 0)
@@ -949,52 +955,49 @@
     pktlen = htcpBuildPacket(pkt, sizeof(pkt), &stuff);
 
     if (pktlen == 0) {
         debugs(31, 3, "htcpClrReply: htcpBuildPacket() failed");
         return;
     }
 
     htcpSend(pkt, (int) pktlen, from);
 }
 
 static void
 
 htcpHandleNop(htcpDataHeader * hdr, char *buf, int sz, Ip::Address &from)
 {
     debugs(31, 3, "htcpHandleNop: Unimplemented");
 }
 
 void
 htcpSpecifier::checkHit()
 {
-    char *blk_end;
     checkHitRequest = request;
 
     if (NULL == checkHitRequest) {
         debugs(31, 3, "htcpCheckHit: NO; failed to parse URL");
         checkedHit(NullStoreEntry::getInstance());
         return;
     }
 
-    blk_end = req_hdrs + strlen(req_hdrs);
-
-    if (!checkHitRequest->header.parse(req_hdrs, blk_end)) {
+    if (!checkHitRequest->header.parse(req_hdrs, reqHdrsSz)) {
         debugs(31, 3, "htcpCheckHit: NO; failed to parse request headers");
         delete checkHitRequest;
         checkHitRequest = NULL;
         checkedHit(NullStoreEntry::getInstance());
         return;
     }
 
     StoreEntry::getPublicByRequest(this, checkHitRequest);
 }
 
 void
 htcpSpecifier::created (StoreEntry *e)
 {
     StoreEntry *hit=NULL;
     assert (e);
 
     if (e->isNull()) {
         debugs(31, 3, "htcpCheckHit: NO; public object not found");
     } else if (!e->validToSend()) {
         debugs(31, 3, "htcpCheckHit: NO; entry not valid to send" );
@@ -1002,53 +1005,50 @@
         debugs(31, 3, "htcpCheckHit: NO; cached response is stale");
     } else {
         debugs(31, 3, "htcpCheckHit: YES!?");
         hit = e;
     }
 
     checkedHit (hit);
 }
 
 static void
 htcpClrStoreEntry(StoreEntry * e)
 {
     debugs(31, 4, "htcpClrStoreEntry: Clearing store for entry: " << e->url()  );
     e->releaseRequest();
 }
 
 static int
 htcpClrStore(const htcpSpecifier * s)
 {
     HttpRequest *request = s->request;
-    char *blk_end;
     StoreEntry *e = NULL;
     int released = 0;
 
     if (request == NULL) {
         debugs(31, 3, "htcpClrStore: failed to parse URL");
         return -1;
     }
 
     /* Parse request headers */
-    blk_end = s->req_hdrs + strlen(s->req_hdrs);
-
-    if (!request->header.parse(s->req_hdrs, blk_end)) {
+    if (!request->header.parse(s->req_hdrs, s->reqHdrsSz)) {
         debugs(31, 2, "htcpClrStore: failed to parse request headers");
         return -1;
     }
 
     /* Lookup matching entries. This matches both GET and HEAD */
     while ((e = storeGetPublicByRequest(request)) != NULL) {
         if (e != NULL) {
             htcpClrStoreEntry(e);
             ++released;
         }
     }
 
     if (released) {
         debugs(31, 4, "htcpClrStore: Cleared " << released << " matching entries");
         return 1;
     } else {
         debugs(31, 4, "htcpClrStore: No matching entry found");
         return 0;
     }
 }
@@ -1109,47 +1109,47 @@
         debugs(31, 2, "htcpHandleTstResponse: error condition, F1/MO == 1");
         return;
     }
 
     htcpReply.msg_id = hdr->msg_id;
     debugs(31, 3, "htcpHandleTstResponse: msg_id = " << htcpReply.msg_id);
     htcpReply.hit = hdr->response ? 0 : 1;
 
     if (hdr->F1) {
         debugs(31, 3, "htcpHandleTstResponse: MISS");
     } else {
         debugs(31, 3, "htcpHandleTstResponse: HIT");
         d = htcpUnpackDetail(buf, sz);
 
         if (d == NULL) {
             debugs(31, 3, "htcpHandleTstResponse: bad DETAIL");
             return;
         }
 
         if ((t = d->resp_hdrs))
-            htcpReply.hdr.parse(t, t + strlen(t));
+            htcpReply.hdr.parse(t, d->respHdrsSz);
 
         if ((t = d->entity_hdrs))
-            htcpReply.hdr.parse(t, t + strlen(t));
+            htcpReply.hdr.parse(t, d->entityHdrsSz);
 
         if ((t = d->cache_hdrs))
-            htcpReply.hdr.parse(t, t + strlen(t));
+            htcpReply.hdr.parse(t, d->cacheHdrsSz);
     }
 
     debugs(31, 3, "htcpHandleTstResponse: key (" << key << ") " << storeKeyText(key));
     neighborsHtcpReply(key, &htcpReply, from);
     htcpReply.hdr.clean();
 
     if (d)
         htcpFreeDetail(d);
 }
 
 static void
 htcpHandleTstRequest(htcpDataHeader * dhdr, char *buf, int sz, Ip::Address &from)
 {
     /* buf should be a SPECIFIER */
     htcpSpecifier *s;
 
     if (sz == 0) {
         debugs(31, 3, "htcpHandleTst: nothing to do");
         return;
     }

=== modified file 'src/htcp.h'
--- src/htcp.h	2014-09-13 13:59:43 +0000
+++ src/htcp.h	2014-09-14 12:23:03 +0000
@@ -1,39 +1,37 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #ifndef SQUID_HTCP_H
 #define SQUID_HTCP_H
 
 #if USE_HTCP
 
 #include "HttpHeader.h"
-#include "HttpRequestMethod.h"
+#include "http/forward.h"
 #include "ip/forward.h"
 
-class HttpRequest;
-
 /// \ingroup ServerProtocolHTCP
 class HtcpReplyData
 {
 
 public:
     HtcpReplyData();
     int hit;
     HttpHeader hdr;
     uint32_t msg_id;
     double version;
 
     struct cto_t {
         /* cache-to-origin */
         double rtt;
         int samp;
         int hops;
     } cto;
 };
 
 /// \ingroup ServerProtocolHTCP

=== modified file 'src/http/Makefile.am'
--- src/http/Makefile.am	2014-09-13 13:59:43 +0000
+++ src/http/Makefile.am	2014-09-14 12:23:03 +0000
@@ -1,26 +1,37 @@
 ## Copyright (C) 1996-2014 The Squid Software Foundation and contributors
 ##
 ## Squid software is distributed under GPLv2+ license and includes
 ## contributions from numerous individuals and organizations.
 ## Please see the COPYING and CONTRIBUTORS files for details.
 ##
 
 include $(top_srcdir)/src/Common.am
 include $(top_srcdir)/src/TestHeaders.am
 
+AUTOMAKE_OPTIONS = subdir-objects
+
+SUBDIRS = one
+DIST_SUBDIRS = one
+
 noinst_LTLIBRARIES = libsquid-http.la
 
 libsquid_http_la_SOURCES = \
+	forward.h \
 	MethodType.cc \
 	MethodType.h \
 	ProtocolVersion.h \
+	RegisteredHeaders.h \
+	RequestMethod.cc \
+	RequestMethod.h \
 	StatusCode.cc \
 	StatusCode.h \
 	StatusLine.cc \
 	StatusLine.h
 
+libsquid_http_la_LIBADD= one/libhttp1.la
+
 MethodType.cc: MethodType.h $(top_srcdir)/src/mk-string-arrays.awk
 	($(AWK) -f $(top_srcdir)/src/mk-string-arrays.awk sbuf=1 < $(srcdir)/MethodType.h | \
 		sed -e 's%METHOD_%%' -e 's%_C%-C%' >$@) || ($(RM) -f $@ && exit 1)
 
 CLEANFILES += MethodType.cc

=== added file 'src/http/RegisteredHeaders.h'
--- src/http/RegisteredHeaders.h	1970-01-01 00:00:00 +0000
+++ src/http/RegisteredHeaders.h	2014-08-19 12:14:45 +0000
@@ -0,0 +1,113 @@
+#ifndef SQUID_HTTP_REGISTEREDHEADERS_H
+#define SQUID_HTTP_REGISTEREDHEADERS_H
+
+/// recognized or "known" header fields; and the RFC which defines them (or not)
+/// http://www.iana.org/assignments/message-headers/message-headers.xhtml
+typedef enum {
+    HDR_BAD_HDR = -1,
+    HDR_ACCEPT = 0,                     /**< RFC 7231 */
+    HDR_ACCEPT_CHARSET,                 /**< RFC 7231 */
+    HDR_ACCEPT_ENCODING,                /**< RFC 7231 */
+    /*HDR_ACCEPT_FEATURES,*/            /* RFC 2295 */
+    HDR_ACCEPT_LANGUAGE,                /**< RFC 7231 */
+    HDR_ACCEPT_RANGES,                  /**< RFC 7233 */
+    HDR_AGE,                            /**< RFC 7234 */
+    HDR_ALLOW,                          /**< RFC 7231 */
+    HDR_AUTHENTICATION_INFO,            /**< RFC 2617 */
+    HDR_AUTHORIZATION,                  /**< RFC 7235, 4559 */
+    HDR_CACHE_CONTROL,                  /**< RFC 7234 */
+    HDR_CONNECTION,                     /**< RFC 7230 */
+    HDR_CONTENT_BASE,                   /**< obsoleted RFC 2068 */
+    HDR_CONTENT_DISPOSITION,            /**< RFC 2183, 6266 */
+    HDR_CONTENT_ENCODING,               /**< RFC 7231 */
+    HDR_CONTENT_LANGUAGE,               /**< RFC 7231 */
+    HDR_CONTENT_LENGTH,                 /**< RFC 7230 */
+    HDR_CONTENT_LOCATION,               /**< RFC 7231 */
+    HDR_CONTENT_MD5,                    /**< deprecated, RFC 2616 */
+    HDR_CONTENT_RANGE,                  /**< RFC 7233 */
+    HDR_CONTENT_TYPE,                   /**< RFC 7231 */
+    HDR_COOKIE,                         /**< RFC 6265 header we may need to erase */
+    HDR_COOKIE2,                        /**< obsolete RFC 2965 header we may need to erase */
+    HDR_DATE,                           /**< RFC 7231 */
+    /*HDR_DAV,*/                        /* RFC 2518 */
+    /*HDR_DEPTH,*/                      /* RFC 2518 */
+    /*HDR_DERIVED_FROM,*/               /* deprecated RFC 2068 */
+    /*HDR_DESTINATION,*/                /* RFC 2518 */
+    HDR_ETAG,                           /**< RFC 7232 */
+    HDR_EXPECT,                         /**< RFC 7231 */
+    HDR_EXPIRES,                        /**< RFC 7234 */
+    HDR_FORWARDED,                      /**< RFC 7239 */
+    HDR_FROM,                           /**< RFC 7231 */
+    HDR_HOST,                           /**< RFC 7230 */
+    HDR_HTTP2_SETTINGS,                 /**< HTTP/2.0 upgrade header. see draft-ietf-httpbis-http2-13 */
+    /*HDR_IF,*/                         /* RFC 2518 */
+    HDR_IF_MATCH,                       /**< RFC 7232 */
+    HDR_IF_MODIFIED_SINCE,              /**< RFC 7232 */
+    HDR_IF_NONE_MATCH,                  /**< RFC 7232 */
+    HDR_IF_RANGE,                       /**< RFC 7233 */
+    HDR_IF_UNMODIFIED_SINCE,            /**< RFC 7232 */
+    HDR_KEEP_ALIVE,                     /**< obsoleted RFC 2068 header we may need to erase */
+    HDR_KEY,                            /**< experimental RFC Draft draft-fielding-http-key-02 */
+    HDR_LAST_MODIFIED,                  /**< RFC 7232 */
+    HDR_LINK,                           /**< RFC 5988 */
+    HDR_LOCATION,                       /**< RFC 7231 */
+    /*HDR_LOCK_TOKEN,*/                 /* RFC 2518 */
+    HDR_MAX_FORWARDS,                   /**< RFC 7231 */
+    HDR_MIME_VERSION,                   /**< RFC 2045, 7231 */
+    HDR_NEGOTIATE,                      /**< experimental RFC 2295. Why only this one from 2295? */
+    /*HDR_OVERWRITE,*/                  /* RFC 2518 */
+    HDR_ORIGIN,                         /* CORS Draft specification (see http://www.w3.org/TR/cors/) */
+    HDR_PRAGMA,                         /**< RFC 7234 */
+    HDR_PROXY_AUTHENTICATE,             /**< RFC 7235 */
+    HDR_PROXY_AUTHENTICATION_INFO,      /**< RFC 2617 */
+    HDR_PROXY_AUTHORIZATION,            /**< RFC 7235 */
+    HDR_PROXY_CONNECTION,               /**< obsolete Netscape header we may need to erase. */
+    HDR_PROXY_SUPPORT,                  /**< RFC 4559 */
+    HDR_PUBLIC,                         /**<  RFC 2068 */
+    HDR_RANGE,                          /**< RFC 7233 */
+    HDR_REFERER,                        /**< RFC 7231 */
+    HDR_REQUEST_RANGE,                  /**< some clients use this, sigh */
+    HDR_RETRY_AFTER,                    /**< RFC 7231 */
+    HDR_SERVER,                         /**< RFC 7231 */
+    HDR_SET_COOKIE,                     /**< RFC 6265 header we may need to erase */
+    HDR_SET_COOKIE2,                    /**< obsoleted RFC 2965 header we may need to erase */
+    /*HDR_STATUS_URI,*/                 /* RFC 2518 */
+    /*HDR_TCN,*/                        /* experimental RFC 2295 */
+    HDR_TE,                             /**< RFC 7230 */
+    /*HDR_TIMEOUT,*/                    /* RFC 2518 */
+    HDR_TITLE,                          /* obsolete draft suggested header */
+    HDR_TRAILER,                        /**< RFC 7230 */
+    HDR_TRANSFER_ENCODING,              /**< RFC 7230 */
+    HDR_TRANSLATE,                      /**< IIS custom header we may need to erase */
+    HDR_UNLESS_MODIFIED_SINCE,          /**< IIS custom header we may need to erase */
+    HDR_UPGRADE,                        /**< RFC 7230 */
+    HDR_USER_AGENT,                     /**< RFC 7231 */
+    /*HDR_VARIANT_VARY,*/               /* experimental RFC 2295 */
+    HDR_VARY,                           /**< RFC 7231 */
+    HDR_VIA,                            /**< RFC 7230 */
+    HDR_WARNING,                        /**< RFC 7234 */
+    HDR_WWW_AUTHENTICATE,               /**< RFC 7235, 4559 */
+    HDR_X_CACHE,                        /**< Squid custom header */
+    HDR_X_CACHE_LOOKUP,                 /**< Squid custom header. temporary hack that became de-facto. TODO remove */
+    HDR_X_FORWARDED_FOR,                /**< obsolete Squid custom header, RFC 7239 */
+    HDR_X_REQUEST_URI,                  /**< Squid custom header appended if ADD_X_REQUEST_URI is defined */
+    HDR_X_SQUID_ERROR,                  /**< Squid custom header on generated error responses */
+#if X_ACCELERATOR_VARY
+    HDR_X_ACCELERATOR_VARY,             /**< obsolete Squid custom header. */
+#endif
+#if USE_ADAPTATION
+    HDR_X_NEXT_SERVICES,                /**< Squid custom ICAP header */
+#endif
+    HDR_SURROGATE_CAPABILITY,           /**< Edge Side Includes (ESI) header */
+    HDR_SURROGATE_CONTROL,              /**< Edge Side Includes (ESI) header */
+    HDR_FRONT_END_HTTPS,                /**< MS Exchange custom header we may have to add */
+    HDR_FTP_COMMAND,                    /**< Internal header for FTP command */
+    HDR_FTP_ARGUMENTS,                  /**< Internal header for FTP command arguments */
+    HDR_FTP_PRE,                        /**< Internal header containing leading FTP control response lines */
+    HDR_FTP_STATUS,                     /**< Internal header for FTP reply status */
+    HDR_FTP_REASON,                     /**< Internal header for FTP reply reason */
+    HDR_OTHER,                          /**< internal tag value for "unknown" headers */
+    HDR_ENUM_END
+} http_hdr_type;
+
+#endif /* SQUID_HTTP_REGISTEREDHEADERS_H */

=== renamed file 'src/HttpRequestMethod.cc' => 'src/http/RequestMethod.cc'
--- src/HttpRequestMethod.cc	2014-09-13 13:59:43 +0000
+++ src/http/RequestMethod.cc	2014-09-14 12:23:03 +0000
@@ -1,84 +1,112 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 73    HTTP Request */
 
 #include "squid.h"
-#include "HttpRequestMethod.h"
+#include "http/RequestMethod.h"
 #include "SquidConfig.h"
 #include "wordlist.h"
 
 static Http::MethodType &
 operator++ (Http::MethodType &aMethod)
 {
     int tmp = (int)aMethod;
     aMethod = (Http::MethodType)(++tmp);
     return 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;
 
     // 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(begin, end-begin)) {
 
             // relaxed parser allows mixed-case and corrects them on output
             if (Config.onoff.relaxed_header_parser)
                 return;
 
             if (0 == image().cmp(begin, end-begin))
                 return;
         }
     }
 
     // if method not found and method string is not null then it is other method
     theMethod = Http::METHOD_OTHER;
     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
 {
     static const SBuf methodOther("METHOD_OTHER");
     if (Http::METHOD_OTHER != theMethod) {
         return Http::MethodType_sb[theMethod];
     } else {
         if (!theImage.isEmpty()) {
             return theImage;
         } else {
             return methodOther;
         }
     }
 }
 
 bool
 HttpRequestMethod::isHttpSafe() const
 {
     // Only a few methods are defined as safe. All others are "unsafe"
 

=== renamed file 'src/HttpRequestMethod.h' => 'src/http/RequestMethod.h'
--- src/HttpRequestMethod.h	2014-09-13 13:59:43 +0000
+++ src/http/RequestMethod.h	2014-09-14 12:23:03 +0000
@@ -1,61 +1,52 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #ifndef SQUID_HTTPREQUESTMETHOD_H
 #define SQUID_HTTPREQUESTMETHOD_H
 
+#include "http/forward.h"
 #include "http/MethodType.h"
 #include "SBuf.h"
 
 class SquidConfig;
 
 #include <iosfwd>
 
 /**
  * This class represents an HTTP Request METHOD
  * - i.e. PUT, POST, GET etc.
  * It has a runtime extension facility to allow it to
  * efficiently support new methods
  */
-class HttpRequestMethod
+class HttpRequestMethod : public RefCountable
 {
-
 public:
-//    static void Configure(SquidConfig &Config);
-
     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;
         theImage = aMethod.theImage;
         return *this;
     }
 
     HttpRequestMethod & operator = (Http::MethodType const aMethod) {
         theMethod = aMethod;
         theImage.clear();
         return *this;
     }
 
     bool operator == (Http::MethodType const & aMethod) const { return theMethod == aMethod; }
     bool operator == (HttpRequestMethod const & aMethod) const {
         return theMethod == aMethod.theMethod &&
                (theMethod != Http::METHOD_OTHER || theImage == aMethod.theImage);
     }
 
     bool operator != (Http::MethodType const & aMethod) const { return theMethod != aMethod; }

=== added file 'src/http/forward.h'
--- src/http/forward.h	1970-01-01 00:00:00 +0000
+++ src/http/forward.h	2014-05-20 07:45:55 +0000
@@ -0,0 +1,16 @@
+#ifndef SQUID_SRC_HTTP_FORWARD_H
+#define SQUID_SRC_HTTP_FORWARD_H
+
+#include "http/one/forward.h"
+
+// TODO move these classes into Http namespace
+class HttpRequestMethod;
+typedef RefCount<HttpRequestMethod> HttpRequestMethodPointer;
+
+class HttpRequest;
+typedef RefCount<HttpRequest> HttpRequestPointer;
+
+class HttpReply;
+typedef RefCount<HttpReply> HttpReplyPointer;
+
+#endif /* SQUID_SRC_HTTP_FORWARD_H */

=== added directory 'src/http/one'
=== added file 'src/http/one/Makefile.am'
--- src/http/one/Makefile.am	1970-01-01 00:00:00 +0000
+++ src/http/one/Makefile.am	2014-07-30 07:38:25 +0000
@@ -0,0 +1,11 @@
+include $(top_srcdir)/src/Common.am
+include $(top_srcdir)/src/TestHeaders.am
+
+noinst_LTLIBRARIES = libhttp1.la
+
+libhttp1_la_SOURCES = \
+	forward.h \
+	Parser.cc \
+	Parser.h \
+	RequestParser.cc \
+	RequestParser.h

=== renamed file 'src/HttpParser.cc' => 'src/http/one/Parser.cc'
--- src/HttpParser.cc	2014-09-13 13:59:43 +0000
+++ src/http/one/Parser.cc	2014-09-14 12:23:03 +0000
@@ -1,302 +1,80 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #include "squid.h"
 #include "Debug.h"
-#include "HttpParser.h"
-#include "profiler/Profiler.h"
-#include "SquidConfig.h"
+#include "http/one/Parser.h"
+#include "parser/Tokenizer.h"
 
-void
-HttpParser::clear()
-{
-    state = HTTP_PARSE_NONE;
-    request_parse_status = Http::scNone;
-    buf = NULL;
-    bufsiz = 0;
-    req.start = req.end = -1;
-    hdr_start = hdr_end = -1;
-    req.m_start = req.m_end = -1;
-    req.u_start = req.u_end = -1;
-    req.v_start = req.v_end = -1;
-    req.v_maj = req.v_min = 0;
-}
+/// RFC 7230 section 2.6 - 7 magic octets
+const SBuf Http::One::Parser::Http1magic("HTTP/1.");
 
 void
-HttpParser::reset(const char *aBuf, int len)
+Http::One::Parser::clear()
 {
-    clear(); // empty the state.
-    state = HTTP_PARSE_NEW;
-    buf = aBuf;
-    bufsiz = len;
-    debugs(74, 5, HERE << "Request buffer is " << buf);
+    parsingStage_ = HTTP_PARSE_NONE;
+    buf_ = NULL;
+    msgProtocol_ = AnyP::ProtocolVersion();
+    mimeHeaderBlock_.clear();
 }
 
-int
-HttpParser::parseRequestFirstLine()
+// arbitrary maximum-length for headers which can be found by Http1Parser::getHeaderField()
+#define GET_HDR_SZ	1024
+
+// BUG: returns only the first header line with given name,
+//      ignores multi-line headers and obs-fold headers
+char *
+Http::One::Parser::getHeaderField(const char *name)
 {
-    int second_word = -1; // track the suspected URI start
-    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, HERE << "parsing possible request: " << buf);
-
-    // Single-pass parse: (provided we have the whole line anyways)
-
-    req.start = 0;
-    if (Config.onoff.relaxed_header_parser) {
-        if (Config.onoff.relaxed_header_parser < 0 && buf[req.start] == ' ')
-            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 (; req.start < bufsiz && buf[req.start] == ' '; ++req.start);
-    }
-    req.end = -1;
-    for (int i = 0; i < bufsiz; ++i) {
-        // track first and last whitespace (SP only)
-        if (buf[i] == ' ') {
-            last_whitespace = i;
-            if (first_whitespace < req.start)
-                first_whitespace = i;
-        }
-
-        // track next non-SP/non-HT byte after first_whitespace
-        if (second_word < first_whitespace && buf[i] != ' ' && buf[i] != '\t') {
-            second_word = i;
-        }
-
-        // locate line terminator
-        if (buf[i] == '\n') {
-            req.end = i;
-            line_end = i - 1;
-            break;
-        }
-        if (i < bufsiz - 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: " <<
-                           "Series of carriage-return bytes received prior to line terminator. " <<
-                           "Ignored due to relaxed_header_parser.");
-
-                // 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')
-                    ++i;
-
-                if (buf[i + 1] == '\n') {
-                    req.end = i + 1;
-                    break;
-                }
-            } else {
-                if (buf[i + 1] == '\n') {
-                    req.end = i + 1;
-                    line_end = i - 1;
-                    break;
-                }
-            }
-
-            // RFC 2616 section 5.1
-            // "No CR or LF is allowed except in the final CRLF sequence"
-            request_parse_status = Http::scBadRequest;
-            return -1;
-        }
-    }
-    if (req.end == -1) {
-        debugs(74, 5, "Parser: retval 0: from " << req.start <<
-               "->" << req.end << ": needs more data to complete first line.");
-        return 0;
-    }
+    if (!headerBlockSize() || !name)
+        return NULL;
 
-    // NP: we have now seen EOL, more-data (0) cannot occur.
-    //     From here on any failure is -1, success is 1
+    LOCAL_ARRAY(char, header, GET_HDR_SZ);
+    const int namelen = name ? strlen(name) : 0;
 
-    // Input Validation:
+    debugs(25, 5, "looking for " << name);
 
-    // Process what we now know about the line structure into field offsets
-    // generating HTTP status for any aborts as we go.
+    // while we can find more LF in the SBuf
+    static CharacterSet iso8859Line = CharacterSet("non-LF",'\0','\n'-1) + CharacterSet(NULL, '\n'+1, (unsigned char)0xFF);
+    ::Parser::Tokenizer tok(mimeHeaderBlock_);
+    SBuf p;
+    static const SBuf crlf("\r\n");
 
-    // First non-whitespace = beginning of method
-    if (req.start > line_end) {
-        request_parse_status = Http::scBadRequest;
-        return -1;
-    }
-    req.m_start = req.start;
+    while (tok.prefix(p, iso8859Line)) {
+        tok.skipOne(CharacterSet::LF); // move tokenizer past the LF
 
-    // First whitespace = end of method
-    if (first_whitespace > line_end || first_whitespace < req.start) {
-        request_parse_status = Http::scBadRequest; // no method
-        return -1;
-    }
-    req.m_end = first_whitespace - 1;
-    if (req.m_end < req.m_start) {
-        request_parse_status = Http::scBadRequest; // missing URI?
-        return -1;
-    }
+        // header lines must start with the name (case insensitive)
+        if (p.substr(0, namelen).caseCmp(name, namelen))
+            continue;
 
-    // First non-whitespace after first SP = beginning of URL+Version
-    if (second_word > line_end || second_word < req.start) {
-        request_parse_status = Http::scBadRequest; // missing URI
-        return -1;
-    }
-    req.u_start = second_word;
+        // then a COLON
+        if (p[namelen] != ':')
+            continue;
 
-    // RFC 1945: SP and version following URI are optional, marking version 0.9
-    // we identify this by the last whitespace being earlier than URI start
-    if (last_whitespace < second_word && last_whitespace >= req.start) {
-        req.v_maj = 0;
-        req.v_min = 9;
-        req.u_end = line_end;
-        request_parse_status = Http::scOkay; // HTTP/0.9
-        return 1;
-    } else {
-        // otherwise last whitespace is somewhere after end of URI.
-        req.u_end = last_whitespace;
-        // crop any trailing whitespace in the area we think of as URI
-        for (; req.u_end >= req.u_start && xisspace(buf[req.u_end]); --req.u_end);
-    }
-    if (req.u_end < req.u_start) {
-        request_parse_status = Http::scBadRequest; // missing URI
-        return -1;
-    }
+        // drop any trailing *CR sequence
+        p.trim(crlf, false, true);
 
-    // Last whitespace SP = before start of protocol/version
-    if (last_whitespace >= line_end) {
-        request_parse_status = Http::scBadRequest; // missing version
-        return -1;
-    }
-    req.v_start = last_whitespace + 1;
-    req.v_end = line_end;
+        debugs(25, 5, "checking " << p);
+        p.consume(namelen + 1);
 
-    // 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 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.
-        req.v_maj = 0;
-        req.v_min = 9;
-        req.u_end = line_end;
-        request_parse_status = Http::scOkay; // treat as HTTP/0.9
-        return 1;
-#else
-        // protocol not supported / implemented.
-        request_parse_status = Http::scHttpVersionNotSupported;
-        return -1;
-#endif
-    }
+        // TODO: optimize SBuf::trim to take CharacterSet directly
+        ::Parser::Tokenizer t(p);
+        t.skipAll(CharacterSet::WSP);
+        p = t.remaining();
 
-    int i = req.v_start + sizeof("HTTP/") -1;
+        // prevent buffer overrun on char header[];
+        p.chop(0, sizeof(header)-1);
 
-    /* next should be 1 or more digits */
-    if (!isdigit(buf[i])) {
-        request_parse_status = Http::scHttpVersionNotSupported;
-        return -1;
-    }
-    int maj = 0;
-    for (; i <= line_end && (isdigit(buf[i])) && maj < 65536; ++i) {
-        maj = maj * 10;
-        maj = maj + (buf[i]) - '0';
+        // return the header field-value
+        xstrncpy(header, p.rawContent(), p.length());
+        debugs(25, 5, "returning " << header);
+        return header;
     }
-    // catch too-big values or missing remainders
-    if (maj >= 65536 || i > line_end) {
-        request_parse_status = Http::scHttpVersionNotSupported;
-        return -1;
-    }
-    req.v_maj = maj;
 
-    /* next should be .; we -have- to have this as we have a whole line.. */
-    if (buf[i] != '.') {
-        request_parse_status = Http::scHttpVersionNotSupported;
-        return -1;
-    }
-    // catch missing minor part
-    if (++i > line_end) {
-        request_parse_status = Http::scHttpVersionNotSupported;
-        return -1;
-    }
-    /* next should be one or more digits */
-    if (!isdigit(buf[i])) {
-        request_parse_status = Http::scHttpVersionNotSupported;
-        return -1;
-    }
-    int min = 0;
-    for (; i <= line_end && (isdigit(buf[i])) && min < 65536; ++i) {
-        min = min * 10;
-        min = min + (buf[i]) - '0';
-    }
-    // catch too-big values or trailing garbage
-    if (min >= 65536 || i < line_end) {
-        request_parse_status = Http::scHttpVersionNotSupported;
-        return -1;
-    }
-    req.v_min = min;
-
-    /*
-     * Rightio - we have all the schtuff. Return true; we've got enough.
-     */
-    request_parse_status = Http::scOkay;
-    return 1;
-}
-
-int
-HttpParserParseReqLine(HttpParser *hmsg)
-{
-    PROF_start(HttpParserParseReqLine);
-    int retcode = hmsg->parseRequestFirstLine();
-    debugs(74, 5, "Parser: retval " << retcode << ": from " << hmsg->req.start <<
-           "->" << hmsg->req.end << ": method " << hmsg->req.m_start << "->" <<
-           hmsg->req.m_end << "; url " << hmsg->req.u_start << "->" << hmsg->req.u_end <<
-           "; version " << hmsg->req.v_start << "->" << hmsg->req.v_end << " (" << hmsg->req.v_maj <<
-           "/" << hmsg->req.v_min << ")");
-    PROF_stop(HttpParserParseReqLine);
-    return retcode;
-}
-
-#if MSGDODEBUG
-/* XXX This should eventually turn into something inlined or #define'd */
-int
-HttpParserReqSz(HttpParser *hp)
-{
-    assert(hp->state == HTTP_PARSE_NEW);
-    assert(hp->req.start != -1);
-    assert(hp->req.end != -1);
-    return hp->req.end - hp->req.start + 1;
-}
-
-/*
- * This +1 makes it 'right' but won't make any sense if
- * there's a 0 byte header? This won't happen normally - a valid header
- * is at -least- a blank line (\n, or \r\n.)
- */
-int
-HttpParserHdrSz(HttpParser *hp)
-{
-    assert(hp->state == HTTP_PARSE_NEW);
-    assert(hp->hdr_start != -1);
-    assert(hp->hdr_end != -1);
-    return hp->hdr_end - hp->hdr_start + 1;
-}
-
-const char *
-HttpParserHdrBuf(HttpParser *hp)
-{
-    assert(hp->state == HTTP_PARSE_NEW);
-    assert(hp->hdr_start != -1);
-    assert(hp->hdr_end != -1);
-    return hp->buf + hp->hdr_start;
+    return NULL;
 }
-
-int
-HttpParserRequestLen(HttpParser *hp)
-{
-    return hp->hdr_end - hp->req.start + 1;
-}
-#endif
-

=== renamed file 'src/HttpParser.h' => 'src/http/one/Parser.h'
--- src/HttpParser.h	2014-09-13 13:59:43 +0000
+++ src/http/one/Parser.h	2014-09-14 12:26:22 +0000
@@ -1,104 +1,109 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
-#ifndef _SQUID_SRC_HTTPPARSER_H
-#define _SQUID_SRC_HTTPPARSER_H
+#ifndef _SQUID_SRC_HTTP_ONE_PARSER_H
+#define _SQUID_SRC_HTTP_ONE_PARSER_H
 
-#include "http/StatusCode.h"
+#include "anyp/ProtocolVersion.h"
+#include "http/one/forward.h"
+#include "SBuf.h"
+
+namespace Http {
+namespace One {
 
 // Parser states
-#define HTTP_PARSE_NONE   0 // nothing. completely unset state.
-#define HTTP_PARSE_NEW    1 // initialized, but nothing usefully parsed yet.
+enum ParseState {
+    HTTP_PARSE_NONE,     ///< initialized, but nothing usefully parsed yet
+    HTTP_PARSE_FIRST,    ///< HTTP/1 message first-line
+    HTTP_PARSE_MIME,     ///< HTTP/1 mime-header block
+    HTTP_PARSE_DONE      ///< parsed a message header, or reached a terminal syntax error
+};
 
-/** HTTP protocol parser.
+/** HTTP/1.x protocol parser
  *
  * Works on a raw character I/O buffer and tokenizes the content into
- * either an error state or, an HTTP procotol request major segments:
- *  1. Request Line (method, URL, protocol, version)
- *  2. Mime header block
+ * the major CRLF delimited segments of an HTTP/1 procotol message:
+ *
+ * \item first-line (request-line / simple-request / status-line)
+ * \item mime-header 0*( header-name ':' SP field-value CRLF)
  */
-class HttpParser
+class Parser : public RefCountable
 {
 public:
-    HttpParser() { clear(); }
+    typedef SBuf::size_type size_type;
 
-    /** Initialize a new parser.
-     * Presenting it a buffer to work on and the current length of available
-     * data.
-     * NOTE: This is *not* the buffer size, just the parse-able data length.
-     * The parse routines may be called again later with more data.
-     */
-    HttpParser(const char *aBuf, int len) { reset(aBuf,len); };
+    Parser() : parsingStage_(HTTP_PARSE_NONE) {}
 
     /// Set this parser back to a default state.
     /// Will DROP any reference to a buffer (does not free).
-    void clear();
+    virtual void clear();
+
+    /// attempt to parse a message from the buffer
+    /// \retval true if a full message was found and parsed
+    /// \retval false if incomplete, invalid or no message was found
+    virtual bool parse(const SBuf &aBuf) = 0;
+
+    /** Whether the parser is waiting on more data to complete parsing a message.
+     * Use to distinguish between incomplete data and error results
+     * when parse() returns false.
+     */
+    bool needsMoreData() const {return parsingStage_!=HTTP_PARSE_DONE;}
+
+    /// size in bytes of the first line including CRLF terminator
+    virtual size_type firstLineSize() const = 0;
 
-    /// Reset the parser for use on a new buffer.
-    void reset(const char *aBuf, int len);
+    /// size in bytes of the message headers including CRLF terminator(s)
+    /// but excluding first-line bytes
+    size_type headerBlockSize() const {return mimeHeaderBlock_.length();}
+
+    /// size in bytes of HTTP message block, includes first-line and mime headers
+    /// excludes any body/entity/payload bytes
+    /// excludes any garbage prefix before the first-line
+    size_type messageHeaderSize() const {return firstLineSize() + headerBlockSize();}
+
+    /// buffer containing HTTP mime headers, excluding message first-line.
+    SBuf mimeHeader() const {return mimeHeaderBlock_;}
+
+    /// the protocol label for this message
+    const AnyP::ProtocolVersion & messageProtocol() const {return msgProtocol_;}
 
     /**
-     * Attempt to parse the first line of a new request message.
+     * Scan the mime header block (badly) for a header with teh given name.
      *
-     * Governed by:
-     *  RFC 1945 section 5.1
-     *  RFC 2616 section 5.1
+     * BUG: omits lines when searching for headers with obs-fold or multiple entries.
      *
-     * Parsing state is stored between calls. However the current implementation
-     * begins parsing from scratch on every call.
-     * The return value tells you whether the parsing state fields are valid or not.
+     * BUG: limits output to just 1KB when Squid accepts up to 64KB line length.
      *
-     * \retval -1  an error occurred. request_parse_status indicates HTTP status result.
-     * \retval  1  successful parse. member fields contain the request-line items
-     * \retval  0  more data is needed to complete the parse
+     * \return A pointer to a field-value of the first matching field-name, or NULL.
      */
-    int parseRequestFirstLine();
+    char *getHeaderField(const char *name);
 
-public:
-    uint8_t state;
-    const char *buf;
-    int bufsiz;
-
-    /// Offsets for pieces of the (HTTP request) Request-Line as per RFC 2616
-    struct request_offsets {
-        int start, end;
-        int m_start, m_end; // method
-        int u_start, u_end; // url
-        int v_start, v_end; // version (full text)
-        int v_maj, v_min;   // version numerics
-    } req;
+    /// the remaining unprocessed section of buffer
+    const SBuf &remaining() const {return buf_;}
 
-    // Offsets for pieces of the MiME Header segment
-    int hdr_start, hdr_end;
+protected:
+    /// RFC 7230 section 2.6 - 7 magic octets
+    static const SBuf Http1magic;
 
-    // TODO: Offsets for pieces of the (HTTP reply) Status-Line as per RFC 2616
+    /// bytes remaining to be parsed
+    SBuf buf_;
 
-    /** 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;
+    /// what stage the parser is currently up to
+    ParseState parsingStage_;
+
+    /// what protocol label has been found in the first line (if any)
+    AnyP::ProtocolVersion msgProtocol_;
+
+    /// buffer holding the mime headers (if any)
+    SBuf mimeHeaderBlock_;
 };
 
-// Legacy functions
-#define HttpParserInit(h,b,l) (h)->reset((b),(l))
-int HttpParserParseReqLine(HttpParser *hp);
-
-#define MSGDODEBUG 0
-#if MSGDODEBUG
-int HttpParserReqSz(HttpParser *);
-int HttpParserHdrSz(HttpParser *);
-const char * HttpParserHdrBuf(HttpParser *);
-int HttpParserRequestLen(HttpParser *hp);
-#else
-#define HttpParserReqSz(hp)     ( (hp)->req.end - (hp)->req.start + 1 )
-#define HttpParserHdrSz(hp)     ( (hp)->hdr_end - (hp)->hdr_start + 1 )
-#define HttpParserHdrBuf(hp)    ( (hp)->buf + (hp)->hdr_start )
-#define HttpParserRequestLen(hp)        ( (hp)->hdr_end - (hp)->req.start + 1 )
-#endif
+} // namespace One
+} // namespace Http
 
-#endif /*  _SQUID_SRC_HTTPPARSER_H */
+#endif /*  _SQUID_SRC_HTTP_ONE_PARSER_H */

=== added file 'src/http/one/RequestParser.cc'
--- src/http/one/RequestParser.cc	1970-01-01 00:00:00 +0000
+++ src/http/one/RequestParser.cc	2014-10-11 19:43:55 +0000
@@ -0,0 +1,359 @@
+#include "squid.h"
+#include "Debug.h"
+#include "http/one/RequestParser.h"
+#include "http/ProtocolVersion.h"
+#include "mime_header.h"
+#include "profiler/Profiler.h"
+#include "SquidConfig.h"
+
+void
+Http::One::RequestParser::clear()
+{
+    Http1::Parser::clear();
+
+    request_parse_status = Http::scNone;
+    req.start = req.end = -1;
+    req.m_start = req.m_end = -1;
+    req.u_start = req.u_end = -1;
+    req.v_start = req.v_end = -1;
+    method_ = HttpRequestMethod();
+}
+
+/**
+ * Attempt to parse the first line of a new request message.
+ *
+ * Governed by RFC 7230 section 3.5
+ *  "
+ *    In the interest of robustness, a server that is expecting to receive
+ *    and parse a request-line SHOULD ignore at least one empty line (CRLF)
+ *    received prior to the request-line.
+ *  "
+ *
+ * Parsing state is stored between calls to avoid repeating buffer scans.
+ * If garbage is found the parsing offset is incremented.
+ */
+void
+Http::One::RequestParser::skipGarbageLines()
+{
+    if (Config.onoff.relaxed_header_parser) {
+        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
+        while (!buf_.isEmpty() && (buf_[0] == '\n' || (buf_[0] == '\r' && buf_[1] == '\n'))) {
+            buf_.consume(1);
+        }
+    }
+
+    /* XXX: this is a Squid-specific tolerance
+     * it appears never to have been relevant outside out unit-tests
+     * because the ConnStateData parser loop starts with consumeWhitespace()
+     * which absorbs any SP HTAB VTAB CR LF characters.
+     * But unit-tests called the HttpParser method directly without that pruning.
+     */
+#if USE_HTTP_VIOLATIONS
+    if (Config.onoff.relaxed_header_parser) {
+        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)
+        while (!buf_.isEmpty() && buf_[0] == ' ') {
+            buf_.consume(1);
+        }
+    }
+#endif
+}
+
+/**
+ * Attempt to parse the first line of a new request message.
+ *
+ * Governed by:
+ *  RFC 1945 section 5.1
+ *  RFC 7230 section 3.1 and 3.5
+ *
+ * Parsing state is stored between calls. However the current implementation
+ * begins parsing from scratch on every call.
+ * The return value tells you whether the parsing state fields are valid or not.
+ *
+ * \retval -1  an error occurred. request_parse_status indicates HTTP status result.
+ * \retval  1  successful parse. member fields contain the request-line items
+ * \retval  0  more data is needed to complete the parse
+ */
+int
+Http::One::RequestParser::parseRequestFirstLine()
+{
+    int second_word = -1; // track the suspected URI start
+    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: buf.length=" << buf_.length());
+    debugs(74, DBG_DATA, buf_);
+
+    // Single-pass parse: (provided we have the whole line anyways)
+
+    req.start = 0;
+    req.end = -1;
+    for (SBuf::size_type i = 0; i < buf_.length(); ++i) {
+        // track first and last whitespace (SP only)
+        if (buf_[i] == ' ') {
+            last_whitespace = i;
+            if (first_whitespace < req.start)
+                first_whitespace = i;
+        }
+
+        // track next non-SP/non-HT byte after first_whitespace
+        if (second_word < first_whitespace && buf_[i] != ' ' && buf_[i] != '\t') {
+            second_word = i;
+        }
+
+        // locate line terminator
+        if (buf_[i] == '\n') {
+            req.end = i;
+            line_end = i - 1;
+            break;
+        }
+        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: " <<
+                           "Series of carriage-return bytes received prior to line terminator. " <<
+                           "Ignored due to relaxed_header_parser.");
+
+                // 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 < buf_.length() - 1 && buf_[i + 1] == '\r')
+                    ++i;
+
+                if (buf_[i + 1] == '\n') {
+                    req.end = i + 1;
+                    break;
+                }
+            } else {
+                if (buf_[i + 1] == '\n') {
+                    req.end = i + 1;
+                    line_end = i - 1;
+                    break;
+                }
+            }
+
+            // RFC 7230 section 3.1.1 does not prohibit embeded CR like RFC 2616 used to.
+            // However it does explicitly state an exact syntax which omits un-encoded CR
+            // and defines 400 (Bad Request) as the required action when
+            // handed an invalid request-line.
+            request_parse_status = Http::scBadRequest;
+            return -1;
+        }
+    }
+
+    if (req.end == -1) {
+        // DoS protection against long first-line
+        if ((size_t)buf_.length() >= Config.maxRequestHeaderSize) {
+            debugs(33, 5, "Too large request-line");
+            // RFC 7230 section 3.1.1 mandatory 414 response if URL longer than acceptible.
+            request_parse_status = Http::scUriTooLong;
+            return -1;
+        }
+
+        debugs(74, 5, "Parser: retval 0: from " << req.start <<
+               "->" << req.end << ": needs more data to complete first line.");
+        return 0;
+    }
+
+    // NP: we have now seen EOL, more-data (0) cannot occur.
+    //     From here on any failure is -1, success is 1
+
+    // Input Validation:
+
+    // DoS protection against long first-line
+    if ((size_t)(req.end-req.start) >= Config.maxRequestHeaderSize) {
+        debugs(33, 5, "Too large request-line");
+        request_parse_status = Http::scUriTooLong;
+        return -1;
+    }
+
+    // Process what we now know about the line structure into field offsets
+    // generating HTTP status for any aborts as we go.
+
+    // First non-whitespace = beginning of method
+    if (req.start > line_end) {
+        request_parse_status = Http::scBadRequest;
+        return -1;
+    }
+    req.m_start = req.start;
+
+    // First whitespace = end of method
+    if (first_whitespace > line_end || first_whitespace < req.start) {
+        request_parse_status = Http::scBadRequest; // no method
+        return -1;
+    }
+    req.m_end = first_whitespace - 1;
+    if (req.m_end < req.m_start) {
+        request_parse_status = Http::scBadRequest; // missing URI?
+        return -1;
+    }
+
+    /* Set method_ */
+    const 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) {
+        request_parse_status = Http::scBadRequest; // missing URI
+        return -1;
+    }
+    req.u_start = second_word;
+
+    // RFC 1945: SP and version following URI are optional, marking version 0.9
+    // we identify this by the last whitespace being earlier than URI start
+    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 {
+        // otherwise last whitespace is somewhere after end of URI.
+        req.u_end = last_whitespace;
+        // crop any trailing whitespace in the area we think of as URI
+        for (; req.u_end >= req.u_start && xisspace(buf_[req.u_end]); --req.u_end);
+    }
+    if (req.u_end < req.u_start) {
+        request_parse_status = Http::scBadRequest; // missing URI
+        return -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) {
+        request_parse_status = Http::scBadRequest; // missing version
+        return -1;
+    }
+    req.v_start = last_whitespace + 1;
+    req.v_end = line_end;
+
+    /* RFC 7230 section 2.6 : handle unsupported HTTP major versions cleanly. */
+    if ((req.v_end - req.v_start +1) < (int)Http1magic.length() || !buf_.substr(req.v_start, SBuf::npos).startsWith(Http1magic)) {
+        // non-HTTP/1 protocols not supported / implemented.
+        request_parse_status = Http::scHttpVersionNotSupported;
+        return -1;
+    }
+    // NP: magic octets include the protocol name and major version DIGIT.
+    msgProtocol_.protocol = AnyP::PROTO_HTTP;
+    msgProtocol_.major = 1;
+
+    int i = req.v_start + Http1magic.length() -1;
+
+    // catch missing minor part
+    if (++i > line_end) {
+        request_parse_status = Http::scHttpVersionNotSupported;
+        return -1;
+    }
+    /* next should be one or more digits */
+    if (!isdigit(buf_[i])) {
+        request_parse_status = Http::scHttpVersionNotSupported;
+        return -1;
+    }
+    int min = 0;
+    for (; i <= line_end && (isdigit(buf_[i])) && min < 65536; ++i) {
+        min = min * 10;
+        min = min + (buf_[i]) - '0';
+    }
+    // catch too-big values or trailing garbage
+    if (min >= 65536 || i < line_end) {
+        request_parse_status = Http::scHttpVersionNotSupported;
+        return -1;
+    }
+    msgProtocol_.minor = min;
+
+    /*
+     * Rightio - we have all the schtuff. Return true; we've got enough.
+     */
+    request_parse_status = Http::scOkay;
+    return 1;
+}
+
+bool
+Http::One::RequestParser::parse(const SBuf &aBuf)
+{
+    buf_ = aBuf;
+    debugs(74, DBG_DATA, "Parse buf={length=" << aBuf.length() << ", data='" << aBuf << "'}");
+
+    // stage 1: locate the request-line
+    if (parsingStage_ == HTTP_PARSE_NONE) {
+        skipGarbageLines();
+
+        // if we hit something before EOS treat it as a message
+        if (!buf_.isEmpty())
+            parsingStage_ = HTTP_PARSE_FIRST;
+        else
+            return false;
+    }
+
+    // stage 2: parse the request-line
+    if (parsingStage_ == HTTP_PARSE_FIRST) {
+        PROF_start(HttpParserParseReqLine);
+        const int retcode = parseRequestFirstLine();
+
+        // first-line (or a look-alike) found successfully.
+        if (retcode > 0) {
+            buf_.consume(firstLineSize()); // first line bytes including CRLF terminator are now done.
+            parsingStage_ = HTTP_PARSE_MIME;
+        }
+
+        debugs(74, 5, "request-line: retval " << retcode << ": from " << req.start << "->" << req.end <<
+               " line={" << aBuf.length() << ", data='" << aBuf << "'}");
+        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: bytes processed=" << (aBuf.length()-buf_.length()));
+        PROF_stop(HttpParserParseReqLine);
+
+        // syntax errors already
+        if (retcode < 0) {
+            parsingStage_ = HTTP_PARSE_DONE;
+            return false;
+        }
+    }
+
+    // stage 3: locate the mime header block
+    if (parsingStage_ == HTTP_PARSE_MIME) {
+        // 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.
+             *       So the rest of the code will need to deal with '0'-byte headers
+             *       (ie, none, so don't try parsing em)
+             */
+            int64_t mimeHeaderBytes = 0;
+            // XXX: c_str() reallocates. performance regression.
+            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::scRequestHeaderFieldsTooLarge;
+                    parsingStage_ = HTTP_PARSE_DONE;
+                } else
+                    debugs(33, 5, "Incomplete request, waiting for end of headers");
+                return false;
+            }
+            mimeHeaderBlock_ = buf_.consume(mimeHeaderBytes);
+            debugs(74, 5, "mime header (0-" << mimeHeaderBytes << ") {" << mimeHeaderBlock_ << "}");
+
+        } else
+            debugs(33, 3, "Missing HTTP/1.x identifier");
+
+        // NP: we do not do any further stages here yet so go straight to DONE
+        parsingStage_ = HTTP_PARSE_DONE;
+
+        // Squid could handle these headers, but admin does not want to
+        if (messageHeaderSize() >= Config.maxRequestHeaderSize) {
+            debugs(33, 5, "Too large request");
+            request_parse_status = Http::scRequestHeaderFieldsTooLarge;
+            return false;
+        }
+    }
+
+    return !needsMoreData();
+}

=== added file 'src/http/one/RequestParser.h'
--- src/http/one/RequestParser.h	1970-01-01 00:00:00 +0000
+++ src/http/one/RequestParser.h	2014-10-11 09:16:29 +0000
@@ -0,0 +1,63 @@
+#ifndef _SQUID_SRC_HTTP_ONE_REQUESTPARSER_H
+#define _SQUID_SRC_HTTP_ONE_REQUESTPARSER_H
+
+#include "http/one/Parser.h"
+#include "http/RequestMethod.h"
+#include "http/StatusCode.h"
+
+namespace Http {
+namespace One {
+
+/** HTTP/1.x protocol request parser
+ *
+ * Works on a raw character I/O buffer and tokenizes the content into
+ * the major CRLF delimited segments of an HTTP/1 request message:
+ *
+ * \item request-line (method, URL, protocol, version)
+ * \item mime-header (set of RFC2616 syntax header fields)
+ */
+class RequestParser : public Http1::Parser
+{
+public:
+    /* Http::One::Parser API */
+    RequestParser() : Parser() {clear();}
+    virtual void clear();
+    virtual Http1::Parser::size_type firstLineSize() const {return req.end - req.start + 1;}
+    virtual bool parse(const SBuf &aBuf);
+
+    /// the HTTP method if this is a request message
+    const HttpRequestMethod & method() const {return method_;}
+
+    /// 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::StatusCode request_parse_status;
+
+private:
+    void skipGarbageLines();
+    int parseRequestFirstLine();
+
+    /// Offsets for pieces of the (HTTP request) Request-Line as per RFC 7230 section 3.1.1.
+    /// only valid before and during parse stage HTTP_PARSE_FIRST
+    struct request_offsets {
+        int start, end;
+        int m_start, m_end; // method
+        int u_start, u_end; // url
+        int v_start, v_end; // version (full text)
+    } req;
+
+    /// what request method has been found on the first line
+    HttpRequestMethod method_;
+
+    /// raw copy of the origina client reqeust-line URI field
+    SBuf uri_;
+};
+
+} // namespace One
+} // namespace Http
+
+#endif /*  _SQUID_SRC_HTTP_ONE_REQUESTPARSER_H */

=== added file 'src/http/one/forward.h'
--- src/http/one/forward.h	1970-01-01 00:00:00 +0000
+++ src/http/one/forward.h	2014-07-30 07:38:25 +0000
@@ -0,0 +1,20 @@
+#ifndef SQUID_SRC_HTTP_ONE_FORWARD_H
+#define SQUID_SRC_HTTP_ONE_FORWARD_H
+
+#include "base/RefCount.h"
+
+namespace Http {
+namespace One {
+
+class Parser;
+typedef RefCount<Http::One::Parser> ParserPointer;
+
+class RequestParser;
+typedef RefCount<Http::One::RequestParser> RequestParserPointer;
+
+} // namespace One
+} // namespace Http
+
+namespace Http1 = Http::One;
+
+#endif /* SQUID_SRC_HTTP_ONE_FORWARD_H */

=== modified file 'src/mgr/ActionParams.cc'
--- src/mgr/ActionParams.cc	2014-09-13 13:59:43 +0000
+++ src/mgr/ActionParams.cc	2014-09-14 12:23:03 +0000
@@ -6,41 +6,41 @@
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 16    Cache Manager API */
 
 #include "squid.h"
 #include "base/TextException.h"
 #include "ipc/TypedMsgHdr.h"
 #include "mgr/ActionParams.h"
 
 Mgr::ActionParams::ActionParams(): httpMethod(Http::METHOD_NONE)
 {
 }
 
 Mgr::ActionParams::ActionParams(const Ipc::TypedMsgHdr &msg)
 {
     msg.getString(httpUri);
 
     String method;
     msg.getString(method);
-    httpMethod = HttpRequestMethod(method.termedBuf(), NULL);
+    httpMethod = HttpRequestMethod(method.termedBuf());
 
     msg.getPod(httpFlags);
     msg.getString(httpOrigin);
 
     msg.getString(actionName);
     msg.getString(userName);
     msg.getString(password);
     queryParams.unpack(msg);
 }
 
 void
 Mgr::ActionParams::pack(Ipc::TypedMsgHdr &msg) const
 {
     msg.putString(httpUri);
     String foo(httpMethod.image().toString());
     msg.putString(foo);
     msg.putPod(httpFlags);
     msg.putString(httpOrigin);
 
     msg.putString(actionName);

=== modified file 'src/mgr/ActionParams.h'
--- src/mgr/ActionParams.h	2014-09-13 13:59:43 +0000
+++ src/mgr/ActionParams.h	2014-09-14 12:23:03 +0000
@@ -1,34 +1,34 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 16    Cache Manager API */
 
 #ifndef SQUID_MGR_ACTION_PARAMS_H
 #define SQUID_MGR_ACTION_PARAMS_H
 
-#include "HttpRequestMethod.h"
+#include "http/RequestMethod.h"
 #include "ipc/forward.h"
 #include "mgr/QueryParams.h"
 #include "RequestFlags.h"
 
 namespace Mgr
 {
 
 /// Cache Manager Action parameters extracted from the user request
 class ActionParams
 {
 public:
     ActionParams();
 
     explicit ActionParams(const Ipc::TypedMsgHdr &msg); ///< load from msg
     void pack(Ipc::TypedMsgHdr &msg) const; ///< store into msg
 
 public:
     /* details of the client HTTP request that caused the action */
     String httpUri; ///< HTTP request URI
     HttpRequestMethod httpMethod; ///< HTTP request method

=== modified file 'src/mgr/ActionWriter.h'
--- src/mgr/ActionWriter.h	2014-09-13 13:59:43 +0000
+++ src/mgr/ActionWriter.h	2014-09-14 12:23:03 +0000
@@ -1,35 +1,34 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 16    Cache Manager API */
 
 #ifndef SQUID_MGR_ACTION_WRITER_H
 #define SQUID_MGR_ACTION_WRITER_H
 
 #include "comm/forward.h"
-#include "HttpRequestMethod.h"
 #include "mgr/StoreToCommWriter.h"
 
 namespace Mgr
 {
 
 /// Creates Store entry, fills it using action's fillEntry(), and
 /// Comm-writes it using parent StoreToCommWriter.
 class ActionWriter: public StoreToCommWriter
 {
 public:
     ActionWriter(const Action::Pointer &anAction, const Comm::ConnectionPointer &conn);
 
 protected:
     /* AsyncJob API */
     virtual void start();
 
 private:
     Action::Pointer action; ///< action that fills the entry
 
     CBDATA_CLASS2(ActionWriter);

=== modified file 'src/mgr/Filler.h'
--- src/mgr/Filler.h	2014-09-13 13:59:43 +0000
+++ src/mgr/Filler.h	2014-09-14 12:23:03 +0000
@@ -1,35 +1,34 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 16    Cache Manager API */
 
 #ifndef SQUID_MGR_FILLER_H
 #define SQUID_MGR_FILLER_H
 
 #include "comm/forward.h"
-#include "HttpRequestMethod.h"
 #include "mgr/Action.h"
 #include "mgr/StoreToCommWriter.h"
 
 namespace Mgr
 {
 
 /// provides Coordinator with a local cache manager response
 class Filler: public StoreToCommWriter
 {
 public:
     Filler(const Action::Pointer &anAction, const Comm::ConnectionPointer &conn, unsigned int aRequestId);
 
 protected:
     /* AsyncJob API */
     virtual void start();
     virtual void swanSong();
 
 private:
     Action::Pointer action; ///< action that will run() and sendResponse()
     unsigned int requestId; ///< the ID of the Request we are responding to

=== modified file 'src/mime.cc'
--- src/mime.cc	2014-09-13 13:59:43 +0000
+++ src/mime.cc	2014-09-14 12:23:03 +0000
@@ -12,42 +12,40 @@
 #include "disk.h"
 #include "fde.h"
 #include "globals.h"
 #include "HttpHdrCc.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "internal.h"
 #include "Mem.h"
 #include "MemBuf.h"
 #include "MemObject.h"
 #include "mime.h"
 #include "RequestFlags.h"
 #include "SquidConfig.h"
 #include "Store.h"
 #include "StoreClient.h"
 
 #if HAVE_SYS_STAT_H
 #include <sys/stat.h>
 #endif
 
-#define GET_HDR_SZ 1024
-
 /* forward declarations */
 static void mimeFreeMemory(void);
 static char const *mimeGetIcon(const char *fn);
 
 class MimeIcon : public StoreClient
 {
 public:
     explicit MimeIcon(const char *aName);
     ~MimeIcon();
     void setName(char const *);
     char const * getName() const;
     void load();
     void created(StoreEntry *newEntry);
     MEMPROXY_CLASS(MimeIcon);
 
 private:
     const char *icon_;
     char *url_;
 };
 MEMPROXY_CLASS_INLINE(MimeIcon);

=== modified file 'src/mime_header.cc'
--- src/mime_header.cc	2014-09-13 13:59:43 +0000
+++ src/mime_header.cc	2014-09-14 12:23:03 +0000
@@ -1,116 +1,34 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 25    MiME Header Parsing */
 
 #include "squid.h"
-
-#define GET_HDR_SZ 1024
 #include "Debug.h"
 #include "profiler/Profiler.h"
 
-/*
- * returns a pointer to a field-value of the first matching field-name where
- * field-value matches prefix if any
- */
-char *
-mime_get_header_field(const char *mime, const char *name, const char *prefix)
-{
-    LOCAL_ARRAY(char, header, GET_HDR_SZ);
-    const char *p = NULL;
-    char *q = NULL;
-    char got = 0;
-    const int namelen = name ? strlen(name) : 0;
-    const int preflen = prefix ? strlen(prefix) : 0;
-    int l;
-
-    if (NULL == mime)
-        return NULL;
-
-    assert(NULL != name);
-
-    debugs(25, 5, "mime_get_header: looking for '" << name << "'");
-
-    for (p = mime; *p; p += strcspn(p, "\n\r")) {
-        if (strcmp(p, "\r\n\r\n") == 0 || strcmp(p, "\n\n") == 0)
-            return NULL;
-
-        while (xisspace(*p))
-            ++p;
-
-        if (strncasecmp(p, name, namelen))
-            continue;
-
-        if (!xisspace(p[namelen]) && p[namelen] != ':')
-            continue;
-
-        l = strcspn(p, "\n\r") + 1;
-
-        if (l > GET_HDR_SZ)
-            l = GET_HDR_SZ;
-
-        xstrncpy(header, p, l);
-
-        debugs(25, 5, "mime_get_header: checking '" << header << "'");
-
-        q = header;
-
-        q += namelen;
-
-        if (*q == ':') {
-            ++q;
-            got = 1;
-        }
-
-        while (xisspace(*q)) {
-            ++q;
-            got = 1;
-        }
-
-        if (got && prefix) {
-            /* we could process list entries here if we had strcasestr(). */
-            /* make sure we did not match a part of another field-value */
-            got = !strncasecmp(q, prefix, preflen) && !xisalpha(q[preflen]);
-        }
-
-        if (got) {
-            debugs(25, 5, "mime_get_header: returning '" << q << "'");
-            return q;
-        }
-    }
-
-    return NULL;
-}
-
-/* returns a pointer to a field-value of the first matching field-name */
-char *
-mime_get_header(const char *mime, const char *name)
-{
-    return mime_get_header_field(mime, name, NULL);
-}
-
 size_t
 headersEnd(const char *mime, size_t l)
 {
     size_t e = 0;
     int state = 1;
 
     PROF_start(headersEnd);
 
     while (e < l && state < 3) {
         switch (state) {
 
         case 0:
 
             if ('\n' == mime[e])
                 state = 1;
 
             break;
 
         case 1:
             if ('\r' == mime[e])

=== modified file 'src/mime_header.h'
--- src/mime_header.h	2014-09-13 13:59:43 +0000
+++ src/mime_header.h	2014-09-14 12:23:03 +0000
@@ -1,18 +1,16 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 25    MiME Header Parsing */
 
 #ifndef SQUID_MIME_HEADER_H_
 #define SQUID_MIME_HEADER_H_
 
-char *mime_get_header(const char *mime, const char *header);
-char *mime_get_header_field(const char *mime, const char *name, const char *prefix);
 size_t headersEnd(const char *, size_t);
 
 #endif /* SQUID_MIME_HEADER_H_ */

=== modified file 'src/servers/FtpServer.cc'
--- src/servers/FtpServer.cc	2014-09-29 05:13:17 +0000
+++ src/servers/FtpServer.cc	2014-10-05 10:43:33 +0000
@@ -7,40 +7,41 @@
  */
 
 /* DEBUG: section 33    Transfer protocol servers */
 
 #include "squid.h"
 #include "base/CharacterSet.h"
 #include "base/RefCount.h"
 #include "base/Subscription.h"
 #include "client_side_reply.h"
 #include "client_side_request.h"
 #include "clientStream.h"
 #include "comm/ConnOpener.h"
 #include "comm/Read.h"
 #include "comm/TcpAcceptor.h"
 #include "comm/Write.h"
 #include "errorpage.h"
 #include "fd.h"
 #include "ftp/Elements.h"
 #include "ftp/Parsing.h"
 #include "globals.h"
+#include "http/one/RequestParser.h"
 #include "HttpHdrCc.h"
 #include "ip/tools.h"
 #include "ipc/FdNotes.h"
 #include "parser/Tokenizer.h"
 #include "servers/forward.h"
 #include "servers/FtpServer.h"
 #include "SquidConfig.h"
 #include "StatCounters.h"
 #include "tools.h"
 
 #include <set>
 #include <map>
 
 CBDATA_NAMESPACED_CLASS_INIT(Ftp, Server);
 
 namespace Ftp
 {
 static void PrintReply(MemBuf &mb, const HttpReply *reply, const char *const prefix = "");
 static bool SupportedCommand(const SBuf &name);
 static bool CommandHasPathParameter(const SBuf &cmd);
@@ -123,49 +124,48 @@
 {
     // zero pipelinePrefetchMax() ensures that there is only parsed request
     ClientSocketContext::Pointer context = getCurrentContext();
     Must(context != NULL);
     Must(getConcurrentRequestCount() == 1);
 
     ClientHttpRequest *const http = context->http;
     assert(http != NULL);
 
     HttpRequest *const request = http->request;
     Must(http->storeEntry() || request);
     const bool mayForward = !http->storeEntry() && handleRequest(request);
 
     if (http->storeEntry() != NULL) {
         debugs(33, 4, "got an immediate response");
         clientSetKeepaliveFlag(http);
         context->pullData();
     } else if (mayForward) {
         debugs(33, 4, "forwarding request to server side");
         assert(http->storeEntry() == NULL);
-        clientProcessRequest(this, NULL /*parser*/, context.getRaw(),
-                             request->method, request->http_ver);
+        clientProcessRequest(this, Http1::RequestParserPointer(), context.getRaw());
     } else {
         debugs(33, 4, "will resume processing later");
     }
 }
 
 void
-Ftp::Server::processParsedRequest(ClientSocketContext *context, const Http::ProtocolVersion &)
+Ftp::Server::processParsedRequest(ClientSocketContext *context)
 {
     Must(getConcurrentRequestCount() == 1);
 
     // Process FTP request asynchronously to make sure FTP
     // data connection accept callback is fired first.
     CallJobHere(33, 4, CbcPointer<Server>(this),
                 Ftp::Server, doProcessRequest);
 }
 
 /// imports more upload data from the data connection
 void
 Ftp::Server::readUploadData(const CommIoCbParams &io)
 {
     debugs(33, 5, io.conn << " size " << io.size);
     Must(reader != NULL);
     reader = NULL;
 
     assert(Comm::IsConnOpen(dataConn));
     assert(io.conn->fd == dataConn->fd);
 
@@ -601,41 +601,41 @@
         // no default so that a compiler can check that we have covered all cases
     }
 
     ClientSocketContext *context = abortRequestParsing(errUri);
     clientStreamNode *node = context->getClientReplyContext();
     Must(node);
     clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
     Must(repContext);
 
     // We cannot relay FTP scode/reason via HTTP-specific ErrorState.
     // TODO: When/if ErrorState can handle native FTP errors, use it instead.
     HttpReply *reply = Ftp::HttpReplyWrapper(scode, reason, Http::scBadRequest, -1);
     repContext->setReplyToReply(reply);
     return context;
 }
 
 /// Parses a single FTP request on the control connection.
 /// Returns a new ClientSocketContext on valid requests and all errors.
 /// Returns NULL on incomplete requests that may still succeed given more data.
 ClientSocketContext *
-Ftp::Server::parseOneRequest(Http::ProtocolVersion &ver)
+Ftp::Server::parseOneRequest()
 {
     flags.readMore = false; // common for all but one case below
 
     // OWS <command> [ RWS <parameter> ] OWS LF
 
     // InlineSpaceChars are isspace(3) or RFC 959 Section 3.1.1.5.2, except
     // for the LF character that we must exclude here (but see FullWhiteSpace).
     static const char * const InlineSpaceChars = " \f\r\t\v";
     static const CharacterSet InlineSpace = CharacterSet("Ftp::Inline", InlineSpaceChars);
     static const CharacterSet FullWhiteSpace = (InlineSpace + CharacterSet::LF).rename("Ftp::FWS");
     static const CharacterSet CommandChars = FullWhiteSpace.complement("Ftp::Command");
     static const CharacterSet TailChars = CharacterSet::LF.complement("Ftp::Tail");
 
     // This set is used to ignore empty commands without allowing an attacker
     // to keep us endlessly busy by feeding us whitespace or empty commands.
     static const CharacterSet &LeadingSpace = FullWhiteSpace;
 
     SBuf cmd;
     SBuf params;
 
@@ -702,43 +702,42 @@
 
     if (!Ftp::SupportedCommand(cmd))
         return earlyError(eekUnsupportedCommand);
 
     const HttpRequestMethod method =
         cmd == cmdAppe() || cmd == cmdStor() || cmd == cmdStou() ?
         Http::METHOD_PUT : Http::METHOD_GET;
 
     const SBuf *path = (params.length() && CommandHasPathParameter(cmd)) ?
                        &params : NULL;
     calcUri(path);
     char *newUri = xstrdup(uri.c_str());
     HttpRequest *const request = HttpRequest::CreateFromUrlAndMethod(newUri, method);
     if (!request) {
         debugs(33, 5, "Invalid FTP URL: " << uri);
         uri.clear();
         safe_free(newUri);
         return earlyError(eekInvalidUri);
     }
 
-    ver = Http::ProtocolVersion(Ftp::ProtocolVersion().major, Ftp::ProtocolVersion().minor);
     request->flags.ftpNative = true;
-    request->http_ver = ver;
+    request->http_ver = Http::ProtocolVersion(Ftp::ProtocolVersion().major, Ftp::ProtocolVersion().minor);
 
     // Our fake Request-URIs are not distinctive enough for caching to work
     request->flags.cachable = false; // XXX: reset later by maybeCacheable()
     request->flags.noCache = true;
 
     request->header.putStr(HDR_FTP_COMMAND, cmd.c_str());
     request->header.putStr(HDR_FTP_ARGUMENTS, params.c_str()); // may be ""
     if (method == Http::METHOD_PUT) {
         request->header.putStr(HDR_EXPECT, "100-continue");
         request->header.putStr(HDR_TRANSFER_ENCODING, "chunked");
     }
 
     ClientHttpRequest *const http = new ClientHttpRequest(this);
     http->request = request;
     HTTPMSGLOCK(http->request);
     http->req_sz = tok.parsedSize();
     http->uri = newUri;
 
     ClientSocketContext *const result =
         new ClientSocketContext(clientConnection, http);

=== modified file 'src/servers/FtpServer.h'
--- src/servers/FtpServer.h	2014-09-13 13:59:43 +0000
+++ src/servers/FtpServer.h	2014-09-14 12:23:03 +0000
@@ -58,42 +58,42 @@
 
     // This is a pointer in hope to minimize future changes when MasterState
     // becomes a part of MasterXaction. Guaranteed not to be nil.
     MasterState::Pointer master; ///< info shared among our FTP client and server jobs
 
 protected:
     friend void StartListening();
 
     // errors detected before it is possible to create an HTTP request wrapper
     typedef enum {
         eekHugeRequest,
         eekMissingLogin,
         eekMissingUsername,
         eekMissingHost,
         eekUnsupportedCommand,
         eekInvalidUri,
         eekMalformedCommand
     } EarlyErrorKind;
 
     /* ConnStateData API */
-    virtual ClientSocketContext *parseOneRequest(Http::ProtocolVersion &ver);
-    virtual void processParsedRequest(ClientSocketContext *context, const Http::ProtocolVersion &ver);
+    virtual ClientSocketContext *parseOneRequest();
+    virtual void processParsedRequest(ClientSocketContext *context);
     virtual void notePeerConnection(Comm::ConnectionPointer conn);
     virtual void clientPinnedConnectionClosed(const CommCloseCbParams &io);
     virtual void handleReply(HttpReply *header, StoreIOBuffer receivedData);
     virtual int pipelinePrefetchMax() const;
     virtual void writeControlMsgAndCall(ClientSocketContext *context, HttpReply *rep, AsyncCall::Pointer &call);
     virtual time_t idleTimeout() const;
 
     /* BodyPipe API */
     virtual void noteMoreBodySpaceAvailable(BodyPipe::Pointer);
     virtual void noteBodyConsumerAborted(BodyPipe::Pointer ptr);
 
     /* AsyncJob API */
     virtual void start();
 
     /* Comm callbacks */
     static void AcceptCtrlConnection(const CommAcceptCbParams &params);
     void acceptDataConnection(const CommAcceptCbParams &params);
     void readUploadData(const CommIoCbParams &io);
     void wroteEarlyReply(const CommIoCbParams &io);
     void wroteReply(const CommIoCbParams &io);

=== modified file 'src/servers/HttpServer.cc'
--- src/servers/HttpServer.cc	2014-09-13 13:59:43 +0000
+++ src/servers/HttpServer.cc	2014-09-14 12:23:03 +0000
@@ -1,71 +1,72 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 33    Client-side Routines */
 
 #include "squid.h"
 #include "client_side.h"
 #include "client_side_request.h"
 #include "comm/Write.h"
 #include "HttpHeaderTools.h"
+#include "http/one/RequestParser.h"
 #include "profiler/Profiler.h"
 #include "servers/forward.h"
 #include "SquidConfig.h"
 
 namespace Http
 {
 
 /// Manages a connection from an HTTP client.
 class Server: public ConnStateData
 {
 public:
     Server(const MasterXaction::Pointer &xact, const bool beHttpsServer);
     virtual ~Server() {}
 
     void readSomeHttpData();
 
 protected:
     /* ConnStateData API */
-    virtual ClientSocketContext *parseOneRequest(Http::ProtocolVersion &ver);
-    virtual void processParsedRequest(ClientSocketContext *context, const Http::ProtocolVersion &ver);
+    virtual ClientSocketContext *parseOneRequest();
+    virtual void processParsedRequest(ClientSocketContext *context);
     virtual void handleReply(HttpReply *rep, StoreIOBuffer receivedData);
     virtual void writeControlMsgAndCall(ClientSocketContext *context, HttpReply *rep, AsyncCall::Pointer &call);
     virtual time_t idleTimeout() const;
 
     /* BodyPipe API */
     virtual void noteMoreBodySpaceAvailable(BodyPipe::Pointer);
     virtual void noteBodyConsumerAborted(BodyPipe::Pointer);
 
     /* AsyncJob API */
     virtual void start();
 
 private:
     void processHttpRequest(ClientSocketContext *const context);
     void handleHttpRequestData();
 
-    HttpParser parser_;
+    Http1::RequestParserPointer parser_;
     HttpRequestMethod method_; ///< parsed HTTP method
 
     /// temporary hack to avoid creating a true HttpsServer class
     const bool isHttpsServer;
 
     CBDATA_CLASS2(Server);
 };
 
 } // namespace Http
 
 CBDATA_NAMESPACED_CLASS_INIT(Http, Server);
 
 Http::Server::Server(const MasterXaction::Pointer &xact, bool beHttpsServer):
         AsyncJob("Http::Server"),
         ConnStateData(xact),
         isHttpsServer(beHttpsServer)
 {
 }
 
 time_t
@@ -92,54 +93,61 @@
     AsyncCall::Pointer timeoutCall =  JobCallback(33, 5,
                                       TimeoutDialer, this, Http::Server::requestTimeout);
     commSetConnTimeout(clientConnection, Config.Timeout.request, timeoutCall);
     readSomeData();
 }
 
 void
 Http::Server::noteMoreBodySpaceAvailable(BodyPipe::Pointer)
 {
     if (!handleRequestBodyData())
         return;
 
     // too late to read more body
     if (!isOpen() || stoppedReceiving())
         return;
 
     readSomeData();
 }
 
 ClientSocketContext *
-Http::Server::parseOneRequest(Http::ProtocolVersion &ver)
+Http::Server::parseOneRequest()
 {
-    ClientSocketContext *context = NULL;
     PROF_start(HttpServer_parseOneRequest);
-    HttpParserInit(&parser_, in.buf.c_str(), in.buf.length());
-    context = parseHttpRequest(this, &parser_, &method_, &ver);
+
+    // parser is incremental. Generate new parser state if we,
+    // a) dont have one already
+    // b) have completed the previous request parsing already
+    if (!parser_ || !parser_->needsMoreData())
+        parser_ = new Http1::RequestParser();
+
+    /* Process request */
+    ClientSocketContext *context = parseHttpRequest(this, parser_);
+
     PROF_stop(HttpServer_parseOneRequest);
     return context;
 }
 
 void
-Http::Server::processParsedRequest(ClientSocketContext *context, const Http::ProtocolVersion &ver)
+Http::Server::processParsedRequest(ClientSocketContext *context)
 {
-    clientProcessRequest(this, &parser_, context, method_, ver);
+    clientProcessRequest(this, parser_, context);
 }
 
 void
 Http::Server::noteBodyConsumerAborted(BodyPipe::Pointer ptr)
 {
     ConnStateData::noteBodyConsumerAborted(ptr);
     stopReceiving("virgin request body consumer aborted"); // closes ASAP
 }
 
 void
 Http::Server::handleReply(HttpReply *rep, StoreIOBuffer receivedData)
 {
     // the caller guarantees that we are dealing with the current context only
     ClientSocketContext::Pointer context = getCurrentContext();
     Must(context != NULL);
     const ClientHttpRequest *http = context->http;
     Must(http != NULL);
 
     // After sending Transfer-Encoding: chunked (at least), always send
     // the last-chunk if there was no error, ignoring responseFinishedOrFailed.

=== renamed file 'src/tests/testHttpParser.cc' => 'src/tests/testHttp1Parser.cc'
--- src/tests/testHttpParser.cc	2014-09-12 23:00:48 +0000
+++ src/tests/testHttp1Parser.cc	2014-10-11 19:36:02 +0000
@@ -1,1108 +1,1442 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #include "squid.h"
 
 #include <cppunit/TestAssert.h>
 
-#include "HttpParser.h"
+#define private public
+#define protected public
+
+#include "testHttp1Parser.h"
+#include "http/one/RequestParser.h"
+#include "http/RequestMethod.h"
 #include "Mem.h"
 #include "MemBuf.h"
 #include "SquidConfig.h"
-#include "testHttpParser.h"
+#include "testHttp1Parser.h"
 
-CPPUNIT_TEST_SUITE_REGISTRATION( testHttpParser );
+CPPUNIT_TEST_SUITE_REGISTRATION( testHttp1Parser );
 
 void
-testHttpParser::globalSetup()
+testHttp1Parser::globalSetup()
 {
     static bool setup_done = false;
     if (setup_done)
         return;
 
     Mem::Init();
     setup_done = true;
+
+    // default to strict parser. set for loose parsing specifically where behaviour differs.
+    Config.onoff.relaxed_header_parser = 0;
+
+    Config.maxRequestHeaderSize = 1024; // XXX: unit test the RequestParser handling of this limit
+}
+
+struct resultSet {
+    bool parsed;
+    bool needsMore;
+    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(input));
+    CPPUNIT_ASSERT_EQUAL(expect.needsMore, output.needsMoreData());
+    if (output.needsMoreData())
+        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
-testHttpParser::testParseRequestLineProtocols()
+testHttp1Parser::testParserConstruct()
+{
+    // whether the constructor works
+    {
+        Http1::RequestParser output;
+        CPPUNIT_ASSERT_EQUAL(true, output.needsMoreData());
+        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_NONE, output.parsingStage_);
+        CPPUNIT_ASSERT_EQUAL(Http::scNone, output.request_parse_status); // XXX: clear() not being called.
+        CPPUNIT_ASSERT_EQUAL(-1, output.req.start);
+        CPPUNIT_ASSERT_EQUAL(-1, output.req.end);
+        CPPUNIT_ASSERT(output.buf_.isEmpty());
+        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_start);
+        CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
+        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_NONE), output.method_);
+        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
+        CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
+        CPPUNIT_ASSERT(output.uri_.isEmpty());
+        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
+        CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
+        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
+    }
+
+    // whether new() works
+    {
+        Http1::RequestParser *output = new Http1::RequestParser;
+        CPPUNIT_ASSERT_EQUAL(true, output->needsMoreData());
+        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_NONE, output->parsingStage_);
+        CPPUNIT_ASSERT_EQUAL(Http::scNone, output->request_parse_status);
+        CPPUNIT_ASSERT_EQUAL(-1, output->req.start);
+        CPPUNIT_ASSERT_EQUAL(-1, output->req.end);
+        CPPUNIT_ASSERT(output->buf_.isEmpty());
+        CPPUNIT_ASSERT_EQUAL(-1, output->req.m_start);
+        CPPUNIT_ASSERT_EQUAL(-1, output->req.m_end);
+        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_NONE), output->method_);
+        CPPUNIT_ASSERT_EQUAL(-1, output->req.u_start);
+        CPPUNIT_ASSERT_EQUAL(-1, output->req.u_end);
+        CPPUNIT_ASSERT(output->uri_.isEmpty());
+        CPPUNIT_ASSERT_EQUAL(-1, output->req.v_start);
+        CPPUNIT_ASSERT_EQUAL(-1, output->req.v_end);
+        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output->msgProtocol_);
+        delete output;
+    }
+}
+
+void
+testHttp1Parser::testParseRequestLineProtocols()
 {
     // ensure MemPools etc exist
     globalSetup();
 
-    MemBuf input;
-    HttpParser output;
-    input.init();
+    SBuf input;
+    Http1::RequestParser output;
 
     // TEST: Do we comply with RFC 1945 section 5.1 ?
     // TEST: Do we comply with RFC 2616 section 5.1 ?
 
     // RFC 1945 : HTTP/0.9 simple-request
     {
         input.append("GET /\r\n", 7);
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(1, HttpParserParseReqLine(&output));
-        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(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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(9, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = true,
+            .needsMore = false,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(9, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = true,
+            .needsMore = false,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = true,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = true,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(2, output.req.v_min);
-        input.reset();
+    {
+        input.append("GET / HTTP/1.2\r\n", 16);
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = true,
+            .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.clear();
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
-    // RFC 2616 : future version full-request
+    // RFC 7230 : future versions do not use request-line syntax
     {
-        // XXX: IETF HTTPbis WG has made this two-digits format invalid.
         input.append("GET / HTTP/10.12\r\n", 18);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(1, HttpParserParseReqLine(&output));
-        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/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(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(10, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(12, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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()
+        };
+        output.clear();
+        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!
         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(1, HttpParserParseReqLine(&output));
-        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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(9, output.req.v_min);
-#else
-        CPPUNIT_ASSERT_EQUAL(-1, HttpParserParseReqLine(&output));
-        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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-#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(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();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // no version
     {
         input.append("GET / HTTP/\n", 12);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(-1, HttpParserParseReqLine(&output));
-        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(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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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()
+        };
+        output.clear();
+        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(-1, HttpParserParseReqLine(&output));
-        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(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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        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(-1, HttpParserParseReqLine(&output));
-        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(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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        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(-1, HttpParserParseReqLine(&output));
-        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(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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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()
+        };
+        output.clear();
+        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(-1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        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(-1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 }
 
 void
-testHttpParser::testParseRequestLineStrange()
+testHttp1Parser::testParseRequestLineStrange()
 {
     // ensure MemPools etc exist
     globalSetup();
 
-    MemBuf input;
-    HttpParser output;
-    input.init();
+    SBuf input;
+    Http1::RequestParser output;
 
     // 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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = true,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = true,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = true,
+            .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.clear();
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 }
 
 void
-testHttpParser::testParseRequestLineTerminators()
+testHttp1Parser::testParseRequestLineTerminators()
 {
     // ensure MemPools etc exist
     globalSetup();
 
-    MemBuf input;
-    HttpParser output;
-    input.init();
+    SBuf input;
+    Http1::RequestParser output;
 
     // 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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = true,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = true,
+            .needsMore = false,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
-    }
+        Config.onoff.relaxed_header_parser = 1;
+        struct resultSet expectRelaxed = {
+            .parsed = false,
+            .needsMore = true,
+            .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.clear();
+        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(-1, HttpParserParseReqLine(&output));
-        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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expectStrict = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        testResults(__LINE__, input, output, expectStrict);
+        input.clear();
     }
 
     // space padded version
     {
         // 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(-1, HttpParserParseReqLine(&output));
-        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(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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        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(0, HttpParserParseReqLine(&output));
-        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(-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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
-
-        input.append("GET ", 4);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(0, HttpParserParseReqLine(&output));
-        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(-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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
-
-        input.append("GET / HT", 8);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(0, HttpParserParseReqLine(&output));
-        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(-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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
-
-        input.append("GET / HTTP/1.1", 14);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(0, HttpParserParseReqLine(&output));
-        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(-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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 }
 
 void
-testHttpParser::testParseRequestLineMethods()
+testHttp1Parser::testParseRequestLineMethods()
 {
     // ensure MemPools etc exist
     globalSetup();
 
-    MemBuf input;
-    HttpParser output;
-    input.init();
+    SBuf input;
+    Http1::RequestParser output;
 
     // 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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = 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 = 2,
+            .uri = "/",
+            .versionStart = 4,
+            .versionEnd = 11,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+        };
+        output.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = true,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = true,
+            .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.clear();
+        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(-1, HttpParserParseReqLine(&output));
-        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(-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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        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(-1, HttpParserParseReqLine(&output));
-        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(-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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+    {
+        input.append("GET\n", 4);
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
-    }
+        struct resultSet expectRelaxed = {
+            .parsed = false,
+            .needsMore = true,
+            .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.clear();
+        testResults(__LINE__, input, output, expectRelaxed);
+#endif
 
-    // STRICT 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());
+        // STRICT mode obeys RFC syntax
         Config.onoff.relaxed_header_parser = 0;
-        CPPUNIT_ASSERT_EQUAL(-1, HttpParserParseReqLine(&output));
-        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(-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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expectStrict = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        testResults(__LINE__, input, output, expectStrict);
+        input.clear();
+    }
+
+    // RFC 2616 defined tolerance: ignore empty line(s) prefix on messages
+#if WHEN_RFC_COMPLIANT
+    {
+        input.append("\r\n\r\n\nGET / HTTP/1.1\r\n", 21);
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
+#endif
 
     // tab padded method (NP: tab is not SP so treated as any other binary)
     {
         input.append("\tGET / HTTP/1.1\n", 16);
-        //printf("TEST: '%s'\n",input.content());
-        output.reset(input.content(), input.contentSize());
-        CPPUNIT_ASSERT_EQUAL(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
+#if WHEN_RFC_COMPLIANT
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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,
+            .needsMore = true,
+            .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.clear();
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 }
 
 void
-testHttpParser::testParseRequestLineInvalid()
+testHttp1Parser::testParseRequestLineInvalid()
 {
     // ensure MemPools etc exist
     globalSetup();
 
-    MemBuf input;
-    HttpParser output;
-    input.init();
+    SBuf input;
+    Http1::RequestParser output;
 
     // 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(1, HttpParserParseReqLine(&output));
-        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(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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(9, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = true,
+            .needsMore = 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 = 9,
+            .uri = "HTTP/1.0",
+            .versionStart = -1,
+            .versionEnd = -1,
+            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9)
+        };
+        output.clear();
+        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());
-        // 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;
-        CPPUNIT_ASSERT_EQUAL(1, HttpParserParseReqLine(&output));
-        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(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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(9, output.req.v_min);
-        input.reset();
-    }
+        struct resultSet expectRelaxed = {
+            .parsed = true,
+            .needsMore = false,
+            .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.clear();
+        testResults(__LINE__, input, output, expectRelaxed);
 
-    // 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)
+        // STRICT detect as invalid
         Config.onoff.relaxed_header_parser = 0;
-        CPPUNIT_ASSERT_EQUAL(-1, HttpParserParseReqLine(&output));
-        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(-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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+#if WHEN_RFC_COMPLIANT
+        // XXX: except Squid does not
+        struct resultSet expectStrict = {
+            .parsed = false,
+            .needsMore = false,
+            .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,
+            .needsMore = false,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
+#if WHEN_RFC_COMPLIANT
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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,
+            .needsMore = true,
+            .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.clear();
+        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(-1, HttpParserParseReqLine(&output));
-        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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(1, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(1, output.req.v_min);
-        input.reset();
+#if WHEN_RFC_COMPLIANT
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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,
+            .needsMore = true,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(9, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = true,
+            .needsMore = false,
+            .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.clear();
+        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(1, HttpParserParseReqLine(&output));
-        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(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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(9, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = true,
+            .needsMore = false,
+            .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.clear();
+        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(-1, HttpParserParseReqLine(&output));
-        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(-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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // mixed whitespace line
     {
         // 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(-1, HttpParserParseReqLine(&output));
-        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(-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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        testResults(__LINE__, input, output, expect);
+        input.clear();
     }
 
     // mixed whitespace line with CR middle
     {
         // 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(-1, HttpParserParseReqLine(&output));
-        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(0, output.req.v_maj);
-        CPPUNIT_ASSERT_EQUAL(0, output.req.v_min);
-        input.reset();
+        struct resultSet expect = {
+            .parsed = false,
+            .needsMore = false,
+            .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.clear();
+        testResults(__LINE__, input, output, expect);
+        input.clear();
+    }
+}
+
+void
+testHttp1Parser::testDripFeed()
+{
+    // Simulate a client drip-feeding Squid a few bytes at a time.
+    // extend the size of the buffer from 0 bytes to full request length
+    // calling the parser repeatedly as visible data grows.
+
+    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.
+
+    SBuf ioBuf; // begins empty
+    Http1::RequestParser hp;
+
+    // only relaxed parser accepts the garbage whitespace
+    Config.onoff.relaxed_header_parser = 1;
+
+    // state of things we expect right now
+    struct resultSet expect = {
+        .parsed = false,
+        .needsMore = true,
+        .parserState = Http1::HTTP_PARSE_NONE,
+        .status = Http::scNone,
+        .msgStart = -1,
+        .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));
+
+        // 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;
+        }
+
+        // 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);
+        }
+
+        // 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.needsMore = false;
+            expect.suffixSz = 0;
+        }
+
+        testResults(__LINE__, ioBuf, hp, expect);
+
+        // sync the buffers like Squid does
+        ioBuf = hp.remaining();
+
+        // Squid stops using the parser once it has parsed the first message.
+        if (!hp.needsMoreData())
+            break;
     }
 }

=== renamed file 'src/tests/testHttpParser.h' => 'src/tests/testHttp1Parser.h'
--- src/tests/testHttpParser.h	2014-09-12 23:00:48 +0000
+++ src/tests/testHttp1Parser.h	2014-10-11 19:23:50 +0000
@@ -1,35 +1,41 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
-#ifndef SQUID_SRC_TESTS_TESTHTTPPARSER_H
-#define SQUID_SRC_TESTS_TESTHTTPPARSER_H
+#ifndef SQUID_SRC_TESTS_TESTHTTP1PARSER_H
+#define SQUID_SRC_TESTS_TESTHTTP1PARSER_H
 
 #include <cppunit/extensions/HelperMacros.h>
 
-class testHttpParser : public CPPUNIT_NS::TestFixture
+class testHttp1Parser : public CPPUNIT_NS::TestFixture
 {
-    CPPUNIT_TEST_SUITE( testHttpParser );
+    CPPUNIT_TEST_SUITE( testHttp1Parser );
+    CPPUNIT_TEST( testParserConstruct );
     CPPUNIT_TEST( testParseRequestLineTerminators );
     CPPUNIT_TEST( testParseRequestLineMethods );
     CPPUNIT_TEST( testParseRequestLineProtocols );
     CPPUNIT_TEST( testParseRequestLineStrange );
     CPPUNIT_TEST( testParseRequestLineInvalid );
+    CPPUNIT_TEST( testDripFeed );
     CPPUNIT_TEST_SUITE_END();
 
 protected:
     void globalSetup(); // MemPools init etc.
 
+    void testParserConstruct(); // whether the constructor works
+
     // request-line unit tests
     void testParseRequestLineTerminators(); // terminator detection correct
     void testParseRequestLineMethods();     // methoid detection correct
     void testParseRequestLineProtocols();   // protocol tokens handled correctly
     void testParseRequestLineStrange();     // strange but valid lines accepted
     void testParseRequestLineInvalid();     // rejection of invalid lines happens
+
+    void testDripFeed(); // test incremental parse works
 };
 
 #endif

=== modified file 'src/tests/testHttpRequestMethod.cc'
--- src/tests/testHttpRequestMethod.cc	2014-09-12 23:00:48 +0000
+++ src/tests/testHttpRequestMethod.cc	2014-09-14 12:23:03 +0000
@@ -1,67 +1,67 @@
 /*
  * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #include "squid.h"
 #include <cppunit/TestAssert.h>
 
-#include "HttpRequestMethod.h"
+#include "http/RequestMethod.h"
 #include "Mem.h"
 #include "SquidConfig.h"
 #include "testHttpRequestMethod.h"
 
 #include <sstream>
 
 CPPUNIT_TEST_SUITE_REGISTRATION( testHttpRequestMethod );
 
 /*
  * We should be able to make an HttpRequestMethod straight from a string.
  */
 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);
 }
 
 /*
  * We can also parse precise ranges of characters
  */
 void
 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);
 }
 
 /*
  * we should be able to assign a Http::MethodType to a HttpRequestMethod
  */
 void
 testHttpRequestMethod::testAssignFrommethod_t()
 {
     HttpRequestMethod method;
     method = Http::METHOD_NONE;
     CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_NONE), method);
     method = Http::METHOD_POST;
     CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_POST), method);
 }
 
 /*
  * a default constructed HttpRequestMethod is == Http::METHOD_NONE
  */
 void
 testHttpRequestMethod::testDefaultConstructor()
@@ -73,74 +73,74 @@
 
 /*
  * we should be able to construct a HttpRequestMethod from a Http::MethodType
  */
 void
 testHttpRequestMethod::testConstructmethod_t()
 {
     CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_NONE), HttpRequestMethod(Http::METHOD_NONE));
     CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_POST), HttpRequestMethod(Http::METHOD_POST));
     CPPUNIT_ASSERT(HttpRequestMethod(Http::METHOD_NONE) != HttpRequestMethod(Http::METHOD_POST));
 }
 
 /*
  * we should be able to get a char const * version of the method.
  */
 void
 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());
 }
 
 /*
  * an HttpRequestMethod should be comparable to a Http::MethodType without false
  * matches
  */
 void
 testHttpRequestMethod::testEqualmethod_t()
 {
     CPPUNIT_ASSERT(HttpRequestMethod(Http::METHOD_NONE) == Http::METHOD_NONE);
     CPPUNIT_ASSERT(not (HttpRequestMethod(Http::METHOD_POST) == Http::METHOD_GET));
     CPPUNIT_ASSERT(HttpRequestMethod(Http::METHOD_GET) == Http::METHOD_GET);
     CPPUNIT_ASSERT(not (HttpRequestMethod(Http::METHOD_TRACE) == Http::METHOD_SEARCH));
 }
 
 /*
  * an HttpRequestMethod should testable for inequality without fail maatches
  */
 void
 testHttpRequestMethod::testNotEqualmethod_t()
 {
     CPPUNIT_ASSERT(HttpRequestMethod(Http::METHOD_NONE) != Http::METHOD_GET);
     CPPUNIT_ASSERT(not (HttpRequestMethod(Http::METHOD_POST) != Http::METHOD_POST));
     CPPUNIT_ASSERT(HttpRequestMethod(Http::METHOD_GET) != Http::METHOD_NONE);
     CPPUNIT_ASSERT(not (HttpRequestMethod(Http::METHOD_SEARCH) != Http::METHOD_SEARCH));
 }
 
 /*
  * we should be able to send it to a stream and get the normalised version
  */
 void
 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()));
 }

-------------- next part --------------
A non-text attachment was scrubbed...
Name: Parser-NG_mk6.patch.sig
Type: application/octet-stream
Size: 287 bytes
Desc: not available
URL: <http://lists.squid-cache.org/pipermail/squid-dev/attachments/20141012/d9f20758/attachment-0001.obj>


More information about the squid-dev mailing list