#if SHOULD_REJECT_UNKNOWN_URLS
// reject URI which are not well-formed even after the processing above
if (url.isEmpty() || url[0] != '/') {
- hp->request_parse_status = Http::scBadRequest;
+ hp->parseStatusCode = Http::scBadRequest;
return conn->abortRequestParsing("error:invalid-request");
}
#endif
}
if (!parsedOk) {
- if (hp->request_parse_status == Http::scRequestHeaderFieldsTooLarge || hp->request_parse_status == Http::scUriTooLong)
+ if (hp->parseStatusCode == Http::scRequestHeaderFieldsTooLarge || hp->parseStatusCode == Http::scUriTooLong)
return csd->abortRequestParsing("error:request-too-large");
return csd->abortRequestParsing("error:invalid-request");
if (hp->method() == Http::METHOD_CONNECT && csd->port != NULL && csd->port->flags.accelSurrogate) {
debugs(33, DBG_IMPORTANT, "WARNING: CONNECT method received on " << csd->transferProtocol << " Accelerator port " << csd->port->s.port());
debugs(33, DBG_IMPORTANT, "WARNING: for request: " << hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol());
- hp->request_parse_status = Http::scMethodNotAllowed;
+ hp->parseStatusCode = Http::scMethodNotAllowed;
return csd->abortRequestParsing("error:method-not-allowed");
}
if (hp->method() == Http::METHOD_NONE) {
debugs(33, DBG_IMPORTANT, "WARNING: Unsupported method: " << hp->method() << " " << hp->requestUri() << " " << hp->messageProtocol());
- hp->request_parse_status = Http::scMethodNotAllowed;
+ hp->parseStatusCode = Http::scMethodNotAllowed;
return csd->abortRequestParsing("error:unsupported-request-method");
}
// determine which error page templates to use for specific parsing errors
err_type errPage = ERR_INVALID_REQ;
- switch (hp->request_parse_status) {
+ switch (hp->parseStatusCode) {
case Http::scRequestHeaderFieldsTooLarge:
// fall through to next case
case Http::scUriTooLong:
// use default ERR_INVALID_REQ set above.
break;
}
- repContext->setReplyToError(errPage, hp->request_parse_status, hp->method(), http->uri,
+ repContext->setReplyToError(errPage, hp->parseStatusCode, hp->method(), http->uri,
conn->clientConnection->remote, NULL, conn->in.buf.c_str(), NULL);
assert(context->http->out.offset == 0);
context->pullData();
Parser.cc \
Parser.h \
RequestParser.cc \
- RequestParser.h
+ RequestParser.h \
+ ResponseParser.cc \
+ ResponseParser.h
#include "squid.h"
#include "Debug.h"
#include "http/one/Parser.h"
+#include "mime_header.h"
#include "parser/Tokenizer.h"
/// RFC 7230 section 2.6 - 7 magic octets
mimeHeaderBlock_.clear();
}
+bool
+Http::One::Parser::findMimeBlock(const char *which, size_t limit)
+{
+ if (msgProtocol_.major == 1) {
+ /* NOTE: HTTP/0.9 messages do not have a mime header block.
+ * So the rest of the code will need to deal with '0'-byte headers
+ * (ie, none, so don't try parsing em)
+ */
+ int64_t mimeHeaderBytes = 0;
+ // XXX: c_str() reallocates. performance regression.
+ if ((mimeHeaderBytes = headersEnd(buf_.c_str(), buf_.length())) == 0) {
+ if (buf_.length()+firstLineSize() >= limit) {
+ debugs(33, 5, "Too large " << which);
+ parseStatusCode = Http::scHeaderTooLarge;
+ parsingStage_ = HTTP_PARSE_DONE;
+ } else
+ debugs(33, 5, "Incomplete " << which << ", waiting for end of headers");
+ return false;
+ }
+ mimeHeaderBlock_ = buf_.consume(mimeHeaderBytes);
+ debugs(74, 5, "mime header (0-" << mimeHeaderBytes << ") {" << mimeHeaderBlock_ << "}");
+
+ } else
+ debugs(33, 3, "Missing HTTP/1.x identifier");
+
+ // NP: we do not do any further stages here yet so go straight to DONE
+ parsingStage_ = HTTP_PARSE_DONE;
+
+ // Squid could handle these headers, but admin does not want to
+ if (messageHeaderSize() >= limit) {
+ debugs(33, 5, "Too large " << which);
+ parseStatusCode = Http::scHeaderTooLarge;
+ return false;
+ }
+
+ return true;
+}
+
// arbitrary maximum-length for headers which can be found by Http1Parser::getHeaderField()
#define GET_HDR_SZ 1024
#include "anyp/ProtocolVersion.h"
#include "http/one/forward.h"
+#include "http/StatusCode.h"
#include "SBuf.h"
namespace Http {
public:
typedef SBuf::size_type size_type;
- Parser() : parsingStage_(HTTP_PARSE_NONE) {}
+ Parser() : parseStatusCode(Http::scNone), parsingStage_(HTTP_PARSE_NONE) {}
virtual ~Parser() {}
/// Set this parser back to a default state.
const AnyP::ProtocolVersion & messageProtocol() const {return msgProtocol_;}
/**
- * Scan the mime header block (badly) for a header with teh given name.
+ * Scan the mime header block (badly) for a header with the given name.
*
* BUG: omits lines when searching for headers with obs-fold or multiple entries.
*
/// the remaining unprocessed section of buffer
const SBuf &remaining() const {return buf_;}
+ /**
+ * HTTP status code resulting from the parse process.
+ * to be used on the invalid message handling.
+ *
+ * Http::scNone indicates incomplete parse,
+ * Http::scOkay indicates no error,
+ * other codes represent a parse error.
+ */
+ Http::StatusCode parseStatusCode;
+
protected:
+ /// parse scan to find the mime headers block for current message
+ bool findMimeBlock(const char *which, size_t limit);
+
/// RFC 7230 section 2.6 - 7 magic octets
static const SBuf Http1magic;
#include "Debug.h"
#include "http/one/RequestParser.h"
#include "http/ProtocolVersion.h"
-#include "mime_header.h"
#include "profiler/Profiler.h"
#include "SquidConfig.h"
Http::One::RequestParser::RequestParser() :
- Parser(),
- request_parse_status(Http::scNone)
+ Parser()
{
req.start = req.end = -1;
req.m_start = req.m_end = -1;
* begins parsing from scratch on every call.
* The return value tells you whether the parsing state fields are valid or not.
*
- * \retval -1 an error occurred. request_parse_status indicates HTTP status result.
+ * \retval -1 an error occurred. parseStatusCode 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
*/
// 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;
+ parseStatusCode = Http::scBadRequest;
return -1;
}
}
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.
- request_parse_status = Http::scUriTooLong;
+ parseStatusCode = Http::scUriTooLong;
return -1;
}
// 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;
+ parseStatusCode = Http::scUriTooLong;
return -1;
}
// First non-whitespace = beginning of method
if (req.start > line_end) {
- request_parse_status = Http::scBadRequest;
+ parseStatusCode = Http::scBadRequest;
return -1;
}
req.m_start = req.start;
// First whitespace = end of method
if (first_whitespace > line_end || first_whitespace < req.start) {
- request_parse_status = Http::scBadRequest; // no method
+ parseStatusCode = Http::scBadRequest; // no method
return -1;
}
req.m_end = first_whitespace - 1;
if (req.m_end < req.m_start) {
- request_parse_status = Http::scBadRequest; // missing URI?
+ parseStatusCode = Http::scBadRequest; // missing URI?
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
+ parseStatusCode = Http::scBadRequest; // missing URI
return -1;
}
req.u_start = second_word;
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
+ parseStatusCode = Http::scOkay; // HTTP/0.9
return 1;
} else {
// otherwise last whitespace is somewhere after end of 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
+ parseStatusCode = Http::scBadRequest; // missing URI
return -1;
}
uri_ = buf_.substr(req.u_start, req.u_end - req.u_start + 1);
// Last whitespace SP = before start of protocol/version
if (last_whitespace >= line_end) {
- request_parse_status = Http::scBadRequest; // missing version
+ parseStatusCode = Http::scBadRequest; // missing version
return -1;
}
req.v_start = last_whitespace + 1;
/* 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;
+ parseStatusCode = Http::scHttpVersionNotSupported;
return -1;
}
// NP: magic octets include the protocol name and major version DIGIT.
// catch missing minor part
if (++i > line_end) {
- request_parse_status = Http::scHttpVersionNotSupported;
+ parseStatusCode = Http::scHttpVersionNotSupported;
return -1;
}
/* next should be one or more digits */
if (!isdigit(buf_[i])) {
- request_parse_status = Http::scHttpVersionNotSupported;
+ parseStatusCode = Http::scHttpVersionNotSupported;
return -1;
}
int min = 0;
}
// catch too-big values or trailing garbage
if (min >= 65536 || i < line_end) {
- request_parse_status = Http::scHttpVersionNotSupported;
+ parseStatusCode = Http::scHttpVersionNotSupported;
return -1;
}
msgProtocol_.minor = min;
/*
* Rightio - we have all the schtuff. Return true; we've got enough.
*/
- request_parse_status = Http::scOkay;
+ parseStatusCode = Http::scOkay;
return 1;
}
// stage 3: locate the mime header block
if (parsingStage_ == HTTP_PARSE_MIME) {
// HTTP/1.x request-line is valid and parsing completed.
- if (msgProtocol_.major == 1) {
- /* NOTE: HTTP/0.9 requests do not have a mime header block.
- * So the rest of the code will need to deal with '0'-byte headers
- * (ie, none, so don't try parsing em)
- */
- int64_t mimeHeaderBytes = 0;
- // XXX: c_str() reallocates. performance regression.
- if ((mimeHeaderBytes = headersEnd(buf_.c_str(), buf_.length())) == 0) {
- if (buf_.length()+firstLineSize() >= Config.maxRequestHeaderSize) {
- debugs(33, 5, "Too large request");
- request_parse_status = Http::scRequestHeaderFieldsTooLarge;
- parsingStage_ = HTTP_PARSE_DONE;
- } else
- debugs(33, 5, "Incomplete request, waiting for end of headers");
- return false;
- }
- mimeHeaderBlock_ = buf_.consume(mimeHeaderBytes);
- debugs(74, 5, "mime header (0-" << mimeHeaderBytes << ") {" << mimeHeaderBlock_ << "}");
-
- } else
- debugs(33, 3, "Missing HTTP/1.x identifier");
-
- // NP: we do not do any further stages here yet so go straight to DONE
- parsingStage_ = HTTP_PARSE_DONE;
-
- // Squid could handle these headers, but admin does not want to
- if (messageHeaderSize() >= Config.maxRequestHeaderSize) {
- debugs(33, 5, "Too large request");
- request_parse_status = Http::scRequestHeaderFieldsTooLarge;
+ if (!findMimeBlock("Request", Config.maxRequestHeaderSize)) {
+ if (parseStatusCode == Http::scHeaderTooLarge)
+ parseStatusCode = Http::scRequestHeaderFieldsTooLarge;
return false;
}
}
#include "http/one/Parser.h"
#include "http/RequestMethod.h"
-#include "http/StatusCode.h"
namespace Http {
namespace One {
/// the request-line URI if this is a request message, or an empty string.
const SBuf &requestUri() const {return uri_;}
- /** HTTP status code to be used on the invalid-request error page.
- * Http::scNone indicates incomplete parse,
- * Http::scOkay indicates no error.
- */
- Http::StatusCode request_parse_status;
-
private:
void skipGarbageLines();
int parseRequestFirstLine();
--- /dev/null
+#include "squid.h"
+#include "Debug.h"
+#include "http/one/ResponseParser.h"
+#include "http/ProtocolVersion.h"
+#include "parser/Tokenizer.h"
+#include "profiler/Profiler.h"
+#include "SquidConfig.h"
+
+const SBuf Http::One::ResponseParser::IcyMagic("ICY ");
+
+Http1::Parser::size_type
+Http::One::ResponseParser::firstLineSize() const
+{
+ Http1::Parser::size_type result = 0;
+
+ switch (msgProtocol_.protocol)
+ {
+ case AnyP::PROTO_HTTP:
+ result += Http1magic.length();
+ break;
+ case AnyP::PROTO_ICY:
+ result += IcyMagic.length();
+ break;
+ default: // no other protocols supported
+ return result;
+ }
+ // NP: the parser does not accept >2 DIGIT for version numbers
+ if (msgProtocol_.minor >10)
+ result += 2;
+ else
+ result += 1;
+
+ result += 5; /* 5 octets in: SP status SP */
+ result += reasonPhrase_.length();
+ return result;
+}
+
+// NP: we found the protocol version and consumed it already.
+// just need the status code and reason phrase
+const int
+Http::One::ResponseParser::parseResponseStatusAndReason()
+{
+ if (buf_.isEmpty())
+ return 0;
+
+ ::Parser::Tokenizer tok(buf_);
+
+ if (!completedStatus_) {
+ SBuf status;
+ // status code is 3 DIGIT octets
+ if(!tok.prefix(status, CharacterSet::DIGIT, 3))
+ return -1; // invalid status
+ // NOTE: multiple SP or non-SP bytes between version and status code are invalid.
+ if (tok.atEnd())
+ return 0; // need more to be sure we have it all
+ if(!tok.skip(' '))
+ return -1; // invalid status, a single SP terminator required
+ // NOTE: any whitespace after the single SP is part of the reason phrase.
+
+ // get the actual numeric value of the 0-3 digits we found
+ ::Parser::Tokenizer t2(status);
+ int64_t statusValue;
+ if (!t2.int64(statusValue))
+ return -1; // ouch. digits not forming a valid number?
+ if (statusValue < 0 || statusValue > 999)
+ return -1; // ouch. digits not within valid status code range.
+
+ statusCode_ = static_cast<Http::StatusCode>(statusValue);
+
+ buf_ = tok.remaining(); // resume checkpoint
+ completedStatus_ = true;
+ }
+
+ if (tok.atEnd())
+ return 0; // need more to be sure we have it all
+
+ /* RFC 7230 says we SHOULD ignore the reason phrase content
+ * but it has a definite valid vs invalid character set.
+ * We interpret the SHOULD as ignoring absence and syntax, but
+ * producing an error if it contains an invalid octet.
+ */
+
+ // if we got here we are still looking for reason-phrase bytes
+ static const CharacterSet phraseChars = CharacterSet::WSP + CharacterSet::VCHAR + CharacterSet::OBSTEXT;
+ tok.prefix(reasonPhrase_, phraseChars); // optional, no error if missing
+ tok.skip('\r'); // optional trailing CR
+
+ if (tok.atEnd())
+ return 0; // need more to be sure we have it all
+
+ // LF existence matters
+ if (!tok.skip('\n')) {
+ reasonPhrase_.clear();
+ return -1; // found invalid characters in the phrase
+ }
+
+ buf_ = tok.remaining(); // resume checkpoint
+ return 1;
+}
+
+const int
+Http::One::ResponseParser::parseResponseFirstLine()
+{
+ ::Parser::Tokenizer tok(buf_);
+
+ if (msgProtocol_.protocol != AnyP::PROTO_NONE) {
+ // we already found the magic, but not the full line. keep going.
+ return parseResponseStatusAndReason();
+
+ } else if (tok.skip(Http1magic)) {
+ // HTTP Response status-line parse
+
+ // magic contains major version, still need to find minor
+ SBuf verMinor;
+ // NP: we limit to 2-digits for speed, there really is no limit
+ // XXX: the protocols we accept dont have valid versions > 10 anyway
+ if (!tok.prefix(verMinor, CharacterSet::DIGIT, 2))
+ return -1; // invalid version minor code
+ if (tok.atEnd())
+ return 0; // need more to be sure we have it all
+ if(!tok.skip(' '))
+ return -1; // invalid version, a single SP terminator required
+
+ // get the actual numeric value of the 0-3 digits we found
+ ::Parser::Tokenizer t2(verMinor);
+ int64_t tvm = 0;
+ if (!t2.int64(tvm))
+ return -1; // ouch. digits not forming a valid number?
+ msgProtocol_.minor = static_cast<unsigned int>(tvm);
+
+ msgProtocol_.protocol = AnyP::PROTO_HTTP;
+ msgProtocol_.major = 1;
+ buf_ = tok.remaining(); // resume checkpoint
+ return parseResponseStatusAndReason();
+
+ } else if (tok.skip(IcyMagic)) {
+ // ICY Response status-line parse (same as HTTP/1 after the magic version)
+ msgProtocol_.protocol = AnyP::PROTO_ICY;
+ // NP: ICY has no /major.minor details
+ buf_ = tok.remaining(); // resume checkpoint
+ return parseResponseStatusAndReason();
+
+ } else if (buf_.length() > Http1magic.length() && buf_.length() > IcyMagic.length()) {
+ // found something that looks like an HTTP/0.9 response
+ msgProtocol_ = Http::ProtocolVersion(1,1);
+ // XXX: probably should use version 0.9 here and upgrade on output,
+ // but the old code did 1.1 transformation now.
+ statusCode_ = Http::scOkay;
+ static const SBuf gatewayPhrase("Gatewaying");
+ reasonPhrase_ = gatewayPhrase;
+ static const SBuf fakeHttpMimeBlock("X-Transformed-From: HTTP/0.9\r\n"
+ /* Server: visible_appname_string */
+ "Mime-Version: 1.0\r\n"
+ /* Date: squid_curtime */
+ "Expires: -1\r\n\r\n");
+ return 1; // no more parsing
+ }
+
+ return 0; // need more to parse anything.
+}
+
+bool
+Http::One::ResponseParser::parse(const SBuf &aBuf)
+{
+ buf_ = aBuf;
+ debugs(74, DBG_DATA, "Parse buf={length=" << aBuf.length() << ", data='" << aBuf << "'}");
+
+ // stage 1: locate the status-line
+ if (parsingStage_ == HTTP_PARSE_NONE) {
+ // RFC 7230 explicitly states whether garbage whitespace is to be handled
+ // at each point of the message framing boundaries.
+ // It omits mentioning garbage prior to HTTP Responses.
+ // Therefore, if we receive anything at all treat it as Response message.
+ if (!buf_.isEmpty())
+ parsingStage_ = HTTP_PARSE_FIRST;
+ else
+ return false;
+ }
+
+ // stage 2: parse the status-line
+ if (parsingStage_ == HTTP_PARSE_FIRST) {
+ PROF_start(HttpParserParseReplyLine);
+
+ int retcode = parseResponseFirstLine();
+
+ // first-line (or a look-alike) found successfully.
+ if (retcode > 0)
+ parsingStage_ = HTTP_PARSE_MIME;
+ debugs(74, 5, "status-line: retval " << retcode);
+ debugs(74, 5, "status-line: proto " << msgProtocol_);
+ debugs(74, 5, "status-line: status-code " << statusCode_);
+ debugs(74, 5, "status-line: reason-phrase " << reasonPhrase_);
+ debugs(74, 5, "Parser: bytes processed=" << (aBuf.length()-buf_.length()));
+ PROF_stop(HttpParserParseReplyLine);
+
+ // syntax errors already
+ if (retcode < 0) {
+ parsingStage_ = HTTP_PARSE_DONE;
+ statusCode_ = scInvalidHeader;
+ return false;
+ }
+ }
+
+ // stage 3: locate the mime header block
+ if (parsingStage_ == HTTP_PARSE_MIME) {
+ if (!findMimeBlock("Response", Config.maxReplyHeaderSize))
+ return false;
+ }
+
+ return !needsMoreData();
+}
--- /dev/null
+#ifndef _SQUID_SRC_HTTP_ONE_RESPONSEPARSER_H
+#define _SQUID_SRC_HTTP_ONE_RESPONSEPARSER_H
+
+#include "http/one/Parser.h"
+#include "http/StatusCode.h"
+
+namespace Http {
+namespace One {
+
+/** HTTP/1.x protocol response parser
+ *
+ * Also capable of parsing unexpected ICY responses and
+ * upgrading HTTP/0.9 syntax responses to HTTP/1.1
+ *
+ * Works on a raw character I/O buffer and tokenizes the content into
+ * the major CRLF delimited segments of an HTTP/1 respone message:
+ *
+ * \item status-line (version SP status SP reash-phrase)
+ * \item mime-header (set of RFC2616 syntax header fields)
+ */
+class ResponseParser : public Http1::Parser
+{
+public:
+ ResponseParser() : Parser(), completedStatus_(false) {}
+ virtual ~ResponseParser() {}
+
+ /* Http::One::Parser API */
+ virtual void clear() {*this=ResponseParser();}
+ virtual Http1::Parser::size_type firstLineSize() const;
+ virtual bool parse(const SBuf &aBuf);
+
+ /* respone specific fields, read-only */
+ Http::StatusCode messageStatus() const { return statusCode_;}
+ SBuf reasonPhrase() const { return reasonPhrase_;}
+
+private:
+ const int parseResponseFirstLine();
+ const int parseResponseStatusAndReason();
+
+ /// magic prefix for identifying ICY response messages
+ static const SBuf IcyMagic;
+
+ /// Whether we found the status code yet.
+ /// We cannot rely on status value because server may send "000".
+ bool completedStatus_;
+
+ /// HTTP/1 status-line status code
+ Http::StatusCode statusCode_;
+
+ /// HTTP/1 status-line reason phrase
+ SBuf reasonPhrase_;
+};
+
+} // namespace One
+} // namespace Http
+
+#endif /* _SQUID_SRC_HTTP_ONE_RESPONSEPARSER_H */
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.status, output.parseStatusCode);
CPPUNIT_ASSERT_EQUAL(expect.msgStart, output.req.start);
CPPUNIT_ASSERT_EQUAL(expect.msgEnd, output.req.end);
CPPUNIT_ASSERT_EQUAL(expect.suffixSz, output.buf_.length());
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(Http::scNone, output.parseStatusCode); // XXX: clear() not being called.
CPPUNIT_ASSERT_EQUAL(-1, output.req.start);
CPPUNIT_ASSERT_EQUAL(-1, output.req.end);
CPPUNIT_ASSERT(output.buf_.isEmpty());
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(Http::scNone, output->parseStatusCode);
CPPUNIT_ASSERT_EQUAL(-1, output->req.start);
CPPUNIT_ASSERT_EQUAL(-1, output->req.end);
CPPUNIT_ASSERT(output->buf_.isEmpty());