static void clientUpdateSocketStats(LogTags logType, size_t size);
char *skipLeadingSpace(char *aString);
--static void connNoteUseOfBuffer(ConnStateData* conn, size_t byteCount);
clientStreamNode *
ClientSocketContext::getTail() const
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()
{
}
#endif // USE_OPENSSL
- * be freed and the above connNoteUseOfBuffer() would hit an
- * assertion, not to mention that we were accessing freed memory.
+ 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 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;
// 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<clientReplyContext *>(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<clientReplyContext *>(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<clientReplyContext *>(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<clientReplyContext *>(node->data.getRaw());
- assert (repContext);
- repContext->setReplyToError(ERR_INVALID_URL, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL);
++ assert(repContext);
++ repContext->setReplyToError(ERR_INVALID_URL, Http::scBadRequest, hp->method(), http->uri, conn->clientConnection->remote, NULL, NULL, NULL);
+ assert(context->http->out.offset == 0);
+ context->pullData();
- connNoteUseOfBuffer(conn, http->req_sz);
+ return;
+ }
- /* 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<clientReplyContext *>(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<clientReplyContext *>(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;
+ }
- /* 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))) {
+ /* compile headers */
- debugs(33, 5, "Failed to parse request headers:\n" << HttpParserHdrBuf(hp));
++ if (hp->messageProtocol().major >= 1 && !request->parseHeader(*hp)) {
+ clientStreamNode *node = context->getClientReplyContext();
- setLogUri(http, http->uri, true);
++ debugs(33, 5, "Failed to parse request headers:\n" << hp->mimeHeader());
+ conn->quitAfterError(request.getRaw());
+ // setLogUri should called before repContext->setReplyToError
- assert (repContext);
- repContext->setReplyToError(ERR_INVALID_REQ, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL);
++ setLogUri(http, http->uri, true);
+ clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
- connNoteUseOfBuffer(conn, http->req_sz);
++ 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();
+ clientProcessRequestFinished(conn, request);
+ return;
+ }
}
// Some blobs below are still HTTP-specific, but we would have to rewrite
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())) {
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)) {
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 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? */
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
{
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);
}
if (putSize > 0)
-- connNoteUseOfBuffer(this, putSize);
++ consumeInput(putSize);
if (!bodyPipe) {
debugs(33,5, HERE << "produced entire request body for " << clientConnection);
--- /dev/null
- #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 <cppunit/TestAssert.h>
+
+#define private public
+#define protected public
+
+#include "testHttp1Parser.h"
+#include "http/one/RequestParser.h"
+#include "http/RequestMethod.h"
+#include "Mem.h"
+#include "MemBuf.h"
+#include "SquidConfig.h"
+#include "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;
+ }
+}