From: Amos Jeffries Date: Thu, 22 Jan 2015 12:54:08 +0000 (-0800) Subject: RFC 7230 compliant request-line parser based on Tokenizer API X-Git-Tag: merge-candidate-3-v1~270^2~8 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=947ca0c6d0733e2c4f14ef976f1854985c5f1f03;p=thirdparty%2Fsquid.git RFC 7230 compliant request-line parser based on Tokenizer API Refactor the request-line parser using a Tokenizer. RFC 7230 requirements provide field terminator/delimiter limitations and character sets for token validation. Also provides definitions of boundaries for relaxed/tollerant parsing without needing Squid-specific RFC violations. This implementation is slightly stricter regarding whitespace in URLs than previous implementation. It obeys a SHOULD requirement in RFC 7230 regarding responding with 400 status to those broken request messages. --- diff --git a/src/http/one/RequestParser.cc b/src/http/one/RequestParser.cc index 4d55204d7a..321c49b67c 100644 --- a/src/http/one/RequestParser.cc +++ b/src/http/one/RequestParser.cc @@ -11,17 +11,22 @@ #include "http/one/RequestParser.h" #include "http/ProtocolVersion.h" #include "mime_header.h" +#include "parser/Tokenizer.h" #include "profiler/Profiler.h" #include "SquidConfig.h" Http::One::RequestParser::RequestParser() : Parser(), - request_parse_status(Http::scNone) + request_parse_status(Http::scNone), + firstLineGarbage_(0) +{} + +Http1::Parser::size_type +Http::One::RequestParser::firstLineSize() const { - 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; + // RFC 7230 section 2.6 + /* method SP request-target SP "HTTP/" DIGIT "." DIGIT CRLF */ + return method_.image().length() + uri_.length() + 12; } /** @@ -51,240 +56,303 @@ Http::One::RequestParser::skipGarbageLines() buf_.consume(1); } } +} - /* XXX: this is a Squid-specific tolerance - * it appears never to have been relevant outside out unit-tests - * because the ConnStateData parser loop starts with consumeWhitespace() - * which absorbs any SP HTAB VTAB CR LF characters. - * But unit-tests called the HttpParser method directly without that pruning. - */ -#if USE_HTTP_VIOLATIONS - if (Config.onoff.relaxed_header_parser) { - if (Config.onoff.relaxed_header_parser < 0 && buf_[0] == ' ') - debugs(74, DBG_IMPORTANT, "WARNING: Invalid HTTP Request: " << - "Whitespace bytes received ahead of method. " << - "Ignored due to relaxed_header_parser."); - // Be tolerant of prefix spaces (other bytes are valid method values) - while (!buf_.isEmpty() && buf_[0] == ' ') { - buf_.consume(1); - } - } -#endif +/// detect and skip the CRLF or LF line terminator +/// consume from the tokenizer and return true only if found +bool +Http::One::RequestParser::skipLineTerminator(::Parser::Tokenizer &tok) const +{ + static const SBuf crlf("\r\n"); + if (tok.skip(crlf)) + return true; + + if (Config.onoff.relaxed_header_parser && tok.skipOne(CharacterSet::LF)) + return true; + + return false; } /** - * Attempt to parse the first line of a new request message. + * Attempt to parse the method field out of an HTTP message request-line. * * Governed by: * RFC 1945 section 5.1 - * RFC 7230 section 3.1 and 3.5 + * RFC 7230 section 2.6, 3.1 and 3.5 * - * Parsing state is stored between calls. However the current implementation - * begins parsing from scratch on every call. - * The return value tells you whether the parsing state fields are valid or not. + * Parsing state is stored between calls. The current implementation uses + * checkpoints after each successful request-line field. + * The return value tells you whether the parsing is completed 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 1 successful parse. method_ is filled and buffer consumed including first delimiter. * \retval 0 more data is needed to complete the parse */ int -Http::One::RequestParser::parseRequestFirstLine() +Http::One::RequestParser::parseMethodField(::Parser::Tokenizer &tok, const CharacterSet &WspDelim) { - 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 + // scan for up to 16 valid method characters. + static const size_t maxMethodLength = 16; - debugs(74, 5, "parsing possible request: buf.length=" << buf_.length()); - debugs(74, DBG_DATA, buf_); + SBuf methodFound; - // Single-pass parse: (provided we have the whole line anyways) + // method field is a sequence of TCHAR. + // NP: prefix-with-limit returns true if it finds ANY valid chars + if (!tok.prefix(methodFound, CharacterSet::TCHAR, maxMethodLength)) { + // missing/invalid 'method'. + request_parse_status = Http::scBadRequest; + debugs(33, 5, "invalid request-line. missing method"); + return -1; + } - 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; - } + // we may be at the end if we found exactly maxMethodLength bytes + if (tok.atEnd()) { + debugs(74, 5, "Parser needs more data to find method"); + return 0; + } - // track next non-SP/non-HT byte after first_whitespace - if (second_word < first_whitespace && buf_[i] != ' ' && buf_[i] != '\t') { - second_word = i; + // ... followed by at least one whitespace character. + if (!tok.skipOne(WspDelim)) { + // non-delimiter found after accepted method bytes means ... + if (methodFound.length() == maxMethodLength) { + // method longer than acceptible. + // RFC 7230 section 3.1.1 mandatory (SHOULD) 501 response + request_parse_status = Http::scNotImplemented; + debugs(33, 5, "invalid request-line. method too long"); + } else { + // invalid character in the URL + // RFC 7230 section 3.1.1 required (SHOULD) 400 response + request_parse_status = Http::scBadRequest; + debugs(33, 5, "invalid request-line. missing method delimiter"); } + return -1; + } + method_ = HttpRequestMethod(methodFound); + buf_ = tok.remaining(); // incremental parse checkpoint + return 1; +} - // 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; - } - } +int +Http::One::RequestParser::parseUriField(::Parser::Tokenizer &tok, const CharacterSet &WspDelim) +{ + // URI field is a sequence of ... what? segments all have different valid charset + // go with non-whitespace non-binary characters for now + static CharacterSet UriChars("URI-Chars",""); + if (!UriChars['a']) { // if it needs initializing... + /* RFC 3986 section 2: + * " + * A URI is composed from a limited set of characters consisting of + * digits, letters, and a few graphic symbols. + * " + */ + // RFC 3986 section 2.1 - percent encoding "%" HEXDIG + UriChars.add('%'); + UriChars += CharacterSet::HEXDIG; + // RFC 3986 section 2.2 - reserved characters + UriChars += CharacterSet("gen-delims", ":/?#[]@"); + UriChars += CharacterSet("sub-delims", "!$&'()*+,;="); + // RFC 3986 section 2.3 - unreserved characters + UriChars += CharacterSet::ALPHA; + UriChars += CharacterSet::DIGIT; + UriChars += CharacterSet("unreserved", "-._~"); + } - // RFC 7230 section 3.1.1 does not prohibit embeded CR like RFC 2616 used to. - // However it does explicitly state an exact syntax which omits un-encoded CR - // and defines 400 (Bad Request) as the required action when - // handed an invalid request-line. - request_parse_status = Http::scBadRequest; - return -1; - } + /* Arbitrary 64KB URI upper length limit. + * + * Not quite as arbitrary as it seems though. Old SquidString objects + * cannot store strings larger than 64KB, so we must limit until they + * have all been replaced with SBuf. + * + * Not that it matters but RFC 7230 section 3.1.1 requires (RECOMMENDED) + * at least 8000 octets for the whole line, including method and version. + */ + const size_t maxUriLength = min(static_cast(Config.maxRequestHeaderSize) - firstLineSize(), + static_cast((64*1024)-1)); + + SBuf uriFound; + // NP: prefix-with-limit returns true if it finds ANY valid chars + if (!tok.prefix(uriFound, UriChars, maxUriLength)) { + // else did not find any valid TCHAR + debugs(33, 5, "invalid request-line. missing URL"); + request_parse_status = Http::scBadRequest; + return -1; + } - // We are expecting printable ascii characters for method/first word - if (first_whitespace < 0 && (!xisascii(buf_[i]) || !xisprint(buf_[i]))) { - request_parse_status = Http::scBadRequest; - return -1; - } + // we may be at the end if we found exactly maxUriLength bytes + if (tok.atEnd()) { + debugs(74, 5, "Parser needs more data to find URI"); + return 0; } - if (req.end == -1) { - // DoS protection against long first-line - if ((size_t)buf_.length() >= Config.maxRequestHeaderSize) { - debugs(33, 5, "Too large request-line"); - // RFC 7230 section 3.1.1 mandatory 414 response if URL longer than acceptible. + // RFC 1945 - for GET the line terminator may follow URL instead of a delimiter + if (method_ == Http::METHOD_GET && skipLineTerminator(tok)) { + debugs(33, 5, "HTTP/0.9 syntax request-line detected"); + msgProtocol_ = Http::ProtocolVersion(0,9); + uri_ = uriFound; + request_parse_status = Http::scOkay; + buf_ = tok.remaining(); // incremental parse checkpoint + return 1; + } + + // ... followed by at least one whitespace character. + if (!tok.skipOne(WspDelim)) { + // non-delimiter found after accepted URL bytes means ... + if (uriFound.length() == maxUriLength) { + // URL longer than acceptible. + // RFC 7230 section 3.1.1 mandatory (MUST) 414 response request_parse_status = Http::scUriTooLong; + debugs(33, 5, "invalid request-line. URI longer than " << maxUriLength << " bytes"); + return -1; + } else { + // invalid non-delimiter character ended the URL + // RFC 7230 section 3.1.1 required (SHOULD) 400 response + request_parse_status = Http::scBadRequest; + debugs(33, 5, "invalid request-line. missing URI delimiter"); return -1; } + } + uri_ = uriFound; + buf_ = tok.remaining(); // incremental parse checkpoint + return 1; +} - debugs(74, 5, "Parser: retval 0: from " << req.start << - "->" << req.end << ": needs more data to complete first line."); +int +Http::One::RequestParser::parseHttpVersionField(::Parser::Tokenizer &tok) +{ + // partial match of HTTP/1 magic prefix + if (tok.remaining().length() < Http1magic.length() && Http1magic.startsWith(tok.remaining())) { + debugs(74, 5, "Parser needs more data to find version"); return 0; } - // NP: we have now seen EOL, more-data (0) cannot occur. - // From here on any failure is -1, success is 1 - - // Input Validation: - - // DoS protection against long first-line - if ((size_t)(req.end-req.start) >= Config.maxRequestHeaderSize) { - debugs(33, 5, "Too large request-line"); - request_parse_status = Http::scUriTooLong; + if (!tok.skip(Http1magic)) { + debugs(74, 5, "invalid request-line. not HTTP/1 protocol"); + request_parse_status = Http::scHttpVersionNotSupported; return -1; } - // Process what we now know about the line structure into field offsets - // generating HTTP status for any aborts as we go. + if (tok.atEnd()) { + debugs(74, 5, "Parser needs more data to find version"); + return 0; + } - // First non-whitespace = beginning of method - if (req.start > line_end) { - request_parse_status = Http::scBadRequest; + // get the version minor DIGIT + SBuf digit; + if (!tok.prefix(digit, CharacterSet::DIGIT, 1)) { + // non-DIGIT. invalid version number. + request_parse_status = Http::scHttpVersionNotSupported; + debugs(33, 5, "invalid request-line. non-numeric or too-large HTTP minor version"); 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; + if (tok.atEnd()) { + debugs(74, 5, "Parser needs more data to find version"); + return 0; } - req.m_end = first_whitespace - 1; - if (req.m_end < req.m_start) { - request_parse_status = Http::scBadRequest; // missing URI? + + // version is always followed by the terminator + if (!skipLineTerminator(tok)) { + if (tok.skipOne(CharacterSet::CR) && tok.atEnd()) { + debugs(74, 5, "Parser needs more data to find version"); + return 0; + } + request_parse_status = Http::scHttpVersionNotSupported; + debugs(33, 5, "invalid request-line. garabge before line terminator"); return -1; } - /* Set method_ */ - const SBuf tmp = buf_.substr(req.m_start, req.m_end - req.m_start + 1); - method_ = HttpRequestMethod(tmp); + // found version fully AND terminator + msgProtocol_ = Http::ProtocolVersion(1, (*digit.rawContent() - '0')); + request_parse_status = Http::scOkay; + buf_ = tok.remaining(); // incremental parse checkpoint + return 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; +/** + * Attempt to parse the first line of a new request message. + * + * Governed by: + * RFC 1945 section 5.1 + * RFC 7230 section 2.6, 3.1 and 3.5 + * + * Parsing state is stored between calls. The current implementation uses + * checkpoints after each successful request-line field. + * The return value tells you whether the parsing is completed 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() +{ + ::Parser::Tokenizer tok(buf_); - // 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); + debugs(74, 5, "parsing possible request: buf.length=" << buf_.length()); + debugs(74, DBG_DATA, buf_); - // Last whitespace SP = before start of protocol/version - if (last_whitespace >= line_end) { - request_parse_status = Http::scBadRequest; // missing version - return -1; + CharacterSet WspDelim = CharacterSet::SP; // strict parse only accepts SP + + if (Config.onoff.relaxed_header_parser) { + // RFC 7230 section 3.5 + // tolerant parser MAY accept any of SP, HTAB, VT (%x0B), FF (%x0C), or bare CR + // as whitespace between request-line fields + WspDelim += CharacterSet::HTAB + + CharacterSet("VT,FF","\x0B\x0C") + + CharacterSet::CR; } - req.v_start = last_whitespace + 1; - req.v_end = line_end; - /* RFC 7230 section 2.6 : handle unsupported HTTP major versions cleanly. */ - if ((req.v_end - req.v_start +1) < (int)Http1magic.length() || !buf_.substr(req.v_start, SBuf::npos).startsWith(Http1magic)) { - // non-HTTP/1 protocols not supported / implemented. - request_parse_status = Http::scHttpVersionNotSupported; - return -1; + // only search for method if we have not yet found one + if (method_ == Http::METHOD_NONE) { + const int res = parseMethodField(tok, WspDelim); + if (res < 1) + return res; + // else keep going... } - // NP: magic octets include the protocol name and major version DIGIT. - msgProtocol_.protocol = AnyP::PROTO_HTTP; - msgProtocol_.major = 1; - int i = req.v_start + Http1magic.length() -1; + // tolerant parser allows multiple whitespace characters between fields + if (Config.onoff.relaxed_header_parser) { + const size_t garbage = tok.skipAll(WspDelim); + if (garbage > 0) { + firstLineGarbage_ += garbage; + buf_ = tok.remaining(); // re-checkpoint after garbage + } + } + if (tok.atEnd()) { + debugs(74, 5, "Parser needs more data"); + return 0; + } - // catch missing minor part - if (++i > line_end) { - request_parse_status = Http::scHttpVersionNotSupported; - return -1; + // only search for request-target (URL) if we have not yet found one + if (uri_.isEmpty()) { + const int res = parseUriField(tok, WspDelim); + if (res < 1 || msgProtocol_.protocol == AnyP::PROTO_HTTP) + return res; + // else keep going... } - /* next should be one or more digits */ - if (!isdigit(buf_[i])) { - request_parse_status = Http::scHttpVersionNotSupported; - return -1; + + // tolerant parser allows multiple whitespace characters between fields + if (Config.onoff.relaxed_header_parser) { + const size_t garbage = tok.skipAll(WspDelim); + if (garbage > 0) { + firstLineGarbage_ += garbage; + buf_ = tok.remaining(); // re-checkpoint after garbage + } } - int min = 0; - for (; i <= line_end && (isdigit(buf_[i])) && min < 65536; ++i) { - min = min * 10; - min = min + (buf_[i]) - '0'; + if (tok.atEnd()) { + debugs(74, 5, "Parser needs more data"); + return 0; } - // catch too-big values or trailing garbage - if (min >= 65536 || i < line_end) { - request_parse_status = Http::scHttpVersionNotSupported; - return -1; + + // HTTP/1 version suffix (protocol magic) followed by CR*LF + if (msgProtocol_.protocol == AnyP::PROTO_NONE) { + return parseHttpVersionField(tok); } - msgProtocol_.minor = min; - /* - * Rightio - we have all the schtuff. Return true; we've got enough. - */ - request_parse_status = Http::scOkay; - return 1; + // If we got here this method has been called too many times + request_parse_status = Http::scInternalServerError; + debugs(33, 5, "ERROR: Parser already processed request-line"); + return -1; } bool @@ -311,15 +379,13 @@ Http::One::RequestParser::parse(const SBuf &aBuf) // 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, "request-line: retval " << retcode << ": line={" << aBuf.length() << ", data='" << aBuf << "'}"); + debugs(74, 5, "request-line: method: " << method_); + debugs(74, 5, "request-line: url: " << uri_); + debugs(74, 5, "request-line: proto: " << msgProtocol_); debugs(74, 5, "Parser: bytes processed=" << (aBuf.length()-buf_.length())); PROF_stop(HttpParserParseReqLine); diff --git a/src/http/one/RequestParser.h b/src/http/one/RequestParser.h index 87b27d7da5..236cd1c51c 100644 --- a/src/http/one/RequestParser.h +++ b/src/http/one/RequestParser.h @@ -13,6 +13,10 @@ #include "http/RequestMethod.h" #include "http/StatusCode.h" +namespace Parser { +class Tokenizer; +} + namespace Http { namespace One { @@ -32,7 +36,7 @@ public: /* Http::One::Parser API */ virtual void clear() {*this = RequestParser();} - virtual Http1::Parser::size_type firstLineSize() const {return req.end - req.start + 1;} + virtual Http1::Parser::size_type firstLineSize() const; virtual bool parse(const SBuf &aBuf); /// the HTTP method if this is a request message @@ -50,21 +54,20 @@ public: private: void skipGarbageLines(); int parseRequestFirstLine(); - - /// Offsets for pieces of the (HTTP request) Request-Line as per RFC 7230 section 3.1.1. - /// only valid before and during parse stage HTTP_PARSE_FIRST - struct request_offsets { - int start, end; - int m_start, m_end; // method - int u_start, u_end; // url - int v_start, v_end; // version (full text) - } req; + int parseMethodField(::Parser::Tokenizer &, const CharacterSet &); + int parseUriField(::Parser::Tokenizer &, const CharacterSet &); + int parseHttpVersionField(::Parser::Tokenizer &); + bool skipLineTerminator(::Parser::Tokenizer &) const; /// what request method has been found on the first line HttpRequestMethod method_; /// raw copy of the original client reqeust-line URI field SBuf uri_; + + /// amount of garbage bytes tolerantly skipped inside the request-line + /// may be -1 if sender only omitted CR on terminator + int64_t firstLineGarbage_; }; } // namespace One diff --git a/src/tests/testHttp1Parser.cc b/src/tests/testHttp1Parser.cc index e94429f8ac..0fda570cef 100644 --- a/src/tests/testHttp1Parser.cc +++ b/src/tests/testHttp1Parser.cc @@ -13,6 +13,7 @@ #define private public #define protected public +#include "Debug.h" #include "http/one/RequestParser.h" #include "http/RequestMethod.h" #include "MemBuf.h" @@ -46,17 +47,9 @@ struct resultSet { 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; }; @@ -67,24 +60,21 @@ testResults(int line, const SBuf &input, Http1::RequestParser &output, struct re printf("TEST @%d, in=%u: " SQUIDSBUFPH "\n", line, input.length(), SQUIDSBUFPRINT(input)); #endif + // runs the parse 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); + + // check easily visible field outputs 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_); + CPPUNIT_ASSERT_EQUAL(expect.status, output.request_parse_status); + + // check more obscure states + CPPUNIT_ASSERT_EQUAL(expect.needsMore, output.needsMoreData()); + if (output.needsMoreData()) + CPPUNIT_ASSERT_EQUAL(expect.parserState, output.parsingStage_); + CPPUNIT_ASSERT_EQUAL(expect.suffixSz, output.buf_.length()); } #endif /* __cplusplus >= 200103L */ @@ -96,18 +86,10 @@ testHttp1Parser::testParserConstruct() Http1::RequestParser output; CPPUNIT_ASSERT_EQUAL(true, output.needsMoreData()); CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_NONE, output.parsingStage_); - CPPUNIT_ASSERT_EQUAL(Http::scNone, output.request_parse_status); // XXX: clear() not being called. - CPPUNIT_ASSERT_EQUAL(-1, output.req.start); - CPPUNIT_ASSERT_EQUAL(-1, output.req.end); + CPPUNIT_ASSERT_EQUAL(Http::scNone, output.request_parse_status); CPPUNIT_ASSERT(output.buf_.isEmpty()); - CPPUNIT_ASSERT_EQUAL(-1, output.req.m_start); - CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end); CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_NONE), output.method_); - CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start); - CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end); CPPUNIT_ASSERT(output.uri_.isEmpty()); - CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start); - CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end); CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_); } @@ -117,17 +99,9 @@ testHttp1Parser::testParserConstruct() CPPUNIT_ASSERT_EQUAL(true, output->needsMoreData()); CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_NONE, output->parsingStage_); CPPUNIT_ASSERT_EQUAL(Http::scNone, output->request_parse_status); - CPPUNIT_ASSERT_EQUAL(-1, output->req.start); - CPPUNIT_ASSERT_EQUAL(-1, output->req.end); CPPUNIT_ASSERT(output->buf_.isEmpty()); - CPPUNIT_ASSERT_EQUAL(-1, output->req.m_start); - CPPUNIT_ASSERT_EQUAL(-1, output->req.m_end); CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_NONE), output->method_); - CPPUNIT_ASSERT_EQUAL(-1, output->req.u_start); - CPPUNIT_ASSERT_EQUAL(-1, output->req.u_end); CPPUNIT_ASSERT(output->uri_.isEmpty()); - CPPUNIT_ASSERT_EQUAL(-1, output->req.v_start); - CPPUNIT_ASSERT_EQUAL(-1, output->req.v_end); CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output->msgProtocol_); delete output; } @@ -144,7 +118,7 @@ testHttp1Parser::testParseRequestLineProtocols() Http1::RequestParser output; // TEST: Do we comply with RFC 1945 section 5.1 ? - // TEST: Do we comply with RFC 2616 section 5.1 ? + // TEST: Do we comply with RFC 7230 sections 2.6, 3.1.1 and 3.5 ? // RFC 1945 : HTTP/0.9 simple-request { @@ -154,17 +128,9 @@ testHttp1Parser::testParseRequestLineProtocols() .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(); @@ -173,33 +139,24 @@ testHttp1Parser::testParseRequestLineProtocols() } // RFC 1945 : invalid HTTP/0.9 simple-request (only GET is valid) -#if WHEN_RFC_COMPLIANT { - input.append("POST /\r\n", 7); + input.append("POST /\r\n", 8); struct resultSet expect = { - .parsed = true, + .parsed = false, .needsMore = false, .parserState = Http1::HTTP_PARSE_DONE, - .status = Http::scOkay, - .msgStart = 0, - .msgEnd = (int)input.length()-1, - .suffixSz = 0, - .methodStart = 0, - .methodEnd = 3, + .status = Http::scBadRequest, + .suffixSz = 3, .method = HttpRequestMethod(Http::METHOD_POST), - .uriStart = 5, - .uriEnd = 5, - .uri = "/", - .versionStart = -1, - .versionEnd = -1, + .uri = NULL, .version = AnyP::ProtocolVersion() }; output.clear(); testResults(__LINE__, input, output, expect); input.clear(); } -#endif - // RFC 1945 and 2616 : HTTP/1.0 request + + // RFC 1945 and 7230 : HTTP/1.0 request { input.append("GET / HTTP/1.0\r\n", 16); struct resultSet expect = { @@ -207,17 +164,9 @@ testHttp1Parser::testParseRequestLineProtocols() .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(); @@ -225,7 +174,7 @@ testHttp1Parser::testParseRequestLineProtocols() input.clear(); } - // RFC 2616 : HTTP/1.1 request + // RFC 7230 : HTTP/1.1 request { input.append("GET / HTTP/1.1\r\n", 16); struct resultSet expect = { @@ -233,17 +182,9 @@ testHttp1Parser::testParseRequestLineProtocols() .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(); @@ -251,7 +192,7 @@ testHttp1Parser::testParseRequestLineProtocols() input.clear(); } - // RFC 2616 : future version full-request + // RFC 7230 : future version full-request { input.append("GET / HTTP/1.2\r\n", 16); struct resultSet expect = { @@ -259,17 +200,9 @@ testHttp1Parser::testParseRequestLineProtocols() .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(); @@ -279,49 +212,48 @@ testHttp1Parser::testParseRequestLineProtocols() // RFC 7230 : future versions do not use request-line syntax { + input.append("GET / HTTP/2.0\r\n", 16); + struct resultSet expectA = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_MIME, + .status = Http::scHttpVersionNotSupported, + .suffixSz = input.length()-6, + .method = HttpRequestMethod(Http::METHOD_GET), + .uri = "/", + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expectA); + input.clear(); + input.append("GET / HTTP/10.12\r\n", 18); - struct resultSet expect = { + struct resultSet expectB = { .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, + .suffixSz = input.length()-6, .method = HttpRequestMethod(Http::METHOD_GET), - .uriStart = 4, - .uriEnd = 4, .uri = "/", - .versionStart = 6, - .versionEnd = 15, .version = AnyP::ProtocolVersion() }; output.clear(); - testResults(__LINE__, input, output, expect); + testResults(__LINE__, input, output, expectB); input.clear(); } // unknown non-HTTP protocol names { - input.append("GET / FOO/1.0\n", 14); + input.append("GET / FOO/1.0\r\n", 15); 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, + .suffixSz = input.length()-6, .method = HttpRequestMethod(Http::METHOD_GET), - .uriStart = 4, - .uriEnd = 4, .uri = "/", - .versionStart = 6, - .versionEnd = 12, .version = AnyP::ProtocolVersion() }; output.clear(); @@ -329,25 +261,17 @@ testHttp1Parser::testParseRequestLineProtocols() input.clear(); } - // no version + // no version digits { - input.append("GET / HTTP/\n", 12); + input.append("GET / HTTP/\r\n", 13); 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, + .suffixSz = input.length()-6, .method = HttpRequestMethod(Http::METHOD_GET), - .uriStart = 4, - .uriEnd = 4, .uri = "/", - .versionStart = 6, - .versionEnd = 10, .version = AnyP::ProtocolVersion() }; output.clear(); @@ -357,23 +281,15 @@ testHttp1Parser::testParseRequestLineProtocols() // no major version { - input.append("GET / HTTP/.1\n", 14); + input.append("GET / HTTP/.1\r\n", 15); 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, + .suffixSz = input.length()-6, .method = HttpRequestMethod(Http::METHOD_GET), - .uriStart = 4, - .uriEnd = 4, .uri = "/", - .versionStart = 6, - .versionEnd = 12, .version = AnyP::ProtocolVersion() }; output.clear(); @@ -383,23 +299,15 @@ testHttp1Parser::testParseRequestLineProtocols() // no version dot { - input.append("GET / HTTP/11\n", 14); + input.append("GET / HTTP/11\r\n", 15); 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, + .suffixSz = input.length()-6, .method = HttpRequestMethod(Http::METHOD_GET), - .uriStart = 4, - .uriEnd = 4, .uri = "/", - .versionStart = 6, - .versionEnd = 12, .version = AnyP::ProtocolVersion() }; output.clear(); @@ -409,23 +317,15 @@ testHttp1Parser::testParseRequestLineProtocols() // negative major version (bug 3062) { - input.append("GET / HTTP/-999999.1\n", 21); + input.append("GET / HTTP/-999999.1\r\n", 22); 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, + .suffixSz = input.length()-6, .method = HttpRequestMethod(Http::METHOD_GET), - .uriStart = 4, - .uriEnd = 4, .uri = "/", - .versionStart = 6, - .versionEnd = 19, .version = AnyP::ProtocolVersion() }; output.clear(); @@ -435,24 +335,16 @@ testHttp1Parser::testParseRequestLineProtocols() // no minor version { - input.append("GET / HTTP/1.\n", 14); + input.append("GET / HTTP/1.\r\n", 15); 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, + .suffixSz = input.length()-6, .method = HttpRequestMethod(Http::METHOD_GET), - .uriStart = 4, - .uriEnd = 4, .uri = "/", - .versionStart = 6, - .versionEnd = 12, - .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,0) + .version = AnyP::ProtocolVersion() }; output.clear(); testResults(__LINE__, input, output, expect); @@ -461,24 +353,16 @@ testHttp1Parser::testParseRequestLineProtocols() // negative major version (bug 3062 corollary) { - input.append("GET / HTTP/1.-999999\n", 21); + input.append("GET / HTTP/1.-999999\r\n", 22); 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, + .suffixSz = input.length()-6, .method = HttpRequestMethod(Http::METHOD_GET), - .uriStart = 4, - .uriEnd = 4, .uri = "/", - .versionStart = 6, - .versionEnd = 19, - .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,0) + .version = AnyP::ProtocolVersion() }; output.clear(); testResults(__LINE__, input, output, expect); @@ -498,80 +382,89 @@ testHttp1Parser::testParseRequestLineStrange() // space padded URL { input.append("GET / HTTP/1.1\r\n", 21); + // when being tolerant extra (sequential) SP delimiters are acceptable + Config.onoff.relaxed_header_parser = 1; 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); + + Config.onoff.relaxed_header_parser = 0; + struct resultSet expectStrict = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scBadRequest, + .suffixSz = input.length()-4, + .method = HttpRequestMethod(Http::METHOD_GET), + .uri = NULL, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expectStrict); input.clear(); } +#if 0 // XXX: RFC compliant parser does not have tolerance for this (yet) // whitespace inside URI. (nasty but happens) - // XXX: depends on tolerant parser... { - input.append("GET /fo o/ HTTP/1.1\n", 20); + input.append("GET /fo o/ HTTP/1.1\r\n", 21); + Config.onoff.relaxed_header_parser = 1; 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); + + Config.onoff.relaxed_header_parser = 0; + struct resultSet expectStrict = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scBadRequest, + .suffixSz = 0, + .method = HttpRequestMethod(Http::METHOD_GET), + .uri = NULL, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expectStrict); input.clear(); } +#endif // additional data in buffer { - input.append("GET / HTTP/1.1\nboo!", 23); + input.append("GET / HTTP/1.1\r\nboo!", 20); 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(); + Config.onoff.relaxed_header_parser = 0; } } @@ -585,101 +478,67 @@ testHttp1Parser::testParseRequestLineTerminators() Http1::RequestParser output; // alternative EOL sequence: NL-only + // RFC 7230 tolerance permits omitted CR { input.append("GET / HTTP/1.1\n", 15); + Config.onoff.relaxed_header_parser = 1; 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); - struct resultSet expect = { - .parsed = true, + Config.onoff.relaxed_header_parser = 0; + struct resultSet expectStrict = { + .parsed = false, .needsMore = false, .parserState = Http1::HTTP_PARSE_DONE, - .status = Http::scOkay, - .msgStart = 0, - .msgEnd = (int)input.length()-2, - .suffixSz = 0, - .methodStart = 0, - .methodEnd = 2, + .status = Http::scHttpVersionNotSupported, + .suffixSz = 9, .method = HttpRequestMethod(Http::METHOD_GET), - .uriStart = 4, - .uriEnd = 4, .uri = "/", - .versionStart = 6, - .versionEnd = 13, - .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1) + .version = AnyP::ProtocolVersion() }; output.clear(); - testResults(__LINE__, input, output, expect); + testResults(__LINE__, input, output, expectStrict); input.clear(); } - // alternative EOL sequence: multi-CR-NL + // alternative EOL sequence: double-NL-only + // RFC 7230 tolerance permits omitted CR + // NP: represents a request with no mime headers { - input.append("GET / HTTP/1.1\r\r\r\n", 18); - // Being tolerant we can ignore and elide these apparently benign CR + input.append("GET / HTTP/1.1\n\n", 16); Config.onoff.relaxed_header_parser = 1; - struct resultSet expectRelaxed = { - .parsed = false, - .needsMore = true, - .parserState = Http1::HTTP_PARSE_MIME, + 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 = 6, - .versionEnd = 13, .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1) }; output.clear(); - testResults(__LINE__, input, output, expectRelaxed); + testResults(__LINE__, input, output, expect); - // strict mode treats these as several bare-CR in the request line which is explicitly invalid. Config.onoff.relaxed_header_parser = 0; 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, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scHttpVersionNotSupported, + .suffixSz = 10, + .method = HttpRequestMethod(Http::METHOD_GET), + .uri = "/", .version = AnyP::ProtocolVersion() }; output.clear(); @@ -689,25 +548,16 @@ testHttp1Parser::testParseRequestLineTerminators() // 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); + // RFC 7230 specifies version is followed by CRLF. No intermediary bytes. + input.append("GET / HTTP/1.1 \r\n", 17); 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, + .status = Http::scHttpVersionNotSupported, + .suffixSz = input.length()-6, .method = HttpRequestMethod(Http::METHOD_GET), - .uriStart = 4, - .uriEnd = 13, - .uri = "/ HTTP/1.1", - .versionStart = -1, - .versionEnd = -1, + .uri = "/", .version = AnyP::ProtocolVersion() }; output.clear(); @@ -725,25 +575,35 @@ testHttp1Parser::testParseRequestLineMethods() SBuf input; Http1::RequestParser output; - // RFC 2616 : . method + // RFC 7230 : dot method { - input.append(". / HTTP/1.1\n", 13); + input.append(". / HTTP/1.1\r\n", 14); 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(SBuf(".")), - .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(); + } + + // RFC 7230 : special TCHAR method chars + { + input.append("!#$%&'*+-.^_`|~ / HTTP/1.1\r\n", 28); + struct resultSet expect = { + .parsed = false, + .needsMore = true, + .parserState = Http1::HTTP_PARSE_MIME, + .status = Http::scOkay, + .suffixSz = 0, + .method = HttpRequestMethod(SBuf("!#$%&'*+-.^_`|~")), + .uri = "/", .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1) }; output.clear(); @@ -753,23 +613,15 @@ testHttp1Parser::testParseRequestLineMethods() // OPTIONS with * URL { - input.append("OPTIONS * HTTP/1.1\n", 19); + input.append("OPTIONS * HTTP/1.1\r\n", 20); 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(); @@ -779,23 +631,15 @@ testHttp1Parser::testParseRequestLineMethods() // unknown method { - input.append("HELLOWORLD / HTTP/1.1\n", 22); + input.append("HELLOWORLD / HTTP/1.1\r\n", 23); 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(SBuf("HELLOWORLD")), - .uriStart = 11, - .uriEnd = 11, .uri = "/", - .versionStart = 13, - .versionEnd = 20, .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1) }; output.clear(); @@ -803,6 +647,24 @@ testHttp1Parser::testParseRequestLineMethods() input.clear(); } + // too-long method (over 16 bytes) + { + input.append("HELLOSTRANGEWORLD / HTTP/1.1\r\n", 31); + struct resultSet expect = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scNotImplemented, + .suffixSz = input.length(), + .method = HttpRequestMethod(), + .uri = NULL, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expect); + input.clear(); + } + // method-only { input.append("A\n", 2); @@ -811,17 +673,9 @@ testHttp1Parser::testParseRequestLineMethods() .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(); @@ -836,17 +690,9 @@ testHttp1Parser::testParseRequestLineMethods() .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(); @@ -854,52 +700,50 @@ testHttp1Parser::testParseRequestLineMethods() input.clear(); } - // space padded method (in strict mode SP is reserved so invalid as a method byte) + // space padded method (SP is reserved so invalid as a method byte) + { + input.append(" GET / HTTP/1.1\r\n", 17); + struct resultSet expect = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scBadRequest, + .suffixSz = input.length(), + .method = HttpRequestMethod(), + .uri = NULL, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expect); + input.clear(); + } + + // RFC 7230 defined tolerance: ignore empty line(s) prefix on messages { - input.append(" GET / HTTP/1.1\n", 16); - // RELAXED mode Squid custom tolerance ignores SP -#if USE_HTTP_VIOLATIONS + input.append("\r\n\r\n\nGET / HTTP/1.1\r\n", 21); Config.onoff.relaxed_header_parser = 1; - struct resultSet expectRelaxed = { + 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()-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 + testResults(__LINE__, input, output, expect); - // STRICT mode obeys RFC syntax Config.onoff.relaxed_header_parser = 0; 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(); @@ -907,57 +751,89 @@ testHttp1Parser::testParseRequestLineMethods() input.clear(); } - // RFC 2616 defined tolerance: ignore empty line(s) prefix on messages -#if WHEN_RFC_COMPLIANT + // forbidden character in method { - input.append("\r\n\r\n\nGET / HTTP/1.1\r\n", 21); + input.append("\tGET / HTTP/1.1\r\n", 17); struct resultSet expect = { .parsed = false, .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scBadRequest, + .suffixSz = input.length(), + .method = HttpRequestMethod(), + .uri = NULL, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expect); + input.clear(); + } + + // CR in method delimiters + { + // RFC 7230 section 3.5 permits CR in whitespace but only for tolerant parsers + input.append("GET\r / HTTP/1.1\r\n", 17); + Config.onoff.relaxed_header_parser = 1; + struct resultSet expect = { + .parsed = false, + .needsMore = true, .parserState = Http1::HTTP_PARSE_MIME, .status = Http::scOkay, - .msgStart = 5, - .msgEnd = (int)input.length()-1, - .suffixSz = 5, - .methodStart = 0, - .methodEnd = 2, + .suffixSz = 0, .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); + + Config.onoff.relaxed_header_parser = 0; + struct resultSet expectStrict = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scBadRequest, + .suffixSz = input.length(), + .method = HttpRequestMethod(), + .uri = NULL, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expectStrict); input.clear(); } -#endif - // tab padded method (NP: tab is not SP so treated as any other binary) + // tolerant parser delimiters { - input.append("\tGET / HTTP/1.1\n", 16); + // RFC 7230 section 3.5 permits certain binary characters as whitespace delimiters + input.append("GET\r\t\x0B\x0C / HTTP/1.1\r\n", 20); + Config.onoff.relaxed_header_parser = 1; struct resultSet expect = { + .parsed = false, + .needsMore = true, + .parserState = Http1::HTTP_PARSE_MIME, + .status = Http::scOkay, + .suffixSz = 0, + .method = HttpRequestMethod(Http::METHOD_GET), + .uri = "/", + .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1) + }; + output.clear(); + testResults(__LINE__, input, output, expect); + + Config.onoff.relaxed_header_parser = 0; + struct resultSet expectStrict = { .parsed = false, .needsMore = false, .parserState = Http1::HTTP_PARSE_DONE, .status = Http::scBadRequest, - .msgStart = 0, - .msgEnd = -1, //Halt on \t, no valid method char .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); + testResults(__LINE__, input, output, expectStrict); input.clear(); } } @@ -971,154 +847,54 @@ testHttp1Parser::testParseRequestLineInvalid() SBuf input; Http1::RequestParser output; - // no method (but in a form which is ambiguous with HTTP/0.9 simple-request) + // no method (or method delimiter) { - // XXX: HTTP/0.9 requires method to be "GET" + // HTTP/0.9 requires method to be "GET" input.append("/ HTTP/1.0\n", 11); 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(SBuf("/")), - .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(); - } - - // no method (an invalid format) - { - input.append(" / HTTP/1.0\n", 12); -#if USE_HTTP_VIOLATIONS - // squid custom tolerance consumes initial SP. - Config.onoff.relaxed_header_parser = 1; - 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(SBuf("/")), - .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); -#endif - -#if !USE_HTTP_VIOLATIONS - // a compliant or strict parse, detects as invalid - Config.onoff.relaxed_header_parser = 0; - 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() }; -#else - // XXX: for now Squid confuses this with HTTP/0.9 - struct resultSet expectStrict = { - .parsed = true, - .needsMore = false, - .parserState = Http1::HTTP_PARSE_DONE, - .status = Http::scOkay, - .msgStart = 0, - .msgEnd = (int)input.length()-2, - .suffixSz = 0, - .methodStart = 0, - .methodEnd = 0, - .method = HttpRequestMethod(SBuf("/")), - .uriStart = 2, - .uriEnd = 9, - .uri = "HTTP/1.0", - .versionStart = -1, - .versionEnd = -1, - .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9) - }; -#endif output.clear(); - testResults(__LINE__, input, output, expectStrict); + testResults(__LINE__, input, output, expect); input.clear(); } - // binary code in method (invalid) + // no method (with method delimiter) { - input.append("GET\x0B / HTTP/1.1\n", 16); - struct resultSet expect = { + input.append(" / HTTP/1.0\n", 12); + struct resultSet expectStrict = { .parsed = false, .needsMore = false, .parserState = Http1::HTTP_PARSE_DONE, .status = Http::scBadRequest, - .msgStart = 0, - .msgEnd = -1, //Halt on \x0B, no valid method char .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); + testResults(__LINE__, input, output, expectStrict); input.clear(); } - // CR in method + // binary code in method (invalid) { - // RFC 2616 sec 5.1 prohibits CR other than in terminator. - input.append("GET\r / HTTP/1.1\r\n", 16); + input.append("GET\x0A / HTTP/1.1\r\n", 17); 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(); @@ -1126,25 +902,17 @@ testHttp1Parser::testParseRequestLineInvalid() input.clear(); } - // binary code NUL! in method (strange but ...) + // binary code NUL! in method (always invalid) { - input.append("GET\0 / HTTP/1.1\n", 16); + input.append("GET\0 / HTTP/1.1\r\n", 17); 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() }; output.clear(); @@ -1154,49 +922,49 @@ testHttp1Parser::testParseRequestLineInvalid() // no URL (grammer invalid, ambiguous with RFC 1945 HTTP/0.9 simple-request) { - input.append("GET HTTP/1.1\n", 14); + input.append("GET HTTP/1.1\r\n", 15); + // RFC 7230 tolerance allows sequence of SP to make this ambiguous + Config.onoff.relaxed_header_parser = 1; 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); + + Config.onoff.relaxed_header_parser = 0; + struct resultSet expectStrict = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scBadRequest, + .suffixSz = 11, + .method = HttpRequestMethod(Http::METHOD_GET), + .uri = NULL, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expectStrict); input.clear(); } // no URL (grammer invalid, ambiguous with RFC 1945 HTTP/0.9 simple-request) { - input.append("GET HTTP/1.1\n", 13); + input.append("GET HTTP/1.1\r\n", 14); 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(); @@ -1212,17 +980,9 @@ testHttp1Parser::testParseRequestLineInvalid() .needsMore = false, .parserState = Http1::HTTP_PARSE_DONE, .status = Http::scBadRequest, - .msgStart = 0, - .msgEnd = -1, //Halt on \xB, no valid method char .suffixSz = input.length(), - .methodStart = -1, - .methodEnd = -1, .method = HttpRequestMethod(), - .uriStart = -1, - .uriEnd = -1, .uri = NULL, - .versionStart = -1, - .versionEnd = -1, .version = AnyP::ProtocolVersion() }; output.clear(); @@ -1232,25 +992,15 @@ testHttp1Parser::testParseRequestLineInvalid() // 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); struct resultSet expect = { .parsed = false, .needsMore = false, .parserState = Http1::HTTP_PARSE_DONE, .status = Http::scBadRequest, - .msgStart = 0, - .msgEnd = -1, //Halt on \t, no valid method char .suffixSz = input.length(), - .methodStart = -1, - .methodEnd = -1, .method = HttpRequestMethod(), - .uriStart = -1, - .uriEnd = -1, .uri = NULL, - .versionStart = -1, - .versionEnd = -1, .version = AnyP::ProtocolVersion() }; output.clear(); @@ -1258,27 +1008,17 @@ testHttp1Parser::testParseRequestLineInvalid() input.clear(); } - // mixed whitespace line with CR middle + // mixed whitespace line with CR { - // 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); + input.append("\r \t \n", 6); 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(); @@ -1295,13 +1035,13 @@ testHttp1Parser::testDripFeed() // calling the parser repeatedly as visible data grows. SBuf data; -#if USE_HTTP_VIOLATIONS - data.append(" ", 12); -#else data.append("\n\n\n\n\n\n\n\n\n\n\n\n", 12); -#endif SBuf::size_type garbageEnd = data.length(); - data.append("GET http://example.com/ HTTP/1.1\r\n", 34); + data.append("GET ", 4); + SBuf::size_type methodEnd = data.length()-1; + data.append("http://example.com/ ", 20); + SBuf::size_type uriEnd = data.length()-1; + data.append("HTTP/1.1\r\n", 10); SBuf::size_type reqLineEnd = data.length() - 1; data.append("Host: example.com\r\n\r\n", 21); SBuf::size_type mimeEnd = data.length() - 1; @@ -1319,17 +1059,9 @@ testHttp1Parser::testDripFeed() .needsMore = true, .parserState = Http1::HTTP_PARSE_NONE, .status = Http::scNone, - .msgStart = -1, - .msgEnd = -1, .suffixSz = 0, - .methodStart = -1, - .methodEnd = -1, .method = HttpRequestMethod(), - .uriStart = -1, - .uriEnd = -1, .uri = NULL, - .versionStart = -1, - .versionEnd = -1, .version = AnyP::ProtocolVersion() }; @@ -1343,28 +1075,32 @@ testHttp1Parser::testDripFeed() // 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 details + if (pos == methodEnd) { + expect.suffixSz = 0; // and a checkpoint buffer reset + expect.method = HttpRequestMethod(Http::METHOD_GET); + } + + // at end of URI expect to see method, URI details + if (pos == uriEnd) { + expect.suffixSz = 0; // and a checkpoint buffer reset + expect.uri = "http://example.com/"; + } + // 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.suffixSz = 0; // and a checkpoint buffer reset 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); } @@ -1373,7 +1109,7 @@ testHttp1Parser::testDripFeed() if (pos == mimeEnd) { expect.parsed = true; expect.needsMore = false; - expect.suffixSz = 0; + expect.suffixSz = 0; // and a checkpoint buffer reset } testResults(__LINE__, ioBuf, hp, expect); diff --git a/src/tests/testHttp1Parser.h b/src/tests/testHttp1Parser.h index f5c0fed29e..041f6ce66b 100644 --- a/src/tests/testHttp1Parser.h +++ b/src/tests/testHttp1Parser.h @@ -14,15 +14,16 @@ class testHttp1Parser : public CPPUNIT_NS::TestFixture { CPPUNIT_TEST_SUITE( testHttp1Parser ); + // object basics are working, just in case. CPPUNIT_TEST( testParserConstruct ); #if __cplusplus >= 201103L - CPPUNIT_TEST( testParseRequestLineTerminators ); + CPPUNIT_TEST( testDripFeed ); CPPUNIT_TEST( testParseRequestLineMethods ); CPPUNIT_TEST( testParseRequestLineProtocols ); + CPPUNIT_TEST( testParseRequestLineTerminators ); CPPUNIT_TEST( testParseRequestLineStrange ); CPPUNIT_TEST( testParseRequestLineInvalid ); - CPPUNIT_TEST( testDripFeed ); #endif CPPUNIT_TEST_SUITE_END();