]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Add HTTP Response parser
authorAmos Jeffries <squid3@treenet.co.nz>
Sun, 9 Nov 2014 15:02:19 +0000 (07:02 -0800)
committerAmos Jeffries <squid3@treenet.co.nz>
Sun, 9 Nov 2014 15:02:19 +0000 (07:02 -0800)
src/client_side.cc
src/http/one/Makefile.am
src/http/one/Parser.cc
src/http/one/Parser.h
src/http/one/RequestParser.cc
src/http/one/RequestParser.h
src/http/one/ResponseParser.cc [new file with mode: 0644]
src/http/one/ResponseParser.h [new file with mode: 0644]
src/tests/testHttp1Parser.cc

index 34a77c01ec9348f2b8f0473f90cf0d0ffe0c25ca..58a89895fb1a4a3135727fb9d669bbd4223b34d3 100644 (file)
@@ -2049,7 +2049,7 @@ prepareAcceleratedURL(ConnStateData * conn, ClientHttpRequest *http, const Http1
 #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
@@ -2163,7 +2163,7 @@ parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
         }
 
         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");
@@ -2181,13 +2181,13 @@ parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
     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");
     }
 
@@ -2494,7 +2494,7 @@ clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp,
 
             // 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:
@@ -2510,7 +2510,7 @@ clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp,
                 // 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();
index 17ac59068f93bad4f3de22068bf44a0f5a87a689..7c11f5dea24970984ccf3e1fd562992a7219c2cd 100644 (file)
@@ -8,4 +8,6 @@ libhttp1_la_SOURCES = \
        Parser.cc \
        Parser.h \
        RequestParser.cc \
-       RequestParser.h
+       RequestParser.h \
+       ResponseParser.cc \
+       ResponseParser.h
index 48585d438889d1acd2f095cf612858e91961e685..313cc6fd1c2fce9de469191edaa118308b077460 100644 (file)
@@ -9,6 +9,7 @@
 #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
@@ -23,6 +24,44 @@ Http::One::Parser::clear()
     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
 
index 2c9c584b4e81ecd9692a219491f0b0c09a49e2f7..12597920479966efafcf1d1adee586423ecc181a 100644 (file)
@@ -11,6 +11,7 @@
 
 #include "anyp/ProtocolVersion.h"
 #include "http/one/forward.h"
+#include "http/StatusCode.h"
 #include "SBuf.h"
 
 namespace Http {
@@ -37,7 +38,7 @@ class Parser : public RefCountable
 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.
@@ -74,7 +75,7 @@ public:
     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.
      *
@@ -87,7 +88,20 @@ public:
     /// 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;
 
index 64cd6616c5a01ccedebac7613df5b7c84ae257dc..b5ff770746b8bd2ee21d3b48001f0febfc319cb7 100644 (file)
@@ -2,13 +2,11 @@
 #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;
@@ -75,7 +73,7 @@ Http::One::RequestParser::skipGarbageLines()
  * 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
  */
@@ -141,7 +139,7 @@ Http::One::RequestParser::parseRequestFirstLine()
             // 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;
         }
     }
@@ -151,7 +149,7 @@ Http::One::RequestParser::parseRequestFirstLine()
         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;
         }
 
@@ -168,7 +166,7 @@ Http::One::RequestParser::parseRequestFirstLine()
     // 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;
     }
 
@@ -177,19 +175,19 @@ Http::One::RequestParser::parseRequestFirstLine()
 
     // 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;
     }
 
@@ -199,7 +197,7 @@ Http::One::RequestParser::parseRequestFirstLine()
 
     // 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;
@@ -210,7 +208,7 @@ Http::One::RequestParser::parseRequestFirstLine()
         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.
@@ -219,14 +217,14 @@ Http::One::RequestParser::parseRequestFirstLine()
         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;
@@ -235,7 +233,7 @@ Http::One::RequestParser::parseRequestFirstLine()
     /* 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.
@@ -246,12 +244,12 @@ Http::One::RequestParser::parseRequestFirstLine()
 
     // 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;
@@ -261,7 +259,7 @@ Http::One::RequestParser::parseRequestFirstLine()
     }
     // 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;
@@ -269,7 +267,7 @@ Http::One::RequestParser::parseRequestFirstLine()
     /*
      * Rightio - we have all the schtuff. Return true; we've got enough.
      */
-    request_parse_status = Http::scOkay;
+    parseStatusCode = Http::scOkay;
     return 1;
 }
 
@@ -319,35 +317,9 @@ Http::One::RequestParser::parse(const SBuf &aBuf)
     // 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;
         }
     }
index c72b54dc89107b06b0e5619dddb22fe8b9537863..9cad922fdf59ad63c9e266d551a7f9d6fde5f41a 100644 (file)
@@ -3,7 +3,6 @@
 
 #include "http/one/Parser.h"
 #include "http/RequestMethod.h"
-#include "http/StatusCode.h"
 
 namespace Http {
 namespace One {
@@ -33,12 +32,6 @@ public:
     /// 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();
diff --git a/src/http/one/ResponseParser.cc b/src/http/one/ResponseParser.cc
new file mode 100644 (file)
index 0000000..f1138c3
--- /dev/null
@@ -0,0 +1,211 @@
+#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();
+}
diff --git a/src/http/one/ResponseParser.h b/src/http/one/ResponseParser.h
new file mode 100644 (file)
index 0000000..d81f1d4
--- /dev/null
@@ -0,0 +1,57 @@
+#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 */
index 7bfff5ec033aca04eabf245741e622a7ee2bb572..1bf00965563f6091e745005e9ca2fb2b09205f1e 100644 (file)
@@ -69,7 +69,7 @@ testResults(int line, const SBuf &input, Http1::RequestParser &output, struct re
     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());
@@ -93,7 +93,7 @@ testHttp1Parser::testParserConstruct()
         Http1::RequestParser output;
         CPPUNIT_ASSERT_EQUAL(true, output.needsMoreData());
         CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_NONE, output.parsingStage_);
-        CPPUNIT_ASSERT_EQUAL(Http::scNone, output.request_parse_status); // XXX: clear() not being called.
+        CPPUNIT_ASSERT_EQUAL(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());
@@ -113,7 +113,7 @@ testHttp1Parser::testParserConstruct()
         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());