#include "http/one/RequestParser.h"
#include "http/ProtocolVersion.h"
#include "mime_header.h"
+#include "parser/Tokenizer.h"
#include "profiler/Profiler.h"
#include "SquidConfig.h"
Http::One::RequestParser::RequestParser() :
Parser(),
- request_parse_status(Http::scNone)
+ request_parse_status(Http::scNone),
+ firstLineGarbage_(0)
+{}
+
+Http1::Parser::size_type
+Http::One::RequestParser::firstLineSize() const
{
- req.start = req.end = -1;
- req.m_start = req.m_end = -1;
- req.u_start = req.u_end = -1;
- req.v_start = req.v_end = -1;
+ // RFC 7230 section 2.6
+ /* method SP request-target SP "HTTP/" DIGIT "." DIGIT CRLF */
+ return method_.image().length() + uri_.length() + 12;
}
/**
buf_.consume(1);
}
}
+}
- /* XXX: this is a Squid-specific tolerance
- * it appears never to have been relevant outside out unit-tests
- * because the ConnStateData parser loop starts with consumeWhitespace()
- * which absorbs any SP HTAB VTAB CR LF characters.
- * But unit-tests called the HttpParser method directly without that pruning.
- */
-#if USE_HTTP_VIOLATIONS
- if (Config.onoff.relaxed_header_parser) {
- if (Config.onoff.relaxed_header_parser < 0 && buf_[0] == ' ')
- debugs(74, DBG_IMPORTANT, "WARNING: Invalid HTTP Request: " <<
- "Whitespace bytes received ahead of method. " <<
- "Ignored due to relaxed_header_parser.");
- // Be tolerant of prefix spaces (other bytes are valid method values)
- while (!buf_.isEmpty() && buf_[0] == ' ') {
- buf_.consume(1);
- }
- }
-#endif
+/// detect and skip the CRLF or LF line terminator
+/// consume from the tokenizer and return true only if found
+bool
+Http::One::RequestParser::skipLineTerminator(::Parser::Tokenizer &tok) const
+{
+ static const SBuf crlf("\r\n");
+ if (tok.skip(crlf))
+ return true;
+
+ if (Config.onoff.relaxed_header_parser && tok.skipOne(CharacterSet::LF))
+ return true;
+
+ return false;
}
/**
- * Attempt to parse the first line of a new request message.
+ * Attempt to parse the method field out of an HTTP message request-line.
*
* Governed by:
* RFC 1945 section 5.1
- * RFC 7230 section 3.1 and 3.5
+ * RFC 7230 section 2.6, 3.1 and 3.5
*
- * Parsing state is stored between calls. However the current implementation
- * begins parsing from scratch on every call.
- * The return value tells you whether the parsing state fields are valid or not.
+ * Parsing state is stored between calls. The current implementation uses
+ * checkpoints after each successful request-line field.
+ * The return value tells you whether the parsing is completed or not.
*
* \retval -1 an error occurred. request_parse_status indicates HTTP status result.
- * \retval 1 successful parse. member fields contain the request-line items
+ * \retval 1 successful parse. method_ is filled and buffer consumed including first delimiter.
* \retval 0 more data is needed to complete the parse
*/
int
-Http::One::RequestParser::parseRequestFirstLine()
+Http::One::RequestParser::parseMethodField(::Parser::Tokenizer &tok, const CharacterSet &WspDelim)
{
- int second_word = -1; // track the suspected URI start
- int first_whitespace = -1, last_whitespace = -1; // track the first and last SP byte
- int line_end = -1; // tracks the last byte BEFORE terminal \r\n or \n sequence
+ // scan for up to 16 valid method characters.
+ static const size_t maxMethodLength = 16;
- debugs(74, 5, "parsing possible request: buf.length=" << buf_.length());
- debugs(74, DBG_DATA, buf_);
+ SBuf methodFound;
- // Single-pass parse: (provided we have the whole line anyways)
+ // method field is a sequence of TCHAR.
+ // NP: prefix-with-limit returns true if it finds ANY valid chars
+ if (!tok.prefix(methodFound, CharacterSet::TCHAR, maxMethodLength)) {
+ // missing/invalid 'method'.
+ request_parse_status = Http::scBadRequest;
+ debugs(33, 5, "invalid request-line. missing method");
+ return -1;
+ }
- req.start = 0;
- req.end = -1;
- for (SBuf::size_type i = 0; i < buf_.length(); ++i) {
- // track first and last whitespace (SP only)
- if (buf_[i] == ' ') {
- last_whitespace = i;
- if (first_whitespace < req.start)
- first_whitespace = i;
- }
+ // we may be at the end if we found exactly maxMethodLength bytes
+ if (tok.atEnd()) {
+ debugs(74, 5, "Parser needs more data to find method");
+ return 0;
+ }
- // track next non-SP/non-HT byte after first_whitespace
- if (second_word < first_whitespace && buf_[i] != ' ' && buf_[i] != '\t') {
- second_word = i;
+ // ... followed by at least one whitespace character.
+ if (!tok.skipOne(WspDelim)) {
+ // non-delimiter found after accepted method bytes means ...
+ if (methodFound.length() == maxMethodLength) {
+ // method longer than acceptible.
+ // RFC 7230 section 3.1.1 mandatory (SHOULD) 501 response
+ request_parse_status = Http::scNotImplemented;
+ debugs(33, 5, "invalid request-line. method too long");
+ } else {
+ // invalid character in the URL
+ // RFC 7230 section 3.1.1 required (SHOULD) 400 response
+ request_parse_status = Http::scBadRequest;
+ debugs(33, 5, "invalid request-line. missing method delimiter");
}
+ return -1;
+ }
+ method_ = HttpRequestMethod(methodFound);
+ buf_ = tok.remaining(); // incremental parse checkpoint
+ return 1;
+}
- // locate line terminator
- if (buf_[i] == '\n') {
- req.end = i;
- line_end = i - 1;
- break;
- }
- if (i < buf_.length() - 1 && buf_[i] == '\r') {
- if (Config.onoff.relaxed_header_parser) {
- if (Config.onoff.relaxed_header_parser < 0 && buf_[i + 1] == '\r')
- debugs(74, DBG_IMPORTANT, "WARNING: Invalid HTTP Request: " <<
- "Series of carriage-return bytes received prior to line terminator. " <<
- "Ignored due to relaxed_header_parser.");
-
- // Be tolerant of invalid multiple \r prior to terminal \n
- if (buf_[i + 1] == '\n' || buf_[i + 1] == '\r')
- line_end = i - 1;
- while (i < buf_.length() - 1 && buf_[i + 1] == '\r')
- ++i;
-
- if (buf_[i + 1] == '\n') {
- req.end = i + 1;
- break;
- }
- } else {
- if (buf_[i + 1] == '\n') {
- req.end = i + 1;
- line_end = i - 1;
- break;
- }
- }
+int
+Http::One::RequestParser::parseUriField(::Parser::Tokenizer &tok, const CharacterSet &WspDelim)
+{
+ // URI field is a sequence of ... what? segments all have different valid charset
+ // go with non-whitespace non-binary characters for now
+ static CharacterSet UriChars("URI-Chars","");
+ if (!UriChars['a']) { // if it needs initializing...
+ /* RFC 3986 section 2:
+ * "
+ * A URI is composed from a limited set of characters consisting of
+ * digits, letters, and a few graphic symbols.
+ * "
+ */
+ // RFC 3986 section 2.1 - percent encoding "%" HEXDIG
+ UriChars.add('%');
+ UriChars += CharacterSet::HEXDIG;
+ // RFC 3986 section 2.2 - reserved characters
+ UriChars += CharacterSet("gen-delims", ":/?#[]@");
+ UriChars += CharacterSet("sub-delims", "!$&'()*+,;=");
+ // RFC 3986 section 2.3 - unreserved characters
+ UriChars += CharacterSet::ALPHA;
+ UriChars += CharacterSet::DIGIT;
+ UriChars += CharacterSet("unreserved", "-._~");
+ }
- // RFC 7230 section 3.1.1 does not prohibit embeded CR like RFC 2616 used to.
- // However it does explicitly state an exact syntax which omits un-encoded CR
- // and defines 400 (Bad Request) as the required action when
- // handed an invalid request-line.
- request_parse_status = Http::scBadRequest;
- return -1;
- }
+ /* Arbitrary 64KB URI upper length limit.
+ *
+ * Not quite as arbitrary as it seems though. Old SquidString objects
+ * cannot store strings larger than 64KB, so we must limit until they
+ * have all been replaced with SBuf.
+ *
+ * Not that it matters but RFC 7230 section 3.1.1 requires (RECOMMENDED)
+ * at least 8000 octets for the whole line, including method and version.
+ */
+ const size_t maxUriLength = min(static_cast<size_t>(Config.maxRequestHeaderSize) - firstLineSize(),
+ static_cast<size_t>((64*1024)-1));
+
+ SBuf uriFound;
+ // NP: prefix-with-limit returns true if it finds ANY valid chars
+ if (!tok.prefix(uriFound, UriChars, maxUriLength)) {
+ // else did not find any valid TCHAR
+ debugs(33, 5, "invalid request-line. missing URL");
+ request_parse_status = Http::scBadRequest;
+ return -1;
+ }
- // We are expecting printable ascii characters for method/first word
- if (first_whitespace < 0 && (!xisascii(buf_[i]) || !xisprint(buf_[i]))) {
- request_parse_status = Http::scBadRequest;
- return -1;
- }
+ // we may be at the end if we found exactly maxUriLength bytes
+ if (tok.atEnd()) {
+ debugs(74, 5, "Parser needs more data to find URI");
+ return 0;
}
- if (req.end == -1) {
- // DoS protection against long first-line
- if ((size_t)buf_.length() >= Config.maxRequestHeaderSize) {
- debugs(33, 5, "Too large request-line");
- // RFC 7230 section 3.1.1 mandatory 414 response if URL longer than acceptible.
+ // RFC 1945 - for GET the line terminator may follow URL instead of a delimiter
+ if (method_ == Http::METHOD_GET && skipLineTerminator(tok)) {
+ debugs(33, 5, "HTTP/0.9 syntax request-line detected");
+ msgProtocol_ = Http::ProtocolVersion(0,9);
+ uri_ = uriFound;
+ request_parse_status = Http::scOkay;
+ buf_ = tok.remaining(); // incremental parse checkpoint
+ return 1;
+ }
+
+ // ... followed by at least one whitespace character.
+ if (!tok.skipOne(WspDelim)) {
+ // non-delimiter found after accepted URL bytes means ...
+ if (uriFound.length() == maxUriLength) {
+ // URL longer than acceptible.
+ // RFC 7230 section 3.1.1 mandatory (MUST) 414 response
request_parse_status = Http::scUriTooLong;
+ debugs(33, 5, "invalid request-line. URI longer than " << maxUriLength << " bytes");
+ return -1;
+ } else {
+ // invalid non-delimiter character ended the URL
+ // RFC 7230 section 3.1.1 required (SHOULD) 400 response
+ request_parse_status = Http::scBadRequest;
+ debugs(33, 5, "invalid request-line. missing URI delimiter");
return -1;
}
+ }
+ uri_ = uriFound;
+ buf_ = tok.remaining(); // incremental parse checkpoint
+ return 1;
+}
- debugs(74, 5, "Parser: retval 0: from " << req.start <<
- "->" << req.end << ": needs more data to complete first line.");
+int
+Http::One::RequestParser::parseHttpVersionField(::Parser::Tokenizer &tok)
+{
+ // partial match of HTTP/1 magic prefix
+ if (tok.remaining().length() < Http1magic.length() && Http1magic.startsWith(tok.remaining())) {
+ debugs(74, 5, "Parser needs more data to find version");
return 0;
}
- // NP: we have now seen EOL, more-data (0) cannot occur.
- // From here on any failure is -1, success is 1
-
- // Input Validation:
-
- // DoS protection against long first-line
- if ((size_t)(req.end-req.start) >= Config.maxRequestHeaderSize) {
- debugs(33, 5, "Too large request-line");
- request_parse_status = Http::scUriTooLong;
+ if (!tok.skip(Http1magic)) {
+ debugs(74, 5, "invalid request-line. not HTTP/1 protocol");
+ request_parse_status = Http::scHttpVersionNotSupported;
return -1;
}
- // Process what we now know about the line structure into field offsets
- // generating HTTP status for any aborts as we go.
+ if (tok.atEnd()) {
+ debugs(74, 5, "Parser needs more data to find version");
+ return 0;
+ }
- // First non-whitespace = beginning of method
- if (req.start > line_end) {
- request_parse_status = Http::scBadRequest;
+ // get the version minor DIGIT
+ SBuf digit;
+ if (!tok.prefix(digit, CharacterSet::DIGIT, 1)) {
+ // non-DIGIT. invalid version number.
+ request_parse_status = Http::scHttpVersionNotSupported;
+ debugs(33, 5, "invalid request-line. non-numeric or too-large HTTP minor version");
return -1;
}
- req.m_start = req.start;
- // First whitespace = end of method
- if (first_whitespace > line_end || first_whitespace < req.start) {
- request_parse_status = Http::scBadRequest; // no method
- return -1;
+ if (tok.atEnd()) {
+ debugs(74, 5, "Parser needs more data to find version");
+ return 0;
}
- req.m_end = first_whitespace - 1;
- if (req.m_end < req.m_start) {
- request_parse_status = Http::scBadRequest; // missing URI?
+
+ // version is always followed by the terminator
+ if (!skipLineTerminator(tok)) {
+ if (tok.skipOne(CharacterSet::CR) && tok.atEnd()) {
+ debugs(74, 5, "Parser needs more data to find version");
+ return 0;
+ }
+ request_parse_status = Http::scHttpVersionNotSupported;
+ debugs(33, 5, "invalid request-line. garabge before line terminator");
return -1;
}
- /* Set method_ */
- const SBuf tmp = buf_.substr(req.m_start, req.m_end - req.m_start + 1);
- method_ = HttpRequestMethod(tmp);
+ // found version fully AND terminator
+ msgProtocol_ = Http::ProtocolVersion(1, (*digit.rawContent() - '0'));
+ request_parse_status = Http::scOkay;
+ buf_ = tok.remaining(); // incremental parse checkpoint
+ return 1;
+}
- // First non-whitespace after first SP = beginning of URL+Version
- if (second_word > line_end || second_word < req.start) {
- request_parse_status = Http::scBadRequest; // missing URI
- return -1;
- }
- req.u_start = second_word;
+/**
+ * Attempt to parse the first line of a new request message.
+ *
+ * Governed by:
+ * RFC 1945 section 5.1
+ * RFC 7230 section 2.6, 3.1 and 3.5
+ *
+ * Parsing state is stored between calls. The current implementation uses
+ * checkpoints after each successful request-line field.
+ * The return value tells you whether the parsing is completed or not.
+ *
+ * \retval -1 an error occurred. request_parse_status indicates HTTP status result.
+ * \retval 1 successful parse. member fields contain the request-line items
+ * \retval 0 more data is needed to complete the parse
+ */
+int
+Http::One::RequestParser::parseRequestFirstLine()
+{
+ ::Parser::Tokenizer tok(buf_);
- // RFC 1945: SP and version following URI are optional, marking version 0.9
- // we identify this by the last whitespace being earlier than URI start
- if (last_whitespace < second_word && last_whitespace >= req.start) {
- msgProtocol_ = Http::ProtocolVersion(0,9);
- req.u_end = line_end;
- uri_ = buf_.substr(req.u_start, req.u_end - req.u_start + 1);
- request_parse_status = Http::scOkay; // HTTP/0.9
- return 1;
- } else {
- // otherwise last whitespace is somewhere after end of URI.
- req.u_end = last_whitespace;
- // crop any trailing whitespace in the area we think of as URI
- for (; req.u_end >= req.u_start && xisspace(buf_[req.u_end]); --req.u_end);
- }
- if (req.u_end < req.u_start) {
- request_parse_status = Http::scBadRequest; // missing URI
- return -1;
- }
- uri_ = buf_.substr(req.u_start, req.u_end - req.u_start + 1);
+ debugs(74, 5, "parsing possible request: buf.length=" << buf_.length());
+ debugs(74, DBG_DATA, buf_);
- // Last whitespace SP = before start of protocol/version
- if (last_whitespace >= line_end) {
- request_parse_status = Http::scBadRequest; // missing version
- return -1;
+ CharacterSet WspDelim = CharacterSet::SP; // strict parse only accepts SP
+
+ if (Config.onoff.relaxed_header_parser) {
+ // RFC 7230 section 3.5
+ // tolerant parser MAY accept any of SP, HTAB, VT (%x0B), FF (%x0C), or bare CR
+ // as whitespace between request-line fields
+ WspDelim += CharacterSet::HTAB
+ + CharacterSet("VT,FF","\x0B\x0C")
+ + CharacterSet::CR;
}
- req.v_start = last_whitespace + 1;
- req.v_end = line_end;
- /* RFC 7230 section 2.6 : handle unsupported HTTP major versions cleanly. */
- if ((req.v_end - req.v_start +1) < (int)Http1magic.length() || !buf_.substr(req.v_start, SBuf::npos).startsWith(Http1magic)) {
- // non-HTTP/1 protocols not supported / implemented.
- request_parse_status = Http::scHttpVersionNotSupported;
- return -1;
+ // only search for method if we have not yet found one
+ if (method_ == Http::METHOD_NONE) {
+ const int res = parseMethodField(tok, WspDelim);
+ if (res < 1)
+ return res;
+ // else keep going...
}
- // NP: magic octets include the protocol name and major version DIGIT.
- msgProtocol_.protocol = AnyP::PROTO_HTTP;
- msgProtocol_.major = 1;
- int i = req.v_start + Http1magic.length() -1;
+ // tolerant parser allows multiple whitespace characters between fields
+ if (Config.onoff.relaxed_header_parser) {
+ const size_t garbage = tok.skipAll(WspDelim);
+ if (garbage > 0) {
+ firstLineGarbage_ += garbage;
+ buf_ = tok.remaining(); // re-checkpoint after garbage
+ }
+ }
+ if (tok.atEnd()) {
+ debugs(74, 5, "Parser needs more data");
+ return 0;
+ }
- // catch missing minor part
- if (++i > line_end) {
- request_parse_status = Http::scHttpVersionNotSupported;
- return -1;
+ // only search for request-target (URL) if we have not yet found one
+ if (uri_.isEmpty()) {
+ const int res = parseUriField(tok, WspDelim);
+ if (res < 1 || msgProtocol_.protocol == AnyP::PROTO_HTTP)
+ return res;
+ // else keep going...
}
- /* next should be one or more digits */
- if (!isdigit(buf_[i])) {
- request_parse_status = Http::scHttpVersionNotSupported;
- return -1;
+
+ // tolerant parser allows multiple whitespace characters between fields
+ if (Config.onoff.relaxed_header_parser) {
+ const size_t garbage = tok.skipAll(WspDelim);
+ if (garbage > 0) {
+ firstLineGarbage_ += garbage;
+ buf_ = tok.remaining(); // re-checkpoint after garbage
+ }
}
- int min = 0;
- for (; i <= line_end && (isdigit(buf_[i])) && min < 65536; ++i) {
- min = min * 10;
- min = min + (buf_[i]) - '0';
+ if (tok.atEnd()) {
+ debugs(74, 5, "Parser needs more data");
+ return 0;
}
- // catch too-big values or trailing garbage
- if (min >= 65536 || i < line_end) {
- request_parse_status = Http::scHttpVersionNotSupported;
- return -1;
+
+ // HTTP/1 version suffix (protocol magic) followed by CR*LF
+ if (msgProtocol_.protocol == AnyP::PROTO_NONE) {
+ return parseHttpVersionField(tok);
}
- msgProtocol_.minor = min;
- /*
- * Rightio - we have all the schtuff. Return true; we've got enough.
- */
- request_parse_status = Http::scOkay;
- return 1;
+ // If we got here this method has been called too many times
+ request_parse_status = Http::scInternalServerError;
+ debugs(33, 5, "ERROR: Parser already processed request-line");
+ return -1;
}
bool
// first-line (or a look-alike) found successfully.
if (retcode > 0) {
- buf_.consume(firstLineSize()); // first line bytes including CRLF terminator are now done.
parsingStage_ = HTTP_PARSE_MIME;
}
- debugs(74, 5, "request-line: retval " << retcode << ": from " << req.start << "->" << req.end <<
- " line={" << aBuf.length() << ", data='" << aBuf << "'}");
- debugs(74, 5, "request-line: method " << req.m_start << "->" << req.m_end << " (" << method_ << ")");
- debugs(74, 5, "request-line: url " << req.u_start << "->" << req.u_end << " (" << uri_ << ")");
- debugs(74, 5, "request-line: proto " << req.v_start << "->" << req.v_end << " (" << msgProtocol_ << ")");
+ debugs(74, 5, "request-line: retval " << retcode << ": line={" << aBuf.length() << ", data='" << aBuf << "'}");
+ debugs(74, 5, "request-line: method: " << method_);
+ debugs(74, 5, "request-line: url: " << uri_);
+ debugs(74, 5, "request-line: proto: " << msgProtocol_);
debugs(74, 5, "Parser: bytes processed=" << (aBuf.length()-buf_.length()));
PROF_stop(HttpParserParseReqLine);
#define private public
#define protected public
+#include "Debug.h"
#include "http/one/RequestParser.h"
#include "http/RequestMethod.h"
#include "MemBuf.h"
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;
};
printf("TEST @%d, in=%u: " SQUIDSBUFPH "\n", line, input.length(), SQUIDSBUFPRINT(input));
#endif
+ // runs the parse
CPPUNIT_ASSERT_EQUAL(expect.parsed, output.parse(input));
- CPPUNIT_ASSERT_EQUAL(expect.needsMore, output.needsMoreData());
- if (output.needsMoreData())
- CPPUNIT_ASSERT_EQUAL(expect.parserState, output.parsingStage_);
- CPPUNIT_ASSERT_EQUAL(expect.status, output.request_parse_status);
- CPPUNIT_ASSERT_EQUAL(expect.msgStart, output.req.start);
- CPPUNIT_ASSERT_EQUAL(expect.msgEnd, output.req.end);
- CPPUNIT_ASSERT_EQUAL(expect.suffixSz, output.buf_.length());
- CPPUNIT_ASSERT_EQUAL(expect.methodStart, output.req.m_start);
- CPPUNIT_ASSERT_EQUAL(expect.methodEnd, output.req.m_end);
+
+ // check easily visible field outputs
CPPUNIT_ASSERT_EQUAL(expect.method, output.method_);
- CPPUNIT_ASSERT_EQUAL(expect.uriStart, output.req.u_start);
- CPPUNIT_ASSERT_EQUAL(expect.uriEnd, output.req.u_end);
if (expect.uri != NULL)
CPPUNIT_ASSERT_EQUAL(0, output.uri_.cmp(expect.uri));
- CPPUNIT_ASSERT_EQUAL(expect.versionStart, output.req.v_start);
- CPPUNIT_ASSERT_EQUAL(expect.versionEnd, output.req.v_end);
CPPUNIT_ASSERT_EQUAL(expect.version, output.msgProtocol_);
+ CPPUNIT_ASSERT_EQUAL(expect.status, output.request_parse_status);
+
+ // check more obscure states
+ CPPUNIT_ASSERT_EQUAL(expect.needsMore, output.needsMoreData());
+ if (output.needsMoreData())
+ CPPUNIT_ASSERT_EQUAL(expect.parserState, output.parsingStage_);
+ CPPUNIT_ASSERT_EQUAL(expect.suffixSz, output.buf_.length());
}
#endif /* __cplusplus >= 200103L */
Http1::RequestParser output;
CPPUNIT_ASSERT_EQUAL(true, output.needsMoreData());
CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_NONE, output.parsingStage_);
- CPPUNIT_ASSERT_EQUAL(Http::scNone, output.request_parse_status); // XXX: clear() not being called.
- CPPUNIT_ASSERT_EQUAL(-1, output.req.start);
- CPPUNIT_ASSERT_EQUAL(-1, output.req.end);
+ CPPUNIT_ASSERT_EQUAL(Http::scNone, output.request_parse_status);
CPPUNIT_ASSERT(output.buf_.isEmpty());
- CPPUNIT_ASSERT_EQUAL(-1, output.req.m_start);
- CPPUNIT_ASSERT_EQUAL(-1, output.req.m_end);
CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_NONE), output.method_);
- CPPUNIT_ASSERT_EQUAL(-1, output.req.u_start);
- CPPUNIT_ASSERT_EQUAL(-1, output.req.u_end);
CPPUNIT_ASSERT(output.uri_.isEmpty());
- CPPUNIT_ASSERT_EQUAL(-1, output.req.v_start);
- CPPUNIT_ASSERT_EQUAL(-1, output.req.v_end);
CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
}
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;
}
Http1::RequestParser output;
// TEST: Do we comply with RFC 1945 section 5.1 ?
- // TEST: Do we comply with RFC 2616 section 5.1 ?
+ // TEST: Do we comply with RFC 7230 sections 2.6, 3.1.1 and 3.5 ?
// RFC 1945 : HTTP/0.9 simple-request
{
.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();
}
// RFC 1945 : invalid HTTP/0.9 simple-request (only GET is valid)
-#if WHEN_RFC_COMPLIANT
{
- input.append("POST /\r\n", 7);
+ input.append("POST /\r\n", 8);
struct resultSet expect = {
- .parsed = true,
+ .parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
- .status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
- .suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 3,
+ .status = Http::scBadRequest,
+ .suffixSz = 3,
.method = HttpRequestMethod(Http::METHOD_POST),
- .uriStart = 5,
- .uriEnd = 5,
- .uri = "/",
- .versionStart = -1,
- .versionEnd = -1,
+ .uri = NULL,
.version = AnyP::ProtocolVersion()
};
output.clear();
testResults(__LINE__, input, output, expect);
input.clear();
}
-#endif
- // RFC 1945 and 2616 : HTTP/1.0 request
+
+ // RFC 1945 and 7230 : HTTP/1.0 request
{
input.append("GET / HTTP/1.0\r\n", 16);
struct resultSet expect = {
.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();
input.clear();
}
- // RFC 2616 : HTTP/1.1 request
+ // RFC 7230 : HTTP/1.1 request
{
input.append("GET / HTTP/1.1\r\n", 16);
struct resultSet expect = {
.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();
input.clear();
}
- // RFC 2616 : future version full-request
+ // RFC 7230 : future version full-request
{
input.append("GET / HTTP/1.2\r\n", 16);
struct resultSet expect = {
.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();
// RFC 7230 : future versions do not use request-line syntax
{
+ input.append("GET / HTTP/2.0\r\n", 16);
+ struct resultSet expectA = {
+ .parsed = false,
+ .needsMore = false,
+ .parserState = Http1::HTTP_PARSE_MIME,
+ .status = Http::scHttpVersionNotSupported,
+ .suffixSz = input.length()-6,
+ .method = HttpRequestMethod(Http::METHOD_GET),
+ .uri = "/",
+ .version = AnyP::ProtocolVersion()
+ };
+ output.clear();
+ testResults(__LINE__, input, output, expectA);
+ input.clear();
+
input.append("GET / HTTP/10.12\r\n", 18);
- struct resultSet expect = {
+ struct resultSet expectB = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_MIME,
.status = Http::scHttpVersionNotSupported,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
- .suffixSz = input.length(),
- .methodStart = 0,
- .methodEnd = 2,
+ .suffixSz = input.length()-6,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 6,
- .versionEnd = 15,
.version = AnyP::ProtocolVersion()
};
output.clear();
- testResults(__LINE__, input, output, expect);
+ testResults(__LINE__, input, output, expectB);
input.clear();
}
// unknown non-HTTP protocol names
{
- input.append("GET / FOO/1.0\n", 14);
+ input.append("GET / FOO/1.0\r\n", 15);
struct resultSet expect = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scHttpVersionNotSupported,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
- .suffixSz = input.length(),
- .methodStart = 0,
- .methodEnd = 2,
+ .suffixSz = input.length()-6,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 6,
- .versionEnd = 12,
.version = AnyP::ProtocolVersion()
};
output.clear();
input.clear();
}
- // no version
+ // no version digits
{
- input.append("GET / HTTP/\n", 12);
+ input.append("GET / HTTP/\r\n", 13);
struct resultSet expect = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scHttpVersionNotSupported,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
- .suffixSz = input.length(),
- .methodStart = 0,
- .methodEnd = 2,
+ .suffixSz = input.length()-6,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 6,
- .versionEnd = 10,
.version = AnyP::ProtocolVersion()
};
output.clear();
// no major version
{
- input.append("GET / HTTP/.1\n", 14);
+ input.append("GET / HTTP/.1\r\n", 15);
struct resultSet expect = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scHttpVersionNotSupported,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
- .suffixSz = input.length(),
- .methodStart = 0,
- .methodEnd = 2,
+ .suffixSz = input.length()-6,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 6,
- .versionEnd = 12,
.version = AnyP::ProtocolVersion()
};
output.clear();
// no version dot
{
- input.append("GET / HTTP/11\n", 14);
+ input.append("GET / HTTP/11\r\n", 15);
struct resultSet expect = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scHttpVersionNotSupported,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
- .suffixSz = input.length(),
- .methodStart = 0,
- .methodEnd = 2,
+ .suffixSz = input.length()-6,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 6,
- .versionEnd = 12,
.version = AnyP::ProtocolVersion()
};
output.clear();
// negative major version (bug 3062)
{
- input.append("GET / HTTP/-999999.1\n", 21);
+ input.append("GET / HTTP/-999999.1\r\n", 22);
struct resultSet expect = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scHttpVersionNotSupported,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
- .suffixSz = input.length(),
- .methodStart = 0,
- .methodEnd = 2,
+ .suffixSz = input.length()-6,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 6,
- .versionEnd = 19,
.version = AnyP::ProtocolVersion()
};
output.clear();
// no minor version
{
- input.append("GET / HTTP/1.\n", 14);
+ input.append("GET / HTTP/1.\r\n", 15);
struct resultSet expect = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scHttpVersionNotSupported,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
- .suffixSz = input.length(),
- .methodStart = 0,
- .methodEnd = 2,
+ .suffixSz = input.length()-6,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 6,
- .versionEnd = 12,
- .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,0)
+ .version = AnyP::ProtocolVersion()
};
output.clear();
testResults(__LINE__, input, output, expect);
// negative major version (bug 3062 corollary)
{
- input.append("GET / HTTP/1.-999999\n", 21);
+ input.append("GET / HTTP/1.-999999\r\n", 22);
struct resultSet expect = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scHttpVersionNotSupported,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
- .suffixSz = input.length(),
- .methodStart = 0,
- .methodEnd = 2,
+ .suffixSz = input.length()-6,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 6,
- .versionEnd = 19,
- .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,0)
+ .version = AnyP::ProtocolVersion()
};
output.clear();
testResults(__LINE__, input, output, expect);
// space padded URL
{
input.append("GET / HTTP/1.1\r\n", 21);
+ // when being tolerant extra (sequential) SP delimiters are acceptable
+ Config.onoff.relaxed_header_parser = 1;
struct resultSet expect = {
.parsed = false,
.needsMore = true,
.parserState = Http1::HTTP_PARSE_MIME,
.status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
.suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 2,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 5,
- .uriEnd = 5,
.uri = "/",
- .versionStart = 11,
- .versionEnd = 18,
.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
};
output.clear();
testResults(__LINE__, input, output, expect);
+
+ Config.onoff.relaxed_header_parser = 0;
+ struct resultSet expectStrict = {
+ .parsed = false,
+ .needsMore = false,
+ .parserState = Http1::HTTP_PARSE_DONE,
+ .status = Http::scBadRequest,
+ .suffixSz = input.length()-4,
+ .method = HttpRequestMethod(Http::METHOD_GET),
+ .uri = NULL,
+ .version = AnyP::ProtocolVersion()
+ };
+ output.clear();
+ testResults(__LINE__, input, output, expectStrict);
input.clear();
}
+#if 0 // XXX: RFC compliant parser does not have tolerance for this (yet)
// whitespace inside URI. (nasty but happens)
- // XXX: depends on tolerant parser...
{
- input.append("GET /fo o/ HTTP/1.1\n", 20);
+ input.append("GET /fo o/ HTTP/1.1\r\n", 21);
+ Config.onoff.relaxed_header_parser = 1;
struct resultSet expect = {
.parsed = false,
.needsMore = true,
.parserState = Http1::HTTP_PARSE_MIME,
.status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
.suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 2,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 9,
.uri = "/fo o/",
- .versionStart = 11,
- .versionEnd = 18,
.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
};
output.clear();
testResults(__LINE__, input, output, expect);
+
+ Config.onoff.relaxed_header_parser = 0;
+ struct resultSet expectStrict = {
+ .parsed = false,
+ .needsMore = false,
+ .parserState = Http1::HTTP_PARSE_DONE,
+ .status = Http::scBadRequest,
+ .suffixSz = 0,
+ .method = HttpRequestMethod(Http::METHOD_GET),
+ .uri = NULL,
+ .version = AnyP::ProtocolVersion()
+ };
+ output.clear();
+ testResults(__LINE__, input, output, expectStrict);
input.clear();
}
+#endif
// additional data in buffer
{
- input.append("GET / HTTP/1.1\nboo!", 23);
+ input.append("GET / HTTP/1.1\r\nboo!", 20);
struct resultSet expect = {
.parsed = false,
.needsMore = true,
.parserState = Http1::HTTP_PARSE_MIME,
.status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-5,
.suffixSz = 4, // strlen("boo!")
- .methodStart = 0,
- .methodEnd = 2,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 10,
- .versionEnd = 17,
.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
};
output.clear();
testResults(__LINE__, input, output, expect);
input.clear();
+ Config.onoff.relaxed_header_parser = 0;
}
}
Http1::RequestParser output;
// alternative EOL sequence: NL-only
+ // RFC 7230 tolerance permits omitted CR
{
input.append("GET / HTTP/1.1\n", 15);
+ Config.onoff.relaxed_header_parser = 1;
struct resultSet expect = {
.parsed = false,
.needsMore = true,
.parserState = Http1::HTTP_PARSE_MIME,
.status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
.suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 2,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 6,
- .versionEnd = 13,
.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
};
output.clear();
testResults(__LINE__, input, output, expect);
- input.clear();
- }
- // alternative EOL sequence: double-NL-only
- {
- input.append("GET / HTTP/1.1\n\n", 16);
- struct resultSet expect = {
- .parsed = true,
+ Config.onoff.relaxed_header_parser = 0;
+ struct resultSet expectStrict = {
+ .parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
- .status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-2,
- .suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 2,
+ .status = Http::scHttpVersionNotSupported,
+ .suffixSz = 9,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 6,
- .versionEnd = 13,
- .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+ .version = AnyP::ProtocolVersion()
};
output.clear();
- testResults(__LINE__, input, output, expect);
+ testResults(__LINE__, input, output, expectStrict);
input.clear();
}
- // alternative EOL sequence: multi-CR-NL
+ // alternative EOL sequence: double-NL-only
+ // RFC 7230 tolerance permits omitted CR
+ // NP: represents a request with no mime headers
{
- input.append("GET / HTTP/1.1\r\r\r\n", 18);
- // Being tolerant we can ignore and elide these apparently benign CR
+ input.append("GET / HTTP/1.1\n\n", 16);
Config.onoff.relaxed_header_parser = 1;
- struct resultSet expectRelaxed = {
- .parsed = false,
- .needsMore = true,
- .parserState = Http1::HTTP_PARSE_MIME,
+ struct resultSet expect = {
+ .parsed = true,
+ .needsMore = false,
+ .parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
.suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 2,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 6,
- .versionEnd = 13,
.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
};
output.clear();
- testResults(__LINE__, input, output, expectRelaxed);
+ testResults(__LINE__, input, output, expect);
- // strict mode treats these as several bare-CR in the request line which is explicitly invalid.
Config.onoff.relaxed_header_parser = 0;
struct resultSet expectStrict = {
.parsed = false,
.needsMore = false,
- .parserState = Http1::HTTP_PARSE_MIME,
- .status = Http::scBadRequest,
- .msgStart = 0,
- .msgEnd = -1,
- .suffixSz = input.length(),
- .methodStart =-1,
- .methodEnd = -1,
- .method = HttpRequestMethod(),
- .uriStart = -1,
- .uriEnd = -1,
- .uri = NULL,
- .versionStart = -1,
- .versionEnd = -1,
+ .parserState = Http1::HTTP_PARSE_DONE,
+ .status = Http::scHttpVersionNotSupported,
+ .suffixSz = 10,
+ .method = HttpRequestMethod(Http::METHOD_GET),
+ .uri = "/",
.version = AnyP::ProtocolVersion()
};
output.clear();
// space padded version
{
- // RFC 1945 and 2616 specify version is followed by CRLF. No intermediary bytes.
- // NP: the terminal whitespace is a special case: invalid for even HTTP/0.9 with no version tag
- input.append("GET / HTTP/1.1 \n", 16);
+ // RFC 7230 specifies version is followed by CRLF. No intermediary bytes.
+ input.append("GET / HTTP/1.1 \r\n", 17);
struct resultSet expect = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
- .status = Http::scBadRequest,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
- .suffixSz = input.length(),
- .methodStart = 0,
- .methodEnd = 2,
+ .status = Http::scHttpVersionNotSupported,
+ .suffixSz = input.length()-6,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 13,
- .uri = "/ HTTP/1.1",
- .versionStart = -1,
- .versionEnd = -1,
+ .uri = "/",
.version = AnyP::ProtocolVersion()
};
output.clear();
SBuf input;
Http1::RequestParser output;
- // RFC 2616 : . method
+ // RFC 7230 : dot method
{
- input.append(". / HTTP/1.1\n", 13);
+ input.append(". / HTTP/1.1\r\n", 14);
struct resultSet expect = {
.parsed = false,
.needsMore = true,
.parserState = Http1::HTTP_PARSE_MIME,
.status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
.suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 0,
.method = HttpRequestMethod(SBuf(".")),
- .uriStart = 2,
- .uriEnd = 2,
.uri = "/",
- .versionStart = 4,
- .versionEnd = 11,
+ .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+ };
+ output.clear();
+ testResults(__LINE__, input, output, expect);
+ input.clear();
+ }
+
+ // RFC 7230 : special TCHAR method chars
+ {
+ input.append("!#$%&'*+-.^_`|~ / HTTP/1.1\r\n", 28);
+ struct resultSet expect = {
+ .parsed = false,
+ .needsMore = true,
+ .parserState = Http1::HTTP_PARSE_MIME,
+ .status = Http::scOkay,
+ .suffixSz = 0,
+ .method = HttpRequestMethod(SBuf("!#$%&'*+-.^_`|~")),
+ .uri = "/",
.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
};
output.clear();
// OPTIONS with * URL
{
- input.append("OPTIONS * HTTP/1.1\n", 19);
+ input.append("OPTIONS * HTTP/1.1\r\n", 20);
struct resultSet expect = {
.parsed = false,
.needsMore = true,
.parserState = Http1::HTTP_PARSE_MIME,
.status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
.suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 6,
.method = HttpRequestMethod(Http::METHOD_OPTIONS),
- .uriStart = 8,
- .uriEnd = 8,
.uri = "*",
- .versionStart = 10,
- .versionEnd = 17,
.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
};
output.clear();
// unknown method
{
- input.append("HELLOWORLD / HTTP/1.1\n", 22);
+ input.append("HELLOWORLD / HTTP/1.1\r\n", 23);
struct resultSet expect = {
.parsed = false,
.needsMore = true,
.parserState = Http1::HTTP_PARSE_MIME,
.status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
.suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 9,
.method = HttpRequestMethod(SBuf("HELLOWORLD")),
- .uriStart = 11,
- .uriEnd = 11,
.uri = "/",
- .versionStart = 13,
- .versionEnd = 20,
.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
};
output.clear();
input.clear();
}
+ // too-long method (over 16 bytes)
+ {
+ input.append("HELLOSTRANGEWORLD / HTTP/1.1\r\n", 31);
+ struct resultSet expect = {
+ .parsed = false,
+ .needsMore = false,
+ .parserState = Http1::HTTP_PARSE_DONE,
+ .status = Http::scNotImplemented,
+ .suffixSz = input.length(),
+ .method = HttpRequestMethod(),
+ .uri = NULL,
+ .version = AnyP::ProtocolVersion()
+ };
+ output.clear();
+ testResults(__LINE__, input, output, expect);
+ input.clear();
+ }
+
// method-only
{
input.append("A\n", 2);
.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();
.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();
input.clear();
}
- // space padded method (in strict mode SP is reserved so invalid as a method byte)
+ // space padded method (SP is reserved so invalid as a method byte)
+ {
+ input.append(" GET / HTTP/1.1\r\n", 17);
+ struct resultSet expect = {
+ .parsed = false,
+ .needsMore = false,
+ .parserState = Http1::HTTP_PARSE_DONE,
+ .status = Http::scBadRequest,
+ .suffixSz = input.length(),
+ .method = HttpRequestMethod(),
+ .uri = NULL,
+ .version = AnyP::ProtocolVersion()
+ };
+ output.clear();
+ testResults(__LINE__, input, output, expect);
+ input.clear();
+ }
+
+ // RFC 7230 defined tolerance: ignore empty line(s) prefix on messages
{
- input.append(" GET / HTTP/1.1\n", 16);
- // RELAXED mode Squid custom tolerance ignores SP
-#if USE_HTTP_VIOLATIONS
+ input.append("\r\n\r\n\nGET / HTTP/1.1\r\n", 21);
Config.onoff.relaxed_header_parser = 1;
- struct resultSet expectRelaxed = {
+ struct resultSet expect = {
.parsed = false,
.needsMore = true,
.parserState = Http1::HTTP_PARSE_MIME,
.status = Http::scOkay,
- .msgStart = 0, // garbage collection consumes the SP
- .msgEnd = (int)input.length()-2,
.suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 2,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 6,
- .versionEnd = 13,
.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
};
output.clear();
- testResults(__LINE__, input, output, expectRelaxed);
-#endif
+ testResults(__LINE__, input, output, expect);
- // STRICT mode obeys RFC syntax
Config.onoff.relaxed_header_parser = 0;
struct resultSet expectStrict = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scBadRequest,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
.suffixSz = input.length(),
- .methodStart = 0,
- .methodEnd = -1,
.method = HttpRequestMethod(),
- .uriStart = -1,
- .uriEnd = -1,
.uri = NULL,
- .versionStart = -1,
- .versionEnd = -1,
.version = AnyP::ProtocolVersion()
};
output.clear();
input.clear();
}
- // RFC 2616 defined tolerance: ignore empty line(s) prefix on messages
-#if WHEN_RFC_COMPLIANT
+ // forbidden character in method
{
- input.append("\r\n\r\n\nGET / HTTP/1.1\r\n", 21);
+ input.append("\tGET / HTTP/1.1\r\n", 17);
struct resultSet expect = {
.parsed = false,
.needsMore = false,
+ .parserState = Http1::HTTP_PARSE_DONE,
+ .status = Http::scBadRequest,
+ .suffixSz = input.length(),
+ .method = HttpRequestMethod(),
+ .uri = NULL,
+ .version = AnyP::ProtocolVersion()
+ };
+ output.clear();
+ testResults(__LINE__, input, output, expect);
+ input.clear();
+ }
+
+ // CR in method delimiters
+ {
+ // RFC 7230 section 3.5 permits CR in whitespace but only for tolerant parsers
+ input.append("GET\r / HTTP/1.1\r\n", 17);
+ Config.onoff.relaxed_header_parser = 1;
+ struct resultSet expect = {
+ .parsed = false,
+ .needsMore = true,
.parserState = Http1::HTTP_PARSE_MIME,
.status = Http::scOkay,
- .msgStart = 5,
- .msgEnd = (int)input.length()-1,
- .suffixSz = 5,
- .methodStart = 0,
- .methodEnd = 2,
+ .suffixSz = 0,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 4,
.uri = "/",
- .versionStart = 6,
- .versionEnd = 13,
.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
};
output.clear();
testResults(__LINE__, input, output, expect);
+
+ Config.onoff.relaxed_header_parser = 0;
+ struct resultSet expectStrict = {
+ .parsed = false,
+ .needsMore = false,
+ .parserState = Http1::HTTP_PARSE_DONE,
+ .status = Http::scBadRequest,
+ .suffixSz = input.length(),
+ .method = HttpRequestMethod(),
+ .uri = NULL,
+ .version = AnyP::ProtocolVersion()
+ };
+ output.clear();
+ testResults(__LINE__, input, output, expectStrict);
input.clear();
}
-#endif
- // tab padded method (NP: tab is not SP so treated as any other binary)
+ // tolerant parser delimiters
{
- input.append("\tGET / HTTP/1.1\n", 16);
+ // RFC 7230 section 3.5 permits certain binary characters as whitespace delimiters
+ input.append("GET\r\t\x0B\x0C / HTTP/1.1\r\n", 20);
+ Config.onoff.relaxed_header_parser = 1;
struct resultSet expect = {
+ .parsed = false,
+ .needsMore = true,
+ .parserState = Http1::HTTP_PARSE_MIME,
+ .status = Http::scOkay,
+ .suffixSz = 0,
+ .method = HttpRequestMethod(Http::METHOD_GET),
+ .uri = "/",
+ .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
+ };
+ output.clear();
+ testResults(__LINE__, input, output, expect);
+
+ Config.onoff.relaxed_header_parser = 0;
+ struct resultSet expectStrict = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scBadRequest,
- .msgStart = 0,
- .msgEnd = -1, //Halt on \t, no valid method char
.suffixSz = input.length(),
- .methodStart = -1,
- .methodEnd = -1,
.method = HttpRequestMethod(),
- .uriStart = -1,
- .uriEnd = -1,
.uri = NULL,
- .versionStart = -1,
- .versionEnd = -1,
.version = AnyP::ProtocolVersion()
};
output.clear();
- testResults(__LINE__, input, output, expect);
+ testResults(__LINE__, input, output, expectStrict);
input.clear();
}
}
SBuf input;
Http1::RequestParser output;
- // no method (but in a form which is ambiguous with HTTP/0.9 simple-request)
+ // no method (or method delimiter)
{
- // XXX: HTTP/0.9 requires method to be "GET"
+ // HTTP/0.9 requires method to be "GET"
input.append("/ HTTP/1.0\n", 11);
struct resultSet expect = {
- .parsed = true,
- .needsMore = false,
- .parserState = Http1::HTTP_PARSE_MIME,
- .status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
- .suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 0,
- .method = HttpRequestMethod(SBuf("/")),
- .uriStart = 2,
- .uriEnd = 9,
- .uri = "HTTP/1.0",
- .versionStart = -1,
- .versionEnd = -1,
- .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9)
- };
- output.clear();
- testResults(__LINE__, input, output, expect);
- input.clear();
- }
-
- // no method (an invalid format)
- {
- input.append(" / HTTP/1.0\n", 12);
-#if USE_HTTP_VIOLATIONS
- // squid custom tolerance consumes initial SP.
- Config.onoff.relaxed_header_parser = 1;
- struct resultSet expectRelaxed = {
- .parsed = true,
- .needsMore = false,
- .parserState = Http1::HTTP_PARSE_MIME,
- .status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-2,
- .suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 0,
- .method = HttpRequestMethod(SBuf("/")),
- .uriStart = 2,
- .uriEnd = 9,
- .uri = "HTTP/1.0",
- .versionStart = -1,
- .versionEnd = -1,
- .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9)
- };
- output.clear();
- testResults(__LINE__, input, output, expectRelaxed);
-#endif
-
-#if !USE_HTTP_VIOLATIONS
- // a compliant or strict parse, detects as invalid
- Config.onoff.relaxed_header_parser = 0;
- struct resultSet expectStrict = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scBadRequest,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
.suffixSz = input.length(),
- .methodStart = 0,
- .methodEnd = -1,
.method = HttpRequestMethod(),
- .uriStart = -1,
- .uriEnd = -1,
.uri = NULL,
- .versionStart = -1,
- .versionEnd = -1,
.version = AnyP::ProtocolVersion()
};
-#else
- // XXX: for now Squid confuses this with HTTP/0.9
- struct resultSet expectStrict = {
- .parsed = true,
- .needsMore = false,
- .parserState = Http1::HTTP_PARSE_DONE,
- .status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-2,
- .suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 0,
- .method = HttpRequestMethod(SBuf("/")),
- .uriStart = 2,
- .uriEnd = 9,
- .uri = "HTTP/1.0",
- .versionStart = -1,
- .versionEnd = -1,
- .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9)
- };
-#endif
output.clear();
- testResults(__LINE__, input, output, expectStrict);
+ testResults(__LINE__, input, output, expect);
input.clear();
}
- // binary code in method (invalid)
+ // no method (with method delimiter)
{
- input.append("GET\x0B / HTTP/1.1\n", 16);
- struct resultSet expect = {
+ input.append(" / HTTP/1.0\n", 12);
+ struct resultSet expectStrict = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scBadRequest,
- .msgStart = 0,
- .msgEnd = -1, //Halt on \x0B, no valid method char
.suffixSz = input.length(),
- .methodStart = -1,
- .methodEnd = -1,
.method = HttpRequestMethod(),
- .uriStart = -1,
- .uriEnd = -1,
.uri = NULL,
- .versionStart = -1,
- .versionEnd = -1,
.version = AnyP::ProtocolVersion()
};
output.clear();
- testResults(__LINE__, input, output, expect);
+ testResults(__LINE__, input, output, expectStrict);
input.clear();
}
- // CR in method
+ // binary code in method (invalid)
{
- // RFC 2616 sec 5.1 prohibits CR other than in terminator.
- input.append("GET\r / HTTP/1.1\r\n", 16);
+ input.append("GET\x0A / HTTP/1.1\r\n", 17);
struct resultSet expect = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scBadRequest,
- .msgStart = 0,
- .msgEnd = -1, // halt at the first \r
.suffixSz = input.length(),
- .methodStart = -1,
- .methodEnd = -1,
.method = HttpRequestMethod(),
- .uriStart = -1,
- .uriEnd = -1,
.uri = NULL,
- .versionStart = -1,
- .versionEnd = -1,
.version = AnyP::ProtocolVersion()
};
output.clear();
input.clear();
}
- // binary code NUL! in method (strange but ...)
+ // binary code NUL! in method (always invalid)
{
- input.append("GET\0 / HTTP/1.1\n", 16);
+ input.append("GET\0 / HTTP/1.1\r\n", 17);
struct resultSet expect = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scBadRequest,
- .msgStart = 0,
- .msgEnd = -1, // halt at the \0
.suffixSz = input.length(),
- .methodStart = -1,
- .methodEnd = -1,
.method = HttpRequestMethod(),
- .uriStart = -1,
- .uriEnd = -1,
.uri = NULL,
- .versionStart = -1,
- .versionEnd = -1,
.version = AnyP::ProtocolVersion()
};
output.clear();
// no URL (grammer invalid, ambiguous with RFC 1945 HTTP/0.9 simple-request)
{
- input.append("GET HTTP/1.1\n", 14);
+ input.append("GET HTTP/1.1\r\n", 15);
+ // RFC 7230 tolerance allows sequence of SP to make this ambiguous
+ Config.onoff.relaxed_header_parser = 1;
struct resultSet expect = {
.parsed = true,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
.suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 2,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 5,
- .uriEnd = 12,
.uri = "HTTP/1.1",
- .versionStart = -1,
- .versionEnd = -1,
.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9)
};
output.clear();
testResults(__LINE__, input, output, expect);
+
+ Config.onoff.relaxed_header_parser = 0;
+ struct resultSet expectStrict = {
+ .parsed = false,
+ .needsMore = false,
+ .parserState = Http1::HTTP_PARSE_DONE,
+ .status = Http::scBadRequest,
+ .suffixSz = 11,
+ .method = HttpRequestMethod(Http::METHOD_GET),
+ .uri = NULL,
+ .version = AnyP::ProtocolVersion()
+ };
+ output.clear();
+ testResults(__LINE__, input, output, expectStrict);
input.clear();
}
// no URL (grammer invalid, ambiguous with RFC 1945 HTTP/0.9 simple-request)
{
- input.append("GET HTTP/1.1\n", 13);
+ input.append("GET HTTP/1.1\r\n", 14);
struct resultSet expect = {
.parsed = true,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scOkay,
- .msgStart = 0,
- .msgEnd = (int)input.length()-1,
.suffixSz = 0,
- .methodStart = 0,
- .methodEnd = 2,
.method = HttpRequestMethod(Http::METHOD_GET),
- .uriStart = 4,
- .uriEnd = 11,
.uri = "HTTP/1.1",
- .versionStart = -1,
- .versionEnd = -1,
.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9)
};
output.clear();
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scBadRequest,
- .msgStart = 0,
- .msgEnd = -1, //Halt on \xB, no valid method char
.suffixSz = input.length(),
- .methodStart = -1,
- .methodEnd = -1,
.method = HttpRequestMethod(),
- .uriStart = -1,
- .uriEnd = -1,
.uri = NULL,
- .versionStart = -1,
- .versionEnd = -1,
.version = AnyP::ProtocolVersion()
};
output.clear();
// mixed whitespace line
{
- // We accept non-space binary bytes for method so first \t shows up as that
- // but remaining space and tabs are skipped searching for URI-start
input.append("\t \t \t\n", 6);
struct resultSet expect = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scBadRequest,
- .msgStart = 0,
- .msgEnd = -1, //Halt on \t, no valid method char
.suffixSz = input.length(),
- .methodStart = -1,
- .methodEnd = -1,
.method = HttpRequestMethod(),
- .uriStart = -1,
- .uriEnd = -1,
.uri = NULL,
- .versionStart = -1,
- .versionEnd = -1,
.version = AnyP::ProtocolVersion()
};
output.clear();
input.clear();
}
- // mixed whitespace line with CR middle
+ // mixed whitespace line with CR
{
- // CR aborts on sight, so even initial \t method is not marked as above
- // (not when parsing clean with whole line available anyway)
- input.append("\t \r \n", 6);
+ input.append("\r \t \n", 6);
struct resultSet expect = {
.parsed = false,
.needsMore = false,
.parserState = Http1::HTTP_PARSE_DONE,
.status = Http::scBadRequest,
- .msgStart = 0,
- .msgEnd = -1, // halt on the \r
.suffixSz = input.length(),
- .methodStart = -1,
- .methodEnd = -1,
.method = HttpRequestMethod(),
- .uriStart = -1,
- .uriEnd = -1,
.uri = NULL,
- .versionStart = -1,
- .versionEnd = -1,
.version = AnyP::ProtocolVersion()
};
output.clear();
// calling the parser repeatedly as visible data grows.
SBuf data;
-#if USE_HTTP_VIOLATIONS
- data.append(" ", 12);
-#else
data.append("\n\n\n\n\n\n\n\n\n\n\n\n", 12);
-#endif
SBuf::size_type garbageEnd = data.length();
- data.append("GET http://example.com/ HTTP/1.1\r\n", 34);
+ data.append("GET ", 4);
+ SBuf::size_type methodEnd = data.length()-1;
+ data.append("http://example.com/ ", 20);
+ SBuf::size_type uriEnd = data.length()-1;
+ data.append("HTTP/1.1\r\n", 10);
SBuf::size_type reqLineEnd = data.length() - 1;
data.append("Host: example.com\r\n\r\n", 21);
SBuf::size_type mimeEnd = data.length() - 1;
.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()
};
// when the garbage is passed we expect to start seeing first-line bytes
if (pos == garbageEnd) {
expect.parserState = Http1::HTTP_PARSE_FIRST;
- expect.msgStart = 0;
}
// all points after garbage start to see accumulated bytes looking for end of current section
if (pos >= garbageEnd)
expect.suffixSz = ioBuf.length();
+ // at end of request line expect to see method details
+ if (pos == methodEnd) {
+ expect.suffixSz = 0; // and a checkpoint buffer reset
+ expect.method = HttpRequestMethod(Http::METHOD_GET);
+ }
+
+ // at end of URI expect to see method, URI details
+ if (pos == uriEnd) {
+ expect.suffixSz = 0; // and a checkpoint buffer reset
+ expect.uri = "http://example.com/";
+ }
+
// at end of request line expect to see method, URI, version details
// and switch to seeking Mime header section
if (pos == reqLineEnd) {
expect.parserState = Http1::HTTP_PARSE_MIME;
- expect.suffixSz = 0;
- expect.msgEnd = reqLineEnd-garbageEnd;
+ expect.suffixSz = 0; // and a checkpoint buffer reset
expect.status = Http::scOkay;
- expect.methodStart = 0;
- expect.methodEnd = 2;
expect.method = HttpRequestMethod(Http::METHOD_GET);
- expect.uriStart = 4;
- expect.uriEnd = 22;
expect.uri = "http://example.com/";
- expect.versionStart = 24;
- expect.versionEnd = 31;
expect.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1);
}
if (pos == mimeEnd) {
expect.parsed = true;
expect.needsMore = false;
- expect.suffixSz = 0;
+ expect.suffixSz = 0; // and a checkpoint buffer reset
}
testResults(__LINE__, ioBuf, hp, expect);