=== modified file 'configure.ac' --- configure.ac 2014-05-11 19:12:35 +0000 +++ configure.ac 2014-05-20 07:21:32 +0000 @@ -3443,47 +3443,49 @@ scripts/Makefile src/Makefile src/anyp/Makefile src/base/Makefile src/acl/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/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 helpers/basic_auth/LDAP/Makefile helpers/basic_auth/MSNT/Makefile helpers/basic_auth/MSNT-multi-domain/Makefile helpers/basic_auth/NCSA/Makefile helpers/basic_auth/NIS/Makefile helpers/basic_auth/PAM/Makefile helpers/basic_auth/POP3/Makefile helpers/basic_auth/RADIUS/Makefile === modified file 'src/AccessLogEntry.h' --- src/AccessLogEntry.h 2014-04-22 02:47:09 +0000 +++ src/AccessLogEntry.h 2014-04-30 11:07:10 +0000 @@ -19,41 +19,41 @@ * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * * Copyright (c) 2003, Robert Collins */ #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-02-19 17:01:30 +0000 +++ src/HttpHeader.cc 2014-04-03 09:18:41 +0000 @@ -533,188 +533,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 ":[ws]" lines delimited by . * 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 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 */ } @@ -1569,52 +1570,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-03-30 12:00:34 +0000 +++ src/HttpHeader.h 2014-04-03 09:18:41 +0000 @@ -14,162 +14,56 @@ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #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 /* class forward declarations */ class HttpHdrCc; class HttpHdrContRange; class HttpHdrRange; class HttpHdrSc; class Packer; class StoreEntry; -/* constant attributes of http header fields */ - -/// recognized or "known" header fields; and the RFC which defines them (or not) -typedef enum { - HDR_BAD_HDR = -1, - HDR_ACCEPT = 0, /**< RFC 2608, 2616 */ - HDR_ACCEPT_CHARSET, /**< RFC 2608, 2616 */ - HDR_ACCEPT_ENCODING, /**< RFC 2608, 2616 */ - /*HDR_ACCEPT_FEATURES,*/ /* experimental RFC 2295 */ - HDR_ACCEPT_LANGUAGE, /**< RFC 2608, 2616 */ - HDR_ACCEPT_RANGES, /**< RFC 2608, 2616 */ - HDR_AGE, /**< RFC 2608, 2616 */ - HDR_ALLOW, /**< RFC 2608, 2616 */ - /*HDR_ALTERNATES,*/ /* deprecated RFC 2068, 2295 */ - HDR_AUTHORIZATION, /**< RFC 2608, 2616, 4559 */ - HDR_CACHE_CONTROL, /**< RFC 2608, 2616 */ - HDR_CONNECTION, /**< RFC 2608, 2616 */ - HDR_CONTENT_BASE, /**< RFC 2608 */ - HDR_CONTENT_DISPOSITION, /**< RFC 2183, 2616 */ - HDR_CONTENT_ENCODING, /**< RFC 2608, 2616 */ - HDR_CONTENT_LANGUAGE, /**< RFC 2608, 2616 */ - HDR_CONTENT_LENGTH, /**< RFC 2608, 2616 */ - HDR_CONTENT_LOCATION, /**< RFC 2608, 2616 */ - HDR_CONTENT_MD5, /**< RFC 2608, 2616 */ - HDR_CONTENT_RANGE, /**< RFC 2608, 2616 */ - HDR_CONTENT_TYPE, /**< RFC 2608, 2616 */ - /*HDR_CONTENT_VERSION,*/ /* deprecated RFC 2608 header. */ - HDR_COOKIE, /**< de-facto and RFC 2965 header we may need to erase */ - HDR_COOKIE2, /**< obsolete RFC 2965 header we may need to erase */ - HDR_DATE, /**< RFC 2608, 2616 */ - /*HDR_DAV,*/ /* RFC 2518 */ - /*HDR_DEPTH,*/ /* RFC 2518 */ - /*HDR_DERIVED_FROM,*/ /* deprecated RFC 2608 */ - /*HDR_DESTINATION,*/ /* RFC 2518 */ - HDR_ETAG, /**< RFC 2608, 2616 */ - HDR_EXPECT, /**< RFC 2616, 2616 */ - HDR_EXPIRES, /**< RFC 2608, 2616 */ - HDR_FROM, /**< RFC 2608, 2616 */ - HDR_HOST, /**< RFC 2608, 2616 */ - HDR_HTTP2_SETTINGS, /**< HTTP/2.0 upgrade header. see draft-ietf-httpbis-http2-04 */ - /*HDR_IF,*/ /* RFC 2518 */ - HDR_IF_MATCH, /**< RFC 2608, 2616 */ - HDR_IF_MODIFIED_SINCE, /**< RFC 2608, 2616 */ - HDR_IF_NONE_MATCH, /**< RFC 2608, 2616 */ - HDR_IF_RANGE, /**< RFC 2608, 2616 */ - /*HDR_IF_UNMODIFIED_SINCE,*/ /**< RFC 2608, 2616 */ - HDR_KEEP_ALIVE, /**< obsolete HTTP/1.0 header we may need to erase */ - HDR_KEY, /**< experimental RFC Draft draft-fielding-http-key-02 */ - HDR_LAST_MODIFIED, /**< RFC 2608, 2616 */ - HDR_LINK, /**< RFC 2068 */ - HDR_LOCATION, /**< RFC 2608, 2616 */ - /*HDR_LOCK_TOKEN,*/ /* RFC 2518 */ - HDR_MAX_FORWARDS, /**< RFC 2608, 2616 */ - HDR_MIME_VERSION, /**< RFC 2626 */ - 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, /**< deprecated RFC 2068,2616 header we may need to erase */ - HDR_PROXY_AUTHENTICATE, /**< RFC 2608, 2616, 2617 */ - HDR_PROXY_AUTHENTICATION_INFO, /**< RFC 2617 */ - HDR_PROXY_AUTHORIZATION, /**< RFC 2608, 2616, 2617 */ - HDR_PROXY_CONNECTION, /**< obsolete Netscape header we may need to erase. */ - HDR_PROXY_SUPPORT, /**< RFC 4559 */ - HDR_PUBLIC, /**< RFC 2608 */ - HDR_RANGE, /**< RFC 2608, 2616 */ - HDR_REFERER, /**< RFC 2608, 2616 */ - HDR_REQUEST_RANGE, /**< some clients use this, sigh */ - HDR_RETRY_AFTER, /**< RFC 2608, 2616 */ - HDR_SERVER, /**< RFC 2608, 2616 */ - HDR_SET_COOKIE, /**< de-facto standard header we may need to erase */ - HDR_SET_COOKIE2, /**< obsolete RFC 2965 header we may need to erase */ - /*HDR_STATUS_URI,*/ /* RFC 2518 */ - /*HDR_TCN,*/ /* experimental RFC 2295 */ - HDR_TE, /**< RFC 2616 */ - /*HDR_TIMEOUT,*/ /* RFC 2518 */ - HDR_TITLE, /* obsolete draft suggested header */ - HDR_TRAILER, /**< RFC 2616 */ - HDR_TRANSFER_ENCODING, /**< RFC 2608, 2616 */ - 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 2608, 2616 */ - /*HDR_URI,*/ /* obsolete RFC 2068 header */ - HDR_USER_AGENT, /**< RFC 2608, 2616 */ - /*HDR_VARIANT_VARY,*/ /* experimental RFC 2295 */ - HDR_VARY, /**< RFC 2608, 2616 */ - HDR_VIA, /**< RFC 2608, 2616 */ - HDR_WARNING, /**< RFC 2608, 2616 */ - HDR_WWW_AUTHENTICATE, /**< RFC 2608, 2616, 2617, 4559 */ - HDR_AUTHENTICATION_INFO, /**< RFC 2617 */ - 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, /**< Squid custom header */ - 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_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, @@ -219,41 +113,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-03-30 12:00:34 +0000 +++ src/HttpHeaderTools.cc 2014-04-03 09:18:41 +0000 @@ -168,46 +168,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-02-21 10:46:19 +0000 +++ src/HttpHeaderTools.h 2014-04-03 09:18:41 +0000 @@ -99,25 +99,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-04-27 07:59:17 +0000 +++ src/HttpMsg.cc 2014-05-06 17:10:51 +0000 @@ -41,41 +41,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); @@ -261,48 +261,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-04-27 07:59:17 +0000 +++ src/HttpMsg.h 2014-05-06 17:17:09 +0000 @@ -16,44 +16,44 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #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 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); @@ -103,26 +103,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-04-27 07:59:17 +0000 +++ src/HttpRequest.cc 2014-05-20 09:22:38 +0000 @@ -24,40 +24,41 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * * Copyright (c) 2003, Robert Collins */ #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(); @@ -286,54 +287,55 @@ * 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); + method = HttpRequestMethod(m); if (method == Http::METHOD_NONE) return false; start = t + strspn(t, w_space); 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; } @@ -342,49 +344,49 @@ 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); + 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 terminator */ void === modified file 'src/HttpRequest.h' --- src/HttpRequest.h 2014-04-27 07:59:17 +0000 +++ src/HttpRequest.h 2014-05-06 17:21:45 +0000 @@ -19,41 +19,41 @@ * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #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; @@ -214,41 +214,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-04-30 10:50:09 +0000 +++ src/Makefile.am 2014-05-19 23:32:00 +0000 @@ -29,42 +29,42 @@ StoreMeta.cc \ StoreMeta.h \ StoreMetaMD5.cc \ StoreMetaMD5.h \ StoreMetaSTD.cc \ StoreMetaSTD.h \ StoreMetaSTDLFS.cc \ StoreMetaSTDLFS.h \ StoreMetaObjSize.h \ StoreMetaURL.cc \ StoreMetaURL.h \ StoreMetaVary.cc \ StoreMetaVary.h LOADABLE_MODULES_SOURCES = \ LoadableModule.h \ LoadableModule.cc \ LoadableModules.h \ LoadableModules.cc -SUBDIRS = base anyp comm eui acl format fs repl -DIST_SUBDIRS = base anyp comm eui acl format fs repl +SUBDIRS = base anyp parser comm eui acl format fs repl +DIST_SUBDIRS = base anyp parser comm eui acl format fs repl if ENABLE_AUTH SUBDIRS += auth AUTH_LIBS= auth/libauth.la AUTH_ACL_LIBS= auth/libacls.la check_PROGRAMS+= tests/testACLMaxUserIP endif DIST_SUBDIRS += auth SUBDIRS += http ip icmp ident log ipc mgr DIST_SUBDIRS += http ip icmp ident log ipc mgr if ENABLE_SSL SUBDIRS += ssl SSL_LIBS = \ ssl/libsslsquid.la \ ssl/libsslutil.la else SSL_LOCAL_LIBS = endif @@ -391,50 +391,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 \ @@ -727,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 \ HelperChildConfig.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) \ @@ -1055,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) @@ -1233,41 +1230,40 @@ tests/stub_fatal.cc \ FileMap.h \ filemap.cc \ HelperChildConfig.h \ HelperChildConfig.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) \ @@ -1368,46 +1364,43 @@ tests_testBoilerplate_SOURCES = \ tests/testBoilerplate.cc \ tests/testMain.cc \ tests/testBoilerplate.h \ tests/stub_time.cc nodist_tests_testBoilerplate_SOURCES = \ $(TESTSOURCES) tests_testBoilerplate_LDADD= \ $(SQUID_CPPUNIT_LIBS) \ $(SSLLIB) \ $(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 \ @@ -1655,41 +1648,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 \ @@ -1884,49 +1876,46 @@ HelperReply.h \ 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 \ @@ -2134,49 +2123,46 @@ HelperReply.h \ 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 \ @@ -2380,49 +2366,46 @@ HelperReply.h \ 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 \ @@ -2539,96 +2522,94 @@ $(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 \ + 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 \ @@ -2867,41 +2848,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 \ @@ -3135,41 +3115,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 \ @@ -3293,41 +3272,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 \ @@ -3508,49 +3486,46 @@ HelperReply.h \ 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 \ === modified file 'src/MemObject.h' --- src/MemObject.h 2013-12-31 18:49:41 +0000 +++ src/MemObject.h 2014-01-04 23:15:54 +0000 @@ -16,41 +16,41 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #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-02-21 10:46:19 +0000 +++ src/Store.h 2014-04-03 09:18:41 +0000 @@ -22,42 +22,43 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #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 class AsyncCall; class HttpRequest; class Packer; class RequestFlags; class StoreClient; class StoreSearch; class SwapDir; === modified file 'src/acl/Method.h' --- src/acl/Method.h 2013-10-25 00:13:46 +0000 +++ src/acl/Method.h 2013-12-23 16:20:15 +0000 @@ -18,41 +18,41 @@ * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * * * Copyright (c) 2003, Robert Collins */ #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 { public: virtual int match (ACLData * &, 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-05-09 17:32:25 +0000 +++ src/acl/MethodData.cc 2014-05-17 05:48:28 +0000 @@ -19,41 +19,41 @@ * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * * * Copyright (c) 2003, Robert Collins */ #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) @@ -72,40 +72,40 @@ { SBufList sl; CbDataList *data = values; while (data != NULL) { sl.push_back(data->element.image()); data = data->next; } return sl; } void ACLMethodData::parse() { CbDataList **Tail; char *t = NULL; for (Tail = &values; *Tail; Tail = &((*Tail)->next)); while ((t = strtokFile())) { - CbDataList *q = new CbDataList (HttpRequestMethod(t, NULL)); + CbDataList *q = new CbDataList (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 * ACLMethodData::clone() const { assert (!values); return new ACLMethodData(*this); } === modified file 'src/acl/MethodData.h' --- src/acl/MethodData.h 2014-04-12 17:32:34 +0000 +++ src/acl/MethodData.h 2014-04-30 11:07:10 +0000 @@ -19,41 +19,41 @@ * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * * * Copyright (c) 2003, Robert Collins */ #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 { 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 *clone() const; CbDataList *values; === modified file 'src/cache_cf.cc' --- src/cache_cf.cc 2014-05-07 14:40:05 +0000 +++ src/cache_cf.cc 2014-05-17 05:48:00 +0000 @@ -37,41 +37,40 @@ #include "acl/AclNameList.h" #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 "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-04-27 07:59:17 +0000 +++ src/client_side.cc 2014-05-20 10:45:28 +0000 @@ -87,40 +87,41 @@ #include "ChunkedCodingParser.h" #include "client_db.h" #include "client_side.h" #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/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 "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 "profiler/Profiler.h" #include "rfc1738.h" #include "SquidConfig.h" #include "SquidTime.h" #include "StatCounters.h" #include "StatHist.h" @@ -179,41 +180,41 @@ AnyP::PortCfg *portCfg; ///< from Config.Sockaddr.http Ipc::FdNoteId portTypeNote; ///< Type of IPC socket being opened Subscription::Pointer sub; ///< The handler to be subscribed for this connetion listener }; static void clientListenerConnectionOpened(AnyP::PortCfg *s, const Ipc::FdNoteId portTypeNote, const Subscription::Pointer &sub); /* our socket-related context */ CBDATA_CLASS_INIT(ClientSocketContext); /* Local functions */ static IOCB clientWriteComplete; static IOCB clientWriteBodyComplete; static IOACB httpAccept; #if USE_OPENSSL static IOACB httpsAccept; #endif static CTCB clientLifetimeTimeout; static ClientSocketContext *parseHttpRequestAbort(ConnStateData * conn, const char *uri); -static ClientSocketContext *parseHttpRequest(ConnStateData *, HttpParser *, HttpRequestMethod *, Http::ProtocolVersion *); +static ClientSocketContext *parseHttpRequest(ConnStateData *, Http1::RequestParser &); #if USE_IDENT static IDCB clientIdentDone; #endif static CSCB clientSocketRecipient; static CSD clientSocketDetach; static void clientSetKeepaliveFlag(ClientHttpRequest *); 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 int responseFinishedOrFailed(HttpReply * rep, StoreIOBuffer const &receivedData); static void ClientSocketContextPushDeferredIfNeeded(ClientSocketContext::Pointer deferredRequest, ConnStateData * conn); static void clientUpdateSocketStats(LogTags logType, size_t size); @@ -2040,357 +2041,283 @@ char *tmp_uri = static_cast(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, Http1::RequestParser &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 */ + const char *url = SBuf(hp.requestUri()).c_str(); // XXX: performance regression. convert to SBuf parse if (*url != '/') { if (conn->port->vhost) return; /* already in good shape */ /* else we need to ignore the host name */ url = strstr(url, "//"); #if SHOULD_REJECT_UNKNOWN_URLS if (!url) { - hp->request_parse_status = Http::scBadRequest; + hp.request_parse_status = Http::scBadRequest; return parseHttpRequestAbort(conn, "error:invalid-request"); } #endif if (url) url = strchr(url + 2, '/'); if (!url) url = (char *) "/"; } 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 + + 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 << "'"); } 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 <<"'"); } else if (vport > 0 /* && (!vhost || no Host:) */) { debugs(33, 5, "ACCEL VPORT REWRITE: http_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", AnyP::UriScheme(conn->port->transport.protocol).c_str(), ipbuf, vport, 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, Http1::RequestParser &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. */ static ClientSocketContext * -parseHttpRequest(ConnStateData *csd, HttpParser *hp, HttpRequestMethod * method_p, Http::ProtocolVersion *http_ver) +parseHttpRequest(ConnStateData *csd, Http1::RequestParser &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 parseHttpRequestAbort(csd, "error:request-too-large"); - } - - /* Attempt to parse the first line; this'll define the method, url, version and header begin */ - r = HttpParserParseReqLine(hp); + /* 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); - if (r == 0) { - debugs(33, 5, "Incomplete request, waiting for end of request line"); - return NULL; - } - - if (r == -1) { - return parseHttpRequestAbort(csd, "error:invalid-request"); - } + // sync the buffers after parsing. + csd->in.buf = hp.buf; - /* Request line is valid here .. */ - *http_ver = Http::ProtocolVersion(hp->req.v_maj, hp->req.v_min); - - /* 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); + if (!parsedOk) { + if (hp.request_parse_status == Http::scHeaderTooLarge) + return parseHttpRequestAbort(csd, "error:request-too-large"); - hp->hdr_end = req_sz - 1; - - 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 parseHttpRequestAbort(csd, "error:request-too-large"); + return parseHttpRequestAbort(csd, "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 && csd->port->flags.accelSurrogate) { + if (hp.method() == Http::METHOD_CONNECT && csd->port && 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); - hp->request_parse_status = Http::scMethodNotAllowed; + debugs(33, DBG_IMPORTANT, "WARNING: for request: " << hp.method() << " " << hp.requestUri() << " " << hp.messageProtocol()); + hp.request_parse_status = Http::scMethodNotAllowed; return parseHttpRequestAbort(csd, "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 << "'"); - hp->request_parse_status = Http::scMethodNotAllowed; + 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 parseHttpRequestAbort(csd, "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 + 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 */ 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; } const SBuf::size_type wantCapacity = min(static_cast(Config.maxRequestBufferSize), haveCapacity*2); buf.reserveCapacity(wantCapacity); debugs(33, 2, "growing request buffer: available=" << buf.spaceSize() << " used=" << buf.length()); } return (buf.spaceSize() >= 2); } void @@ -2446,61 +2373,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 connNoteUseOfBuffer(ConnStateData* conn, size_t byteCount) { assert(byteCount > 0 && byteCount <= conn->in.buf.length()); conn->in.buf.consume(byteCount); debugs(33, 5, "conn->in.buf has " << conn->in.buf.length() << " bytes unused."); } -/// respond with ERR_TOO_BIG if request header exceeds request_header_max_size -void -ConnStateData::checkHeaderLimits() -{ - if (in.buf.length() < Config.maxRequestHeaderSize) - return; // can accumulte more header data - - debugs(33, 3, "Request header is too large (" << in.buf.length() << " > " << - Config.maxRequestHeaderSize << " bytes)"); - - ClientSocketContext *context = parseHttpRequestAbort(this, "error:request-too-large"); - clientStreamNode *node = context->getClientReplyContext(); - clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); - assert (repContext); - repContext->setReplyToError(ERR_TOO_BIG, - Http::scBadRequest, Http::METHOD_NONE, NULL, - clientConnection->remote, NULL, NULL, NULL); - context->registerWithConn(); - context->pullData(); -} - 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. @@ -2580,118 +2486,102 @@ srvCert, NULL); err->detail = errDetail; // Save the original request for logging purposes. if (!context->http->al->request) { context->http->al->request = request; HTTPMSGLOCK(context->http->al->request); } repContext->setReplyToError(request->method, err); assert(context->http->out.offset == 0); context->pullData(); return true; } } } return false; } #endif // USE_OPENSSL static void -clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *context, const HttpRequestMethod& method, Http::ProtocolVersion http_ver) +clientProcessRequest(ConnStateData *conn, Http1::RequestParser &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; + const AnyP::ProtocolVersion &http_ver = hp.messageProtocol(); + const HttpRequestMethod &method = hp.method(); /* We have an initial client stream in place should it be needed */ /* setup our private context */ context->registerWithConn(); 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); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); - switch (hp->request_parse_status) { + 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); break; case Http::scMethodNotAllowed: repContext->setReplyToError(ERR_UNSUP_REQ, Http::scMethodNotAllowed, method, http->uri, conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL); break; + case Http::scHttpVersionNotSupported: + repContext->setReplyToError(ERR_UNSUP_HTTPVERSION, Http::scHttpVersionNotSupported, method, http->uri, + conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL); + break; default: - repContext->setReplyToError(ERR_INVALID_REQ, hp->request_parse_status, method, http->uri, + repContext->setReplyToError(ERR_INVALID_REQ, hp.request_parse_status, method, http->uri, conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL); } assert(context->http->out.offset == 0); context->pullData(); goto finish; } if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, 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); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); repContext->setReplyToError(ERR_INVALID_URL, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); goto finish; } - /* RFC 2616 section 10.5.6 : handle unsupported HTTP major versions cleanly. */ - /* We currently only support 0.9, 1.0, 1.1 properly */ - if ( (http_ver.major == 0 && http_ver.minor != 9) || - (http_ver.major > 1) ) { - - clientStreamNode *node = context->getClientReplyContext(); - debugs(33, 5, "Unsupported HTTP version discovered. :\n" << HttpParserHdrBuf(hp)); - conn->quitAfterError(request.getRaw()); - // setLogUri should called before repContext->setReplyToError - setLogUri(http, http->uri, true); - clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); - assert (repContext); - repContext->setReplyToError(ERR_UNSUP_HTTPVERSION, Http::scHttpVersionNotSupported, method, http->uri, - conn->clientConnection->remote, NULL, HttpParserHdrBuf(hp), NULL); - assert(context->http->out.offset == 0); - context->pullData(); - goto finish; - } - /* 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 (http_ver.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); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); repContext->setReplyToError(ERR_INVALID_REQ, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); goto finish; } 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 @@ -2731,41 +2621,45 @@ 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; + // XXX: for non-HTTP messages instantiate a different HttpMsg child type + // for now Squid only supports HTTP requests + 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(node->data.getRaw()); assert (repContext); @@ -2796,202 +2690,178 @@ clientStreamNode *node = context->getClientReplyContext(); clientReplyContext *repContext = dynamic_cast(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(); goto finish; } } 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)) goto finish; #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); - // 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(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(); goto finish; } // We may stop producing, comm_close, and/or call setReplyToError() // below, so quit on errors to avoid http->doCallouts() if (!conn->handleRequestBodyData()) goto finish; if (!request->body_pipe->productionEnded()) { debugs(33, 5, HERE << "need more request body"); context->mayUseConnection(true); assert(conn->flags.readMore); } } http->calloutContext = new ClientRequestContext(http); http->doCallouts(); finish: - if (!notedUseOfBuffer) - connNoteUseOfBuffer(conn, http->req_sz); - /* * 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. */ 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); } } -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); - } -} - /** * 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 = Config.pipeline_max_prefetch + 1; // when queue filled already we cant add more. if (existingRequestCount >= concurrentRequestLimit) { debugs(33, 3, clientConnection << " max concurrent requests reached (" << concurrentRequestLimit << ")"); debugs(33, 5, clientConnection << " deferring new request until one is done"); return true; } return false; } /** * Attempt to parse one or more requests from the input buffer. * If a request is successfully parsed, even if the next request * is only partially parsed, it will return TRUE. */ bool ConnStateData::clientParseRequests() { - HttpRequestMethod method; 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; /* Begin the parsing */ PROF_start(parseHttpRequest); - HttpParserInit(&parser_, in.buf.c_str(), in.buf.length()); + + // 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 */ - Http::ProtocolVersion http_ver; - ClientSocketContext *context = parseHttpRequest(this, &parser_, &method, &http_ver); + ClientSocketContext *context = parseHttpRequest(this, *parser_); PROF_stop(parseHttpRequest); - /* partial or incomplete request */ - if (!context) { - // TODO: why parseHttpRequest can just return parseHttpRequestAbort - // (which becomes context) but checkHeaderLimits cannot? - checkHeaderLimits(); - break; - } - /* status -1 or 1 */ if (context) { debugs(33, 5, HERE << clientConnection << ": parsed a request"); AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout", CommTimeoutCbPtrFun(clientLifetimeTimeout, context->http)); commSetConnTimeout(clientConnection, Config.Timeout.lifetime, timeoutCall); - clientProcessRequest(this, &parser_, context, method, http_ver); + clientProcessRequest(this, *parser_, 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 // incomplete parse, wait for more data + break; } /* XXX where to 'finish' the parsing pass? */ return parsed_req; } void ConnStateData::clientReadRequest(const CommIoCbParams &io) { debugs(33,5,HERE << io.conn << " size " << io.size); Must(reading()); reader = NULL; /* Bail out quickly on COMM_ERR_CLOSING - close handlers will tidy up */ if (io.flag == COMM_ERR_CLOSING) { debugs(33,5, HERE << io.conn << " closing Bailout."); return; } === modified file 'src/client_side.h' --- src/client_side.h 2014-03-30 12:00:34 +0000 +++ src/client_side.h 2014-04-03 09:21:24 +0000 @@ -18,41 +18,41 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #ifndef SQUID_CLIENTSIDE_H #define SQUID_CLIENTSIDE_H #include "comm.h" #include "HttpControlMsg.h" -#include "HttpParser.h" +#include "http/forward.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; class HelperReply; namespace AnyP { class PortCfg; } // namespace Anyp /** * Badly named. @@ -183,41 +183,40 @@ * If the above can be confirmed accurate we can call this object PipelineManager or similar */ class ConnStateData : public BodyProducer, public HttpControlMsgSink { public: explicit ConnStateData(const MasterXaction::Pointer &xact); ~ConnStateData(); void readSomeData(); bool areAllContextsForThisConnection() const; void freeAllContexts(); void notifyAllContexts(const int xerrno); ///< tell everybody about the err /// Traffic parsing bool clientParseRequests(); void readNextRequest(); ClientSocketContext::Pointer getCurrentContext() const; void addContextToQueue(ClientSocketContext * context); int getConcurrentRequestCount() const; bool isOpen() const; - void checkHeaderLimits(); // HttpControlMsgSink API virtual void sendControlMsg(HttpControlMsg msg); // Client TCP connection details from comm layer. Comm::ConnectionPointer clientConnection; struct In { In(); ~In(); bool maybeMakeSpaceAvailable(); ChunkedCodingParser *bodyParser; ///< parses chunked request body SBuf buf; } in; /** number of body bytes we need to comm_read for the "current" request * * \retval 0 We do not need to read any [more] body bytes * \retval negative May need more but do not know how many; could be zero! @@ -378,43 +377,42 @@ 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); private: int connReadWasError(comm_err_t flag, int size, int xerrno); int connFinishedWithConn(int size); void clientAfterReadingRequests(); bool concurrentRequestQueueFilled() const; #if USE_AUTH /// some user details that can be used to perform authentication on this connection Auth::UserRequest::Pointer auth_; #endif - HttpParser parser_; - - // XXX: CBDATA plays with public/private and leaves the following 'private' fields all public... :( + /// 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 BodyPipe::Pointer bodyPipe; // set when we are reading request body === modified file 'src/htcp.cc' --- src/htcp.cc 2014-04-22 02:47:09 +0000 +++ src/htcp.cc 2014-05-12 07:24:03 +0000 @@ -155,55 +155,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 @@ -239,41 +245,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); @@ -313,133 +319,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 : "") << "}"); - 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"); @@ -634,40 +633,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; @@ -715,135 +716,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(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; @@ -861,95 +860,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) @@ -972,52 +978,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" ); @@ -1025,53 +1028,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; } } @@ -1130,47 +1130,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 2012-12-30 12:53:01 +0000 +++ src/htcp.h 2013-12-23 16:27:51 +0000 @@ -16,45 +16,43 @@ * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #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-04-22 02:47:09 +0000 +++ src/http/Makefile.am 2014-05-20 07:48:20 +0000 @@ -1,19 +1,30 @@ 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-01-10 13:25:55 +0000 @@ -0,0 +1,109 @@ +#ifndef SQUID_HTTP_REGISTEREDHEADERS_H +#define SQUID_HTTP_REGISTEREDHEADERS_H + +/// recognized or "known" header fields; and the RFC which defines them (or not) +typedef enum { + HDR_BAD_HDR = -1, + HDR_ACCEPT = 0, /**< RFC 2608, 2616 */ + HDR_ACCEPT_CHARSET, /**< RFC 2608, 2616 */ + HDR_ACCEPT_ENCODING, /**< RFC 2608, 2616 */ + /*HDR_ACCEPT_FEATURES,*/ /* experimental RFC 2295 */ + HDR_ACCEPT_LANGUAGE, /**< RFC 2608, 2616 */ + HDR_ACCEPT_RANGES, /**< RFC 2608, 2616 */ + HDR_AGE, /**< RFC 2608, 2616 */ + HDR_ALLOW, /**< RFC 2608, 2616 */ + /*HDR_ALTERNATES,*/ /* deprecated RFC 2068, 2295 */ + HDR_AUTHORIZATION, /**< RFC 2608, 2616, 4559 */ + HDR_CACHE_CONTROL, /**< RFC 2608, 2616 */ + HDR_CONNECTION, /**< RFC 2608, 2616 */ + HDR_CONTENT_BASE, /**< RFC 2608 */ + HDR_CONTENT_DISPOSITION, /**< RFC 2183, 2616 */ + HDR_CONTENT_ENCODING, /**< RFC 2608, 2616 */ + HDR_CONTENT_LANGUAGE, /**< RFC 2608, 2616 */ + HDR_CONTENT_LENGTH, /**< RFC 2608, 2616 */ + HDR_CONTENT_LOCATION, /**< RFC 2608, 2616 */ + HDR_CONTENT_MD5, /**< RFC 2608, 2616 */ + HDR_CONTENT_RANGE, /**< RFC 2608, 2616 */ + HDR_CONTENT_TYPE, /**< RFC 2608, 2616 */ + /*HDR_CONTENT_VERSION,*/ /* deprecated RFC 2608 header. */ + HDR_COOKIE, /**< de-facto and RFC 2965 header we may need to erase */ + HDR_COOKIE2, /**< obsolete RFC 2965 header we may need to erase */ + HDR_DATE, /**< RFC 2608, 2616 */ + /*HDR_DAV,*/ /* RFC 2518 */ + /*HDR_DEPTH,*/ /* RFC 2518 */ + /*HDR_DERIVED_FROM,*/ /* deprecated RFC 2608 */ + /*HDR_DESTINATION,*/ /* RFC 2518 */ + HDR_ETAG, /**< RFC 2608, 2616 */ + HDR_EXPECT, /**< RFC 2608, 2616 */ + HDR_EXPIRES, /**< RFC 2608, 2616 */ + HDR_FROM, /**< RFC 2608, 2616 */ + HDR_HOST, /**< RFC 2608, 2616 */ + HDR_HTTP2_SETTINGS, /**< HTTP/2.0 upgrade header. see draft-ietf-httpbis-http2-04 */ + /*HDR_IF,*/ /* RFC 2518 */ + HDR_IF_MATCH, /**< RFC 2608, 2616 */ + HDR_IF_MODIFIED_SINCE, /**< RFC 2608, 2616 */ + HDR_IF_NONE_MATCH, /**< RFC 2608, 2616 */ + HDR_IF_RANGE, /**< RFC 2608, 2616 */ + /*HDR_IF_UNMODIFIED_SINCE,*/ /* RFC 2608, 2616 */ + HDR_KEEP_ALIVE, /**< obsolete HTTP/1.0 header we may need to erase */ + HDR_KEY, /**< experimental RFC Draft draft-fielding-http-key-02 */ + HDR_LAST_MODIFIED, /**< RFC 2608, 2616 */ + HDR_LINK, /**< RFC 2068 */ + HDR_LOCATION, /**< RFC 2608, 2616 */ + /*HDR_LOCK_TOKEN,*/ /* RFC 2518 */ + HDR_MAX_FORWARDS, /**< RFC 2608, 2616 */ + HDR_MIME_VERSION, /**< RFC 2626 */ + 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, /**< deprecated RFC 2068,2616 header we may need to erase */ + HDR_PROXY_AUTHENTICATE, /**< RFC 2608, 2616, 2617 */ + HDR_PROXY_AUTHENTICATION_INFO, /**< RFC 2617 */ + HDR_PROXY_AUTHORIZATION, /**< RFC 2608, 2616, 2617 */ + HDR_PROXY_CONNECTION, /**< obsolete Netscape header we may need to erase. */ + HDR_PROXY_SUPPORT, /**< RFC 4559 */ + HDR_PUBLIC, /**< RFC 2608 */ + HDR_RANGE, /**< RFC 2608, 2616 */ + HDR_REFERER, /**< RFC 2608, 2616 */ + HDR_REQUEST_RANGE, /**< some clients use this, sigh */ + HDR_RETRY_AFTER, /**< RFC 2608, 2616 */ + HDR_SERVER, /**< RFC 2608, 2616 */ + HDR_SET_COOKIE, /**< de-facto standard header we may need to erase */ + HDR_SET_COOKIE2, /**< obsolete RFC 2965 header we may need to erase */ + /*HDR_STATUS_URI,*/ /* RFC 2518 */ + /*HDR_TCN,*/ /* experimental RFC 2295 */ + HDR_TE, /**< RFC 2616 */ + /*HDR_TIMEOUT,*/ /* RFC 2518 */ + HDR_TITLE, /**< obsolete draft suggested header */ + HDR_TRAILER, /**< RFC 2616 */ + HDR_TRANSFER_ENCODING, /**< RFC 2608, 2616 */ + 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 2608, 2616 */ + /*HDR_URI,*/ /* obsolete RFC 2068 header */ + HDR_USER_AGENT, /**< RFC 2608, 2616 */ + /*HDR_VARIANT_VARY,*/ /* experimental RFC 2295 */ + HDR_VARY, /**< RFC 2608, 2616 */ + HDR_VIA, /**< RFC 2608, 2616 */ + HDR_WARNING, /**< RFC 2608, 2616 */ + HDR_WWW_AUTHENTICATE, /**< RFC 2608, 2616, 2617, 4559 */ + HDR_AUTHENTICATION_INFO, /**< RFC 2617 */ + 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, /**< Squid custom header */ + 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_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-04-22 02:47:09 +0000 +++ src/http/RequestMethod.cc 2014-05-09 15:17:34 +0000 @@ -1,78 +1,106 @@ /* * 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-04-22 02:47:09 +0000 +++ src/http/RequestMethod.h 2014-05-15 09:38:57 +0000 @@ -1,53 +1,44 @@ #ifndef SQUID_HTTPREQUESTMETHOD_H #define SQUID_HTTPREQUESTMETHOD_H +#include "http/forward.h" #include "http/MethodType.h" #include "SBuf.h" class SquidConfig; #include /** * 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 HttpRequestMethodPointer; + +class HttpRequest; +typedef RefCount HttpRequestPointer; + +class HttpReply; +typedef RefCount 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-05-20 07:43:35 +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 2013-03-16 04:57:43 +0000 +++ src/http/one/Parser.cc 2014-05-20 10:50:56 +0000 @@ -1,294 +1,74 @@ #include "squid.h" #include "Debug.h" -#include "HttpParser.h" -#include "profiler/Profiler.h" -#include "SquidConfig.h" +#include "http/one/Parser.h" void -HttpParser::clear() +Http::One::Parser::clear() { - state = HTTP_PARSE_NONE; - request_parse_status = Http::scNone; + parsingStage_ = HTTP_PARSE_NONE; 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; + msgProtocol_ = AnyP::ProtocolVersion(); + mimeHeaderBlock_.clear(); } -void -HttpParser::reset(const char *aBuf, int len) -{ - clear(); // empty the state. - state = HTTP_PARSE_NEW; - buf = aBuf; - bufsiz = len; - debugs(74, 5, HERE << "Request buffer is " << buf); -} +// arbitrary maximum-length for headers which can be found by Http1Parser::getHeaderField() +#define GET_HDR_SZ 1024 -int -HttpParser::parseRequestFirstLine() +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; - } + LOCAL_ARRAY(char, header, GET_HDR_SZ); + const char *p = NULL; + char *q = NULL; + char got = 0; + const int namelen = name ? strlen(name) : 0; - // track next non-SP/non-HT byte after first_whitespace - if (second_word < first_whitespace && buf[i] != ' ' && buf[i] != '\t') { - second_word = i; - } + if (!headerBlockSize() || !name) + return NULL; - // 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; - } + debugs(25, 5, "looking for '" << name << "'"); - // NP: we have now seen EOL, more-data (0) cannot occur. - // From here on any failure is -1, success is 1 + for (p = mimeHeader().c_str(); *p; p += strcspn(p, "\n\r")) { + if (strcmp(p, "\r\n\r\n") == 0 || strcmp(p, "\n\n") == 0) + return NULL; - // Input Validation: + while (xisspace(*p)) + ++p; - // Process what we now know about the line structure into field offsets - // generating HTTP status for any aborts as we go. + if (strncasecmp(p, name, namelen)) + continue; - // First non-whitespace = beginning of method - if (req.start > line_end) { - request_parse_status = Http::scBadRequest; - return -1; - } - req.m_start = req.start; + if (!xisspace(p[namelen]) && p[namelen] != ':') + continue; - // 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; - } + int l = strcspn(p, "\n\r") + 1; - // 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; + if (l > GET_HDR_SZ) + l = GET_HDR_SZ; - // 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; - } + xstrncpy(header, p, l); - // 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 '" << header << "'"); - // 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 - } + q = header; - int i = req.v_start + sizeof("HTTP/") -1; + q += namelen; - /* 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'; - } - // 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; -} + if (*q == ':') { + ++q; + got = 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; -} + while (xisspace(*q)) { + ++q; + got = 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; -} + if (got) { + debugs(25, 5, "returning '" << q << "'"); + return q; + } + } -int -HttpParserRequestLen(HttpParser *hp) -{ - return hp->hdr_end - hp->req.start + 1; + return NULL; } -#endif - === renamed file 'src/HttpParser.h' => 'src/http/one/Parser.h' --- src/HttpParser.h 2014-01-11 01:35:50 +0000 +++ src/http/one/Parser.h 2014-05-20 10:50:51 +0000 @@ -1,96 +1,91 @@ -#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(); } + explicit Parser(const Parser&); // do not implement + Parser& operator =(const Parser&); // do not implement - /** 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); }; +public: + Parser() { clear(); } + virtual ~Parser() {} /// Set this parser back to a default state. /// Will DROP any reference to a buffer (does not free). - void clear(); + virtual void clear(); - /// Reset the parser for use on a new buffer. - void reset(const char *aBuf, int len); + /// 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 int64_t firstLineSize() const = 0; + + /// size in bytes of the message headers including CRLF terminator(s) + /// but excluding first-line bytes + int64_t 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 + int64_t 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. - * - * Governed by: - * RFC 1945 section 5.1 - * RFC 2616 section 5.1 - * - * 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 + * \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; + SBuf buf; - // Offsets for pieces of the MiME Header segment - int hdr_start, hdr_end; +protected: + /// what stage the parser is currently up to + ParseState parsingStage_; - // TODO: Offsets for pieces of the (HTTP reply) Status-Line as per RFC 2616 + /// what protocol label has been found in the first line (if any) + AnyP::ProtocolVersion msgProtocol_; - /** 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; + /// 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-05-20 11:05:48 +0000 @@ -0,0 +1,400 @@ +#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 2616 section 4.1 + * " + * In the interest of robustness, servers SHOULD ignore any empty + * line(s) received where a Request-Line is expected. In other words, if + * the server is reading the protocol stream at the beginning of a + * message and receives a CRLF first, it should ignore the CRLF. + * + * ... To restate what is explicitly forbidden by the + * BNF, an HTTP/1.1 client MUST NOT preface or follow a request with an + * extra CRLF. + * " + * + * 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 WHEN_RFC_COMPLIANT // CRLF or bare-LF is what RFC 2616 tolerant parsers do ... + if (Config.onoff.relaxed_header_parser) { + if (Config.onoff.relaxed_header_parser < 0 && (buf[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); + } + } +#endif + + /* 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 2616 section 5.1 + * + * 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 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) { + // DoS protection against long first-line + if ((size_t)buf.length() >= Config.maxRequestHeaderSize) { + debugs(33, 5, "Too large request-line"); + // XXX: return URL-too-log status code if second_whitespace is not yet found. + request_parse_status = Http::scHeaderTooLarge; + 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::scHeaderTooLarge; + 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_ */ + 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; + + // 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 || buf.substr(req.v_start, 5).caseCmp(SBuf("HTTP/")) != 0) { +#if USE_HTTP_VIOLATIONS + // being lax; old parser accepted strange versions + // there is a LOT of cases which are ambiguous, therefore we cannot use relaxed_header_parser here. + msgProtocol_ = Http::ProtocolVersion(0,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 + } + msgProtocol_.protocol = AnyP::PROTO_HTTP; + + int i = req.v_start + sizeof("HTTP/") -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'; + } + // catch too-big values or missing remainders + if (maj >= 65536 || i > line_end) { + request_parse_status = Http::scHttpVersionNotSupported; + return -1; + } + msgProtocol_.major = 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; + } + msgProtocol_.minor = min; + + /* RFC 2616 section 10.5.6 : handle unsupported HTTP major versions cleanly. */ + /* We currently only support 0.9, 1.0, 1.1 properly in this parser */ + if ((maj == 0 && min != 9) || (maj > 1)) { + request_parse_status = Http::scHttpVersionNotSupported; + return -1; + } + + /* + * 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; + if ((mimeHeaderBytes = headersEnd(buf.c_str(), buf.length())) == 0) { + if (buf.length()+firstLineSize() >= Config.maxRequestHeaderSize) { + debugs(33, 5, "Too large request"); + request_parse_status = Http::scHeaderTooLarge; + parsingStage_ = HTTP_PARSE_DONE; + } else + debugs(33, 5, "Incomplete request, waiting for end of headers"); + return false; + } + mimeHeaderBlock_ = buf.substr(req.end+1, mimeHeaderBytes); + buf.consume(mimeHeaderBytes); // done with these bytes now. + + } 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::scHeaderTooLarge; + 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-05-20 10:06:55 +0000 @@ -0,0 +1,67 @@ +#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 +{ + explicit RequestParser(const RequestParser&); // do not implement + RequestParser& operator =(const RequestParser&); // do not implement + +public: + /* Http::One::Parser API */ + RequestParser() : Parser() {} + virtual ~RequestParser() {} + virtual void clear(); + virtual int64_t 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 2616 + /// 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-05-20 07:24:51 +0000 @@ -0,0 +1,17 @@ +#ifndef SQUID_SRC_HTTP_ONE_FORWARD_H +#define SQUID_SRC_HTTP_ONE_FORWARD_H + +#include "base/RefCount.h" + +namespace Http { +namespace One { + +class RequestParser; +typedef RefCount 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-04-22 02:47:09 +0000 +++ src/mgr/ActionParams.cc 2014-05-09 15:17:34 +0000 @@ -1,41 +1,41 @@ /* * 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 2012-10-26 11:36:45 +0000 +++ src/mgr/ActionParams.h 2013-12-23 16:25:42 +0000 @@ -1,29 +1,29 @@ /* * 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 2012-09-01 14:38:36 +0000 +++ src/mgr/ActionWriter.h 2013-12-23 16:26:01 +0000 @@ -1,30 +1,29 @@ /* * 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 2012-09-01 14:38:36 +0000 +++ src/mgr/Filler.h 2013-12-23 16:26:11 +0000 @@ -1,30 +1,29 @@ /* * 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 2013-12-19 04:53:35 +0000 +++ src/mime.cc 2014-01-04 23:15:54 +0000 @@ -34,42 +34,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 #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 2012-08-29 12:36:10 +0000 +++ src/mime_header.cc 2013-12-31 03:43:08 +0000 @@ -14,125 +14,43 @@ * incorporates software developed and/or copyrighted by other * sources; see the CREDITS file for full details. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #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 2012-09-21 14:57:30 +0000 +++ src/mime_header.h 2013-12-31 03:43:08 +0000 @@ -16,25 +16,23 @@ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #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/tests/stub_client_side.cc' --- src/tests/stub_client_side.cc 2014-03-30 12:00:34 +0000 +++ src/tests/stub_client_side.cc 2014-04-03 09:18:41 +0000 @@ -20,41 +20,40 @@ void ClientSocketContext::buildRangeHeader(HttpReply * rep) STUB clientStreamNode * ClientSocketContext::getTail() const STUB_RETVAL(NULL) clientStreamNode * ClientSocketContext::getClientReplyContext() const STUB_RETVAL(NULL) void ClientSocketContext::connIsFinished() STUB void ClientSocketContext::removeFromConnectionList(ConnStateData * conn) STUB void ClientSocketContext::deferRecipientForLater(clientStreamNode * node, HttpReply * rep, StoreIOBuffer receivedData) STUB bool ClientSocketContext::multipartRangeRequest() const STUB_RETVAL(false) void ClientSocketContext::registerWithConn() STUB void ClientSocketContext::noteIoError(const int xerrno) STUB void ClientSocketContext::writeControlMsg(HttpControlMsg &msg) STUB void ConnStateData::readSomeData() STUB bool ConnStateData::areAllContextsForThisConnection() const STUB_RETVAL(false) void ConnStateData::freeAllContexts() STUB void ConnStateData::notifyAllContexts(const int xerrno) STUB bool ConnStateData::clientParseRequests() STUB_RETVAL(false) void ConnStateData::readNextRequest() STUB void ConnStateData::addContextToQueue(ClientSocketContext * context) STUB int ConnStateData::getConcurrentRequestCount() const STUB_RETVAL(0) bool ConnStateData::isOpen() const STUB_RETVAL(false) -void ConnStateData::checkHeaderLimits() STUB void ConnStateData::sendControlMsg(HttpControlMsg msg) STUB int64_t ConnStateData::mayNeedToReadMoreBody() const STUB_RETVAL(0) #if USE_AUTH void ConnStateData::setAuth(const Auth::UserRequest::Pointer &aur, const char *cause) STUB #endif bool ConnStateData::transparent() const STUB_RETVAL(false) bool ConnStateData::reading() const STUB_RETVAL(false) void ConnStateData::stopReading() STUB void ConnStateData::stopReceiving(const char *error) STUB void ConnStateData::stopSending(const char *error) STUB void ConnStateData::expectNoForwarding() STUB void ConnStateData::noteMoreBodySpaceAvailable(BodyPipe::Pointer) STUB void ConnStateData::noteBodyConsumerAborted(BodyPipe::Pointer) STUB bool ConnStateData::handleReadData(SBuf *buf) STUB_RETVAL(false) bool ConnStateData::handleRequestBodyData() STUB_RETVAL(false) void ConnStateData::pinConnection(const Comm::ConnectionPointer &pinServerConn, HttpRequest *request, CachePeer *peer, bool auth) STUB void ConnStateData::unpinConnection() STUB const Comm::ConnectionPointer ConnStateData::validatePinnedConnection(HttpRequest *request, const CachePeer *peer) STUB_RETVAL(NULL) void ConnStateData::clientPinnedConnectionClosed(const CommCloseCbParams &io) STUB void ConnStateData::clientReadRequest(const CommIoCbParams &io) STUB === renamed file 'src/tests/testHttpParser.cc' => 'src/tests/testHttp1Parser.cc' --- src/tests/testHttpParser.cc 2013-11-18 17:03:55 +0000 +++ src/tests/testHttp1Parser.cc 2014-05-20 09:39:30 +0000 @@ -1,1101 +1,1395 @@ #define SQUID_UNIT_TEST 1 #include "squid.h" #include -#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::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 { - // XXX: IETF HTTPbis WG has made this two-digits format invalid. + // IETF HTTPbis WG has made this two-digits format invalid. + // it gets treated same as HTTP/0.9 for now input.append("GET / HTTP/10.12\r\n", 18); - //printf("TEST: '%s'\n",input.content()); - output.reset(input.content(), input.contentSize()); - CPPUNIT_ASSERT_EQUAL(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(AnyP::PROTO_HTTP,10,12) + }; + 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! + // XXX: violations mode treats them as HTTP/0.9 requests! which is wrong. +#if !USE_HTTP_VIOLATIONS input.append("GET / FOO/1.0\n", 14); - //printf("TEST: '%s'\n",input.content()); - output.reset(input.content(), input.contentSize()); -#if USE_HTTP_VIOLATIONS - CPPUNIT_ASSERT_EQUAL(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); + 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(); #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(); } // 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(AnyP::PROTO_HTTP,0,0) + }; + 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(AnyP::PROTO_HTTP,0,0) + }; + 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(AnyP::PROTO_HTTP,0,0) + }; + 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(AnyP::PROTO_HTTP,0,0) + }; + 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(); - } - - 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(); + 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) { - input.append(" GET / HTTP/1.1\n", 16); - //printf("TEST: '%s'\n",input.content()); - output.reset(input.content(), input.contentSize()); - Config.onoff.relaxed_header_parser = 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(); + 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(); } - // STRICT 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; + 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 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::scBadRequest, + .msgStart = 0, + .msgEnd = -1, + .suffixSz = 0, + .methodStart = -1, + .methodEnd = -1, + .method = HttpRequestMethod(), + .uriStart = -1, + .uriEnd = -1, + .uri = NULL, + .versionStart = -1, + .versionEnd = -1, + .version = AnyP::ProtocolVersion() + }; + + Config.maxRequestHeaderSize = 1024; // large enough to hold the test data. + + for (SBuf::size_type pos = 0; pos <= data.length(); ++pos) { + + // simulate reading one more byte + ioBuf.append(data.substr(pos,1)); + + // 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.buf; + + // 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 2012-06-13 03:17:53 +0000 +++ src/tests/testHttp1Parser.h 2014-05-14 16:14:49 +0000 @@ -1,27 +1,30 @@ -#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 -class testHttpParser : public CPPUNIT_NS::TestFixture +class testHttp1Parser : public CPPUNIT_NS::TestFixture { - CPPUNIT_TEST_SUITE( testHttpParser ); + CPPUNIT_TEST_SUITE( testHttp1Parser ); 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. // 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-04-22 02:47:09 +0000 +++ src/tests/testHttpRequestMethod.cc 2014-05-13 10:19:34 +0000 @@ -1,61 +1,61 @@ #define SQUID_UNIT_TEST 1 #include "squid.h" #include -#include "HttpRequestMethod.h" +#include "http/RequestMethod.h" #include "Mem.h" #include "SquidConfig.h" #include "testHttpRequestMethod.h" #include 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() @@ -67,74 +67,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())); }