From: Amos Jeffries Date: Sun, 14 Sep 2014 12:43:00 +0000 (-0700) Subject: Merge from trunk rev.13584 X-Git-Tag: merge-candidate-3-v1~506^2~7 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=48a37aee5a8855fd8cb05560089b49de93390363;p=thirdparty%2Fsquid.git Merge from trunk rev.13584 Also, re-remove the connNoteUseOfBuffer logics pulled back in by earlier trunk merge. --- 48a37aee5a8855fd8cb05560089b49de93390363 diff --cc src/client_side.cc index 0805774bfb,8524aad6e6..6d6a787f38 --- a/src/client_side.cc +++ b/src/client_side.cc @@@ -217,7 -196,7 +197,6 @@@ static void ClientSocketContextPushDefe static void clientUpdateSocketStats(LogTags logType, size_t size); char *skipLeadingSpace(char *aString); --static void connNoteUseOfBuffer(ConnStateData* conn, size_t byteCount); clientStreamNode * ClientSocketContext::getTail() const @@@ -2355,13 -2388,13 +2336,6 @@@ ConnStateData::consumeInput(const size_ debugs(33, 5, "in.buf has " << in.buf.length() << " unused bytes"); } --// TODO: Remove when renaming ConnStateData --void --connNoteUseOfBuffer(ConnStateData* conn, size_t byteCount) --{ -- conn->consumeInput(byteCount); --} -- void ConnStateData::clientAfterReadingRequests() { @@@ -2474,8 -2507,25 +2448,24 @@@ bool ConnStateData::serveDelayedError(C } #endif // USE_OPENSSL + static void + clientProcessRequestFinished(ConnStateData *conn, const HttpRequest::Pointer &request) + { + /* + * DPW 2007-05-18 + * Moved the TCP_RESET feature from clientReplyContext::sendMoreData + * to here because calling comm_reset_close() causes http to - * be freed and the above connNoteUseOfBuffer() would hit an - * assertion, not to mention that we were accessing freed memory. ++ * be freed before accessing. + */ + if (request != NULL && request->flags.resetTcp && Comm::IsConnOpen(conn->clientConnection)) { + debugs(33, 3, HERE << "Sending TCP RST on " << conn->clientConnection); + conn->flags.readMore = false; + comm_reset_close(conn->clientConnection); + } + } + void -clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *context, const HttpRequestMethod& method, Http::ProtocolVersion http_ver) +clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp, ClientSocketContext *context) { ClientHttpRequest *http = context->http; HttpRequest::Pointer request; @@@ -2492,67 -2542,90 +2482,87 @@@ // only need to go through the final body/conn setup to doCallouts(). assert(http->request); request = http->request; - notedUseOfBuffer = true; } else { - if (context->flags.parsed_ok == 0) { - clientStreamNode *node = context->getClientReplyContext(); - debugs(33, 2, "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) { - case Http::scHeaderTooLarge: - repContext->setReplyToError(ERR_TOO_BIG, Http::scBadRequest, hp->method(), http->uri, - conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL); - break; - case Http::scMethodNotAllowed: - repContext->setReplyToError(ERR_UNSUP_REQ, Http::scMethodNotAllowed, hp->method(), http->uri, - conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL); - break; - case Http::scHttpVersionNotSupported: - repContext->setReplyToError(ERR_UNSUP_HTTPVERSION, Http::scHttpVersionNotSupported, hp->method(), http->uri, - conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL); - break; - default: - repContext->setReplyToError(ERR_INVALID_REQ, hp->request_parse_status, hp->method(), http->uri, - conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL); + if (context->flags.parsed_ok == 0) { + clientStreamNode *node = context->getClientReplyContext(); - debugs(33, 2, "clientProcessRequest: Invalid Request"); ++ debugs(33, 2, "Invalid Request"); + conn->quitAfterError(NULL); + // setLogUri should called before repContext->setReplyToError - setLogUri(http, http->uri, true); ++ setLogUri(http, http->uri, true); + clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); - assert (repContext); ++ assert(repContext); + 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, hp->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, + conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL); + } + assert(context->http->out.offset == 0); + context->pullData(); - connNoteUseOfBuffer(conn, http->req_sz); + return; } - assert(context->http->out.offset == 0); - context->pullData(); - goto finish; - } - if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, hp->method())) == NULL) { - clientStreamNode *node = context->getClientReplyContext(); - debugs(33, 5, "Invalid URL: " << http->uri); - conn->quitAfterError(request.getRaw()); - // setLogUri should called before repContext->setReplyToError - setLogUri(http, http->uri, true); - clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); - assert (repContext); - repContext->setReplyToError(ERR_INVALID_URL, Http::scBadRequest, hp->method(), http->uri, conn->clientConnection->remote, NULL, NULL, NULL); - assert(context->http->out.offset == 0); - context->pullData(); - goto finish; - } - if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, method)) == NULL) { ++ if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, hp->method())) == NULL) { + clientStreamNode *node = context->getClientReplyContext(); + debugs(33, 5, "Invalid URL: " << http->uri); + conn->quitAfterError(request.getRaw()); + // setLogUri should called before repContext->setReplyToError - setLogUri(http, http->uri, true); ++ setLogUri(http, http->uri, true); + clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); - assert (repContext); - repContext->setReplyToError(ERR_INVALID_URL, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL); ++ assert(repContext); ++ repContext->setReplyToError(ERR_INVALID_URL, Http::scBadRequest, hp->method(), http->uri, conn->clientConnection->remote, NULL, NULL, NULL); + assert(context->http->out.offset == 0); + context->pullData(); - connNoteUseOfBuffer(conn, http->req_sz); + return; + } - /* compile headers */ - if (hp->messageProtocol().major >= 1 && !request->parseHeader(*hp)) { - clientStreamNode *node = context->getClientReplyContext(); - 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, hp->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 */ + /* TODO: move HTTP-specific processing into servers/HttpServer and such */ + 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(); - connNoteUseOfBuffer(conn, http->req_sz); + clientProcessRequestFinished(conn, request); + return; + } + /* compile headers */ - /* we should skip request line! */ - /* XXX should actually know the damned buffer size here */ - if (http_ver.major >= 1 && !request->parseHeader(HttpParserHdrBuf(hp), HttpParserHdrSz(hp))) { ++ if (hp->messageProtocol().major >= 1 && !request->parseHeader(*hp)) { + clientStreamNode *node = context->getClientReplyContext(); - debugs(33, 5, "Failed to parse request headers:\n" << HttpParserHdrBuf(hp)); ++ debugs(33, 5, "Failed to parse request headers:\n" << hp->mimeHeader()); + conn->quitAfterError(request.getRaw()); + // setLogUri should called before repContext->setReplyToError - setLogUri(http, http->uri, true); ++ setLogUri(http, http->uri, true); + clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); - assert (repContext); - repContext->setReplyToError(ERR_INVALID_REQ, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL); ++ assert(repContext); ++ repContext->setReplyToError(ERR_INVALID_REQ, Http::scBadRequest, hp->method(), http->uri, conn->clientConnection->remote, NULL, NULL, NULL); + assert(context->http->out.offset == 0); + context->pullData(); - connNoteUseOfBuffer(conn, http->req_sz); + clientProcessRequestFinished(conn, request); + return; + } } // Some blobs below are still HTTP-specific, but we would have to rewrite @@@ -2654,7 -2720,9 +2665,8 @@@ conn->clientConnection->remote, request.getRaw(), NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); - goto finish; - connNoteUseOfBuffer(conn, http->req_sz); + clientProcessRequestFinished(conn, request); + return; } if (!chunked && !clientIsContentLengthValid(request.getRaw())) { @@@ -2667,7 -2735,9 +2679,8 @@@ conn->clientConnection->remote, request.getRaw(), NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); - goto finish; - connNoteUseOfBuffer(conn, http->req_sz); + clientProcessRequestFinished(conn, request); + return; } if (request->header.has(HDR_EXPECT)) { @@@ -2682,7 -2752,9 +2695,8 @@@ conn->clientConnection->remote, request.getRaw(), NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); - goto finish; - connNoteUseOfBuffer(conn, http->req_sz); + clientProcessRequestFinished(conn, request); + return; } } @@@ -2699,8 -2771,16 +2713,10 @@@ } #if USE_OPENSSL - if (conn->switchedToHttps() && conn->serveDelayedError(context)) - goto finish; + if (conn->switchedToHttps() && conn->serveDelayedError(context)) { - if (!notedUseOfBuffer) - connNoteUseOfBuffer(conn, http->req_sz); + clientProcessRequestFinished(conn, request); + return; + } #endif /* Do we expect a request-body? */ @@@ -2742,21 -2831,21 +2761,9 @@@ http->doCallouts(); - finish: - /* - * 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); - } - if (!notedUseOfBuffer) - connNoteUseOfBuffer(conn, http->req_sz); - + clientProcessRequestFinished(conn, request); } -static void -connStripBufferWhitespace (ConnStateData * conn) -{ - // XXX: kill this whole function. - while (!conn->in.buf.isEmpty() && xisspace(conn->in.buf.at(0))) { - conn->in.buf.consume(1); - } -} - int ConnStateData::pipelinePrefetchMax() const { @@@ -2811,9 -3166,13 +3083,18 @@@ ConnStateData::clientParseRequests( if (concurrentRequestQueueFilled()) break; ++<<<<<<< TREE + if (ClientSocketContext *context = parseOneRequest()) { ++======= + // try to parse the PROXY protocol header magic bytes + if (needProxyProtocolHeader_ && !parseProxyProtocolHeader()) + break; + + Http::ProtocolVersion http_ver; + if (ClientSocketContext *context = parseOneRequest(http_ver)) { ++>>>>>>> MERGE-SOURCE debugs(33, 5, clientConnection << ": done parsing a request"); + AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout", CommTimeoutCbPtrFun(clientLifetimeTimeout, context->http)); commSetConnTimeout(clientConnection, Config.Timeout.lifetime, timeoutCall); @@@ -2984,7 -3341,7 +3265,7 @@@ ConnStateData::handleRequestBodyData( } if (putSize > 0) -- connNoteUseOfBuffer(this, putSize); ++ consumeInput(putSize); if (!bodyPipe) { debugs(33,5, HERE << "produced entire request body for " << clientConnection); diff --cc src/http/RequestMethod.cc index 6eb1048aaa,74f9c77cbc..02703a8e7a --- a/src/http/RequestMethod.cc +++ b/src/http/RequestMethod.cc @@@ -1,9 -1,15 +1,15 @@@ /* - * DEBUG: section 73 HTTP Request + * Copyright (C) 1996-2014 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. */ + /* DEBUG: section 73 HTTP Request */ + #include "squid.h" -#include "HttpRequestMethod.h" +#include "http/RequestMethod.h" #include "SquidConfig.h" #include "wordlist.h" diff --cc src/http/one/Parser.cc index 42e3ad18a2,0000000000..48585d4388 mode 100644,000000..100644 --- a/src/http/one/Parser.cc +++ b/src/http/one/Parser.cc @@@ -1,72 -1,0 +1,80 @@@ ++/* ++ * Copyright (C) 1996-2014 The Squid Software Foundation and contributors ++ * ++ * Squid software is distributed under GPLv2+ license and includes ++ * contributions from numerous individuals and organizations. ++ * Please see the COPYING and CONTRIBUTORS files for details. ++ */ ++ +#include "squid.h" +#include "Debug.h" +#include "http/one/Parser.h" +#include "parser/Tokenizer.h" + +/// RFC 7230 section 2.6 - 7 magic octets +const SBuf Http::One::Parser::Http1magic("HTTP/1."); + +void +Http::One::Parser::clear() +{ + parsingStage_ = HTTP_PARSE_NONE; + buf_ = NULL; + msgProtocol_ = AnyP::ProtocolVersion(); + mimeHeaderBlock_.clear(); +} + +// arbitrary maximum-length for headers which can be found by Http1Parser::getHeaderField() +#define GET_HDR_SZ 1024 + +// BUG: returns only the first header line with given name, +// ignores multi-line headers and obs-fold headers +char * +Http::One::Parser::getHeaderField(const char *name) +{ + if (!headerBlockSize() || !name) + return NULL; + + LOCAL_ARRAY(char, header, GET_HDR_SZ); + const int namelen = name ? strlen(name) : 0; + + debugs(25, 5, "looking for " << name); + + // while we can find more LF in the SBuf + static CharacterSet iso8859Line = CharacterSet("non-LF",'\0','\n'-1) + CharacterSet(NULL, '\n'+1, (unsigned char)0xFF); + ::Parser::Tokenizer tok(mimeHeaderBlock_); + SBuf p; + static const SBuf crlf("\r\n"); + + while (tok.prefix(p, iso8859Line)) { + tok.skipOne(CharacterSet::LF); // move tokenizer past the LF + + // header lines must start with the name (case insensitive) + if (p.substr(0, namelen).caseCmp(name, namelen)) + continue; + + // then a COLON + if (p[namelen] != ':') + continue; + + // drop any trailing *CR sequence + p.trim(crlf, false, true); + + debugs(25, 5, "checking " << p); + p.consume(namelen + 1); + + // TODO: optimize SBuf::trim to take CharacterSet directly + ::Parser::Tokenizer t(p); + t.skipAll(CharacterSet::WSP); + p = t.remaining(); + + // prevent buffer overrun on char header[]; + p.chop(0, sizeof(header)-1); + + // return the header field-value + xstrncpy(header, p.rawContent(), p.length()); + debugs(25, 5, "returning " << header); + return header; + } + + return NULL; +} diff --cc src/http/one/Parser.h index e6ae3f7259,0000000000..63c9318c18 mode 100644,000000..100644 --- a/src/http/one/Parser.h +++ b/src/http/one/Parser.h @@@ -1,101 -1,0 +1,109 @@@ ++/* ++ * Copyright (C) 1996-2014 The Squid Software Foundation and contributors ++ * ++ * Squid software is distributed under GPLv2+ license and includes ++ * contributions from numerous individuals and organizations. ++ * Please see the COPYING and CONTRIBUTORS files for details. ++ */ ++ +#ifndef _SQUID_SRC_HTTP_ONE_PARSER_H +#define _SQUID_SRC_HTTP_ONE_PARSER_H + +#include "anyp/ProtocolVersion.h" +#include "http/one/forward.h" +#include "SBuf.h" + +namespace Http { +namespace One { + +// Parser states +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/1.x protocol parser + * + * Works on a raw character I/O buffer and tokenizes the content into + * 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 Parser : public RefCountable +{ +public: + typedef SBuf::size_type size_type; + + Parser() : parsingStage_(HTTP_PARSE_NONE) {} + + /// Set this parser back to a default state. + /// Will DROP any reference to a buffer (does not free). + virtual void clear(); + + /// attempt to parse a message from the buffer + /// \retval true if a full message was found and parsed + /// \retval false if incomplete, invalid or no message was found + virtual bool parse(const SBuf &aBuf) = 0; + + /** Whether the parser is waiting on more data to complete parsing a message. + * Use to distinguish between incomplete data and error results + * when parse() returns false. + */ + bool needsMoreData() const {return parsingStage_!=HTTP_PARSE_DONE;} + + /// size in bytes of the first line including CRLF terminator + virtual size_type firstLineSize() const = 0; + + /// size in bytes of the message headers including CRLF terminator(s) + /// but excluding first-line bytes + size_type headerBlockSize() const {return mimeHeaderBlock_.length();} + + /// size in bytes of HTTP message block, includes first-line and mime headers + /// excludes any body/entity/payload bytes + /// excludes any garbage prefix before the first-line + size_type messageHeaderSize() const {return firstLineSize() + headerBlockSize();} + + /// buffer containing HTTP mime headers, excluding message first-line. + SBuf mimeHeader() const {return mimeHeaderBlock_;} + + /// the protocol label for this message + const AnyP::ProtocolVersion & messageProtocol() const {return msgProtocol_;} + + /** + * Scan the mime header block (badly) for a header with teh given name. + * + * BUG: omits lines when searching for headers with obs-fold or multiple entries. + * + * BUG: limits output to just 1KB when Squid accepts up to 64KB line length. + * + * \return A pointer to a field-value of the first matching field-name, or NULL. + */ + char *getHeaderField(const char *name); + + /// the remaining unprocessed section of buffer + const SBuf &remaining() const {return buf_;} + +protected: + /// RFC 7230 section 2.6 - 7 magic octets + static const SBuf Http1magic; + + /// bytes remaining to be parsed + SBuf buf_; + + /// what stage the parser is currently up to + ParseState parsingStage_; + + /// what protocol label has been found in the first line (if any) + AnyP::ProtocolVersion msgProtocol_; + + /// buffer holding the mime headers (if any) + SBuf mimeHeaderBlock_; +}; + +} // namespace One +} // namespace Http + +#endif /* _SQUID_SRC_HTTP_ONE_PARSER_H */ diff --cc src/mime_header.cc index 2a40576485,92706de16c..4d6f5d15ac --- a/src/mime_header.cc +++ b/src/mime_header.cc @@@ -1,36 -1,16 +1,14 @@@ /* - * DEBUG: section 25 MiME Header Parsing - * AUTHOR: Harvest Derived - * - * SQUID Web Proxy Cache http://www.squid-cache.org/ - * ---------------------------------------------------------- - * - * Squid is the result of efforts by numerous individuals from - * the Internet community; see the CONTRIBUTORS file for full - * details. Many organizations have provided support for Squid's - * development; see the SPONSORS file for full details. Squid is - * Copyrighted (C) 2001 by the Regents of the University of - * California; see the COPYRIGHT file for full details. Squid - * 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. + * Copyright (C) 1996-2014 The Squid Software Foundation and contributors * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. */ + /* DEBUG: section 25 MiME Header Parsing */ + #include "squid.h" - -#define GET_HDR_SZ 1024 #include "Debug.h" #include "profiler/Profiler.h" diff --cc src/tests/testHttp1Parser.cc index 4ca81c0188,0000000000..742bab8039 mode 100644,000000..100644 --- a/src/tests/testHttp1Parser.cc +++ b/src/tests/testHttp1Parser.cc @@@ -1,1435 -1,0 +1,1442 @@@ - #define SQUID_UNIT_TEST 1 ++/* ++ * Copyright (C) 1996-2014 The Squid Software Foundation and contributors ++ * ++ * Squid software is distributed under GPLv2+ license and includes ++ * contributions from numerous individuals and organizations. ++ * Please see the COPYING and CONTRIBUTORS files for details. ++ */ ++ +#include "squid.h" + +#include + +#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 "testHttp1Parser.h" + +CPPUNIT_TEST_SUITE_REGISTRATION( testHttp1Parser ); + +void +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 +testHttp1Parser::testParserConstruct() +{ + // whether the constructor works + { + Http1::RequestParser output; + CPPUNIT_ASSERT_EQUAL(true, output.needsMoreData()); + CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_NONE, output.parsingStage_); + CPPUNIT_ASSERT_EQUAL(Http::scNone, output.request_parse_status); // XXX: clear() not being called. + CPPUNIT_ASSERT_EQUAL(-1, output.req.start); + CPPUNIT_ASSERT_EQUAL(-1, output.req.end); + CPPUNIT_ASSERT(output.buf_.isEmpty()); + CPPUNIT_ASSERT_EQUAL(-1, output.req.m_start); + CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end); + CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_NONE), output.method_); + CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start); + CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end); + CPPUNIT_ASSERT(output.uri_.isEmpty()); + CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start); + CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end); + CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_); + } + + // whether new() works + { + Http1::RequestParser *output = new Http1::RequestParser; + CPPUNIT_ASSERT_EQUAL(true, output->needsMoreData()); + CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_NONE, output->parsingStage_); + CPPUNIT_ASSERT_EQUAL(Http::scNone, output->request_parse_status); + CPPUNIT_ASSERT_EQUAL(-1, output->req.start); + CPPUNIT_ASSERT_EQUAL(-1, output->req.end); + CPPUNIT_ASSERT(output->buf_.isEmpty()); + CPPUNIT_ASSERT_EQUAL(-1, output->req.m_start); + CPPUNIT_ASSERT_EQUAL(-1, output->req.m_end); + CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_NONE), output->method_); + CPPUNIT_ASSERT_EQUAL(-1, output->req.u_start); + CPPUNIT_ASSERT_EQUAL(-1, output->req.u_end); + CPPUNIT_ASSERT(output->uri_.isEmpty()); + CPPUNIT_ASSERT_EQUAL(-1, output->req.v_start); + CPPUNIT_ASSERT_EQUAL(-1, output->req.v_end); + CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output->msgProtocol_); + delete output; + } +} + +void +testHttp1Parser::testParseRequestLineProtocols() +{ + // ensure MemPools etc exist + globalSetup(); + + 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); + 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 WHEN_RFC_COMPLIANT + { + input.append("POST /\r\n", 7); + 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); + 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); + 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); + 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 7230 : future versions do not use request-line syntax + { + input.append("GET / HTTP/10.12\r\n", 18); + struct resultSet expect = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_MIME, + .status = Http::scHttpVersionNotSupported, + .msgStart = 0, + .msgEnd = (int)input.length()-1, + .suffixSz = input.length(), + .methodStart = 0, + .methodEnd = 2, + .method = HttpRequestMethod(Http::METHOD_GET), + .uriStart = 4, + .uriEnd = 4, + .uri = "/", + .versionStart = 6, + .versionEnd = 15, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expect); + input.clear(); + } + + // unknown non-HTTP protocol names + { + input.append("GET / FOO/1.0\n", 14); + struct resultSet expect = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scHttpVersionNotSupported, + .msgStart = 0, + .msgEnd = (int)input.length()-1, + .suffixSz = input.length(), + .methodStart = 0, + .methodEnd = 2, + .method = HttpRequestMethod(Http::METHOD_GET), + .uriStart = 4, + .uriEnd = 4, + .uri = "/", + .versionStart = 6, + .versionEnd = 12, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expect); + input.clear(); + } + + // no version + { + input.append("GET / HTTP/\n", 12); + struct resultSet expect = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scHttpVersionNotSupported, + .msgStart = 0, + .msgEnd = (int)input.length()-1, + .suffixSz = input.length(), + .methodStart = 0, + .methodEnd = 2, + .method = HttpRequestMethod(Http::METHOD_GET), + .uriStart = 4, + .uriEnd = 4, + .uri = "/", + .versionStart = 6, + .versionEnd = 10, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expect); + input.clear(); + } + + // no major version + { + input.append("GET / HTTP/.1\n", 14); + struct resultSet expect = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scHttpVersionNotSupported, + .msgStart = 0, + .msgEnd = (int)input.length()-1, + .suffixSz = input.length(), + .methodStart = 0, + .methodEnd = 2, + .method = HttpRequestMethod(Http::METHOD_GET), + .uriStart = 4, + .uriEnd = 4, + .uri = "/", + .versionStart = 6, + .versionEnd = 12, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expect); + input.clear(); + } + + // no version dot + { + input.append("GET / HTTP/11\n", 14); + struct resultSet expect = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scHttpVersionNotSupported, + .msgStart = 0, + .msgEnd = (int)input.length()-1, + .suffixSz = input.length(), + .methodStart = 0, + .methodEnd = 2, + .method = HttpRequestMethod(Http::METHOD_GET), + .uriStart = 4, + .uriEnd = 4, + .uri = "/", + .versionStart = 6, + .versionEnd = 12, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expect); + input.clear(); + } + + // negative major version (bug 3062) + { + input.append("GET / HTTP/-999999.1\n", 21); + struct resultSet expect = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scHttpVersionNotSupported, + .msgStart = 0, + .msgEnd = (int)input.length()-1, + .suffixSz = input.length(), + .methodStart = 0, + .methodEnd = 2, + .method = HttpRequestMethod(Http::METHOD_GET), + .uriStart = 4, + .uriEnd = 4, + .uri = "/", + .versionStart = 6, + .versionEnd = 19, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expect); + input.clear(); + } + + // no minor version + { + input.append("GET / HTTP/1.\n", 14); + 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); + 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 +testHttp1Parser::testParseRequestLineStrange() +{ + // ensure MemPools etc exist + globalSetup(); + + SBuf input; + Http1::RequestParser output; + + // space padded URL + { + input.append("GET / HTTP/1.1\r\n", 21); + 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); + 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); + 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 +testHttp1Parser::testParseRequestLineTerminators() +{ + // ensure MemPools etc exist + globalSetup(); + + SBuf input; + Http1::RequestParser output; + + // alternative EOL sequence: NL-only + { + input.append("GET / HTTP/1.1\n", 15); + 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, + .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(); + } + + // alternative EOL sequence: multi-CR-NL + { + input.append("GET / HTTP/1.1\r\r\r\n", 18); + // Being tolerant we can ignore and elide these apparently benign CR + 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 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, + .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); + 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 +testHttp1Parser::testParseRequestLineMethods() +{ + // ensure MemPools etc exist + globalSetup(); + + SBuf input; + Http1::RequestParser output; + + // RFC 2616 : . method + { + input.append(". / HTTP/1.1\n", 13); + 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); + 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); + 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); + struct resultSet expect = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scBadRequest, + .msgStart = 0, + .msgEnd = (int)input.length()-1, + .suffixSz = input.length(), + .methodStart = 0, + .methodEnd = -1, + .method = HttpRequestMethod(), + .uriStart = -1, + .uriEnd = -1, + .uri = NULL, + .versionStart = -1, + .versionEnd = -1, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expect); + input.clear(); + } + + { + input.append("GET\n", 4); + 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(); + } + + // space padded method (in strict mode SP is reserved so invalid as a method byte) + { + input.append(" GET / HTTP/1.1\n", 16); + // 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; + 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); +#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 +testHttp1Parser::testParseRequestLineInvalid() +{ + // ensure MemPools etc exist + globalSetup(); + + SBuf input; + Http1::RequestParser output; + + // no method (but in a form which is ambiguous with HTTP/0.9 simple-request) + { + // XXX: 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("/"), + .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); + + // XXX: 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("/"), + .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 detect as invalid + Config.onoff.relaxed_header_parser = 0; +#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 (invalid) + { + input.append("GET\x0B / HTTP/1.1\n", 16); +#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); + 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); +#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 invalid, ambiguous with RFC 1945 HTTP/0.9 simple-request) + { + input.append("GET HTTP/1.1\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 = 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); + 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); + 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); + 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); + struct resultSet expect = { + .parsed = false, + .needsMore = false, + .parserState = Http1::HTTP_PARSE_DONE, + .status = Http::scBadRequest, + .msgStart = 0, + .msgEnd = -1, // halt on the \r + .suffixSz = input.length(), + .methodStart = -1, + .methodEnd = -1, + .method = HttpRequestMethod(), + .uriStart = -1, + .uriEnd = -1, + .uri = NULL, + .versionStart = -1, + .versionEnd = -1, + .version = AnyP::ProtocolVersion() + }; + output.clear(); + testResults(__LINE__, input, output, expect); + input.clear(); + } +} + +void +testHttp1Parser::testDripFeed() +{ + // Simulate a client drip-feeding Squid a few bytes at a time. + // extend the size of the buffer from 0 bytes to full request length + // calling the parser repeatedly as visible data grows. + + SBuf data; + data.append(" ", 12); + SBuf::size_type garbageEnd = data.length(); + data.append("GET http://example.com/ HTTP/1.1\r\n", 34); + SBuf::size_type reqLineEnd = data.length() - 1; + data.append("Host: example.com\r\n\r\n", 21); + SBuf::size_type mimeEnd = data.length() - 1; + data.append("...", 3); // trailer to catch mime EOS errors. + + SBuf ioBuf; // begins empty + Http1::RequestParser hp; + + // only relaxed parser accepts the garbage whitespace + Config.onoff.relaxed_header_parser = 1; + + // state of things we expect right now + struct resultSet expect = { + .parsed = false, + .needsMore = true, + .parserState = Http1::HTTP_PARSE_NONE, + .status = Http::scNone, + .msgStart = -1, + .msgEnd = -1, + .suffixSz = 0, + .methodStart = -1, + .methodEnd = -1, + .method = HttpRequestMethod(), + .uriStart = -1, + .uriEnd = -1, + .uri = NULL, + .versionStart = -1, + .versionEnd = -1, + .version = AnyP::ProtocolVersion() + }; + + Config.maxRequestHeaderSize = 1024; // large enough to hold the test data. + + for (SBuf::size_type pos = 0; pos <= data.length(); ++pos) { + + // simulate reading one more byte + ioBuf.append(data.substr(pos,1)); + + // when the garbage is passed we expect to start seeing first-line bytes + if (pos == garbageEnd) { + expect.parserState = Http1::HTTP_PARSE_FIRST; + expect.msgStart = 0; + } + + // all points after garbage start to see accumulated bytes looking for end of current section + if (pos >= garbageEnd) + expect.suffixSz = ioBuf.length(); + + // at end of request line expect to see method, URI, version details + // and switch to seeking Mime header section + if (pos == reqLineEnd) { + expect.parserState = Http1::HTTP_PARSE_MIME; + expect.suffixSz = 0; + expect.msgEnd = reqLineEnd-garbageEnd; + expect.status = Http::scOkay; + expect.methodStart = 0; + expect.methodEnd = 2; + expect.method = HttpRequestMethod(Http::METHOD_GET); + expect.uriStart = 4; + expect.uriEnd = 22; + expect.uri = "http://example.com/"; + expect.versionStart = 24; + expect.versionEnd = 31; + expect.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1); + } + + // one mime header is done we are expectign a new request + // parse results say true and initial data is all gone from the buffer + if (pos == mimeEnd) { + expect.parsed = true; + expect.needsMore = false; + expect.suffixSz = 0; + } + + testResults(__LINE__, ioBuf, hp, expect); + + // sync the buffers like Squid does + ioBuf = hp.remaining(); + + // Squid stops using the parser once it has parsed the first message. + if (!hp.needsMoreData()) + break; + } +} diff --cc src/tests/testHttp1Parser.h index fae1ff4e2e,9afbd84437..f844827231 --- a/src/tests/testHttp1Parser.h +++ b/src/tests/testHttp1Parser.h @@@ -1,5 -1,13 +1,13 @@@ + /* + * Copyright (C) 1996-2014 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for details. + */ + -#ifndef SQUID_SRC_TESTS_TESTHTTPPARSER_H -#define SQUID_SRC_TESTS_TESTHTTPPARSER_H +#ifndef SQUID_SRC_TESTS_TESTHTTP1PARSER_H +#define SQUID_SRC_TESTS_TESTHTTP1PARSER_H #include