]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/http/one/ResponseParser.cc
3 #include "http/one/ResponseParser.h"
4 #include "http/ProtocolVersion.h"
5 #include "parser/Tokenizer.h"
6 #include "profiler/Profiler.h"
7 #include "SquidConfig.h"
9 const SBuf
Http::One::ResponseParser::IcyMagic("ICY ");
11 Http1::Parser::size_type
12 Http::One::ResponseParser::firstLineSize() const
14 Http1::Parser::size_type result
= 0;
16 switch (msgProtocol_
.protocol
)
18 case AnyP::PROTO_HTTP
:
19 result
+= Http1magic
.length();
22 result
+= IcyMagic
.length();
24 default: // no other protocols supported
27 // NP: the parser does not accept >2 DIGIT for version numbers
28 if (msgProtocol_
.minor
> 9)
33 result
+= 5; /* 5 octets in: SP status SP */
34 result
+= reasonPhrase_
.length();
35 result
+= 2; /* CRLF terminator */
39 // NP: we found the protocol version and consumed it already.
40 // just need the status code and reason phrase
42 Http::One::ResponseParser::parseResponseStatusAndReason(::Parser::Tokenizer
&tok
, const CharacterSet
&WspDelim
)
44 if (!completedStatus_
) {
45 debugs(74, 9, "seek status-code in: " << tok
.remaining().substr(0,10) << "...");
46 /* RFC 7230 section 3.1.2 - status code is 3 DIGIT octets.
47 * There is no limit on what those octets may be.
48 * 000 through 999 are all valid.
51 if (tok
.int64(statusValue
, 10, false, 3) && tok
.skipOne(WspDelim
)) {
53 debugs(74, 6, "found int64 status-code=" << statusValue
);
54 statusCode_
= static_cast<Http::StatusCode
>(statusValue
);
56 buf_
= tok
.remaining(); // resume checkpoint
57 completedStatus_
= true;
59 } else if (tok
.atEnd()) {
60 debugs(74, 6, "Parser needs more data");
61 return 0; // need more to be sure we have it all
64 debugs(74, 6, "invalid status-line. invalid code.");
65 return -1; // invalid status, a single SP terminator required
67 // NOTE: any whitespace after the single SP is part of the reason phrase.
71 return 0; // need more to be sure we have it all
73 /* RFC 7230 says we SHOULD ignore the reason phrase content
74 * but it has a definite valid vs invalid character set.
75 * We interpret the SHOULD as ignoring absence and syntax, but
76 * producing an error if it contains an invalid octet.
79 debugs(74, 9, "seek reason-phrase in: " << tok
.remaining().substr(0,50) << "...");
81 // if we got here we are still looking for reason-phrase bytes
82 static const CharacterSet phraseChars
= CharacterSet::WSP
+ CharacterSet::VCHAR
+ CharacterSet::OBSTEXT
;
83 (void)tok
.prefix(reasonPhrase_
, phraseChars
); // optional, no error if missing
84 if (skipLineTerminator(tok
)) {
85 debugs(74, DBG_DATA
, "parse remaining buf={length=" << tok
.remaining().length() << ", data='" << tok
.remaining() << "'}");
86 buf_
= tok
.remaining(); // resume checkpoint
89 reasonPhrase_
.clear();
92 return 0; // need more to be sure we have it all
94 debugs(74, 6, "invalid status-line. garbage in reason phrase.");
99 * Attempt to parse the method field out of an HTTP message status-line.
102 * RFC 1945 section 6.1
103 * RFC 7230 section 2.6, 3.1 and 3.5
105 * Parsing state is stored between calls. The current implementation uses
106 * checkpoints after each successful status-line field.
107 * The return value tells you whether the parsing is completed or not.
109 * \retval -1 an error occurred.
110 * \retval 1 successful parse. statusCode_ and maybe reasonPhrase_ are filled and buffer consumed including first delimiter.
111 * \retval 0 more data is needed to complete the parse
114 Http::One::ResponseParser::parseResponseFirstLine()
116 ::Parser::Tokenizer
tok(buf_
);
118 CharacterSet WspDelim
= CharacterSet::SP
; // strict parse only accepts SP
120 if (Config
.onoff
.relaxed_header_parser
) {
121 // RFC 7230 section 3.5
122 // tolerant parser MAY accept any of SP, HTAB, VT (%x0B), FF (%x0C), or bare CR
123 // as whitespace between status-line fields
124 WspDelim
+= CharacterSet::HTAB
125 + CharacterSet("VT,FF","\x0B\x0C")
129 if (msgProtocol_
.protocol
!= AnyP::PROTO_NONE
) {
130 debugs(74, 6, "continue incremental parse for " << msgProtocol_
);
131 debugs(74, DBG_DATA
, "parse remaining buf={length=" << tok
.remaining().length() << ", data='" << tok
.remaining() << "'}");
132 // we already found the magic, but not the full line. keep going.
133 return parseResponseStatusAndReason(tok
, WspDelim
);
135 } else if (tok
.skip(Http1magic
)) {
136 debugs(74, 6, "found prefix magic " << Http1magic
);
137 // HTTP Response status-line parse
139 // magic contains major version, still need to find minor DIGIT
141 if (tok
.int64(verMinor
, 10, false, 1) && tok
.skipOne(WspDelim
)) {
142 msgProtocol_
.protocol
= AnyP::PROTO_HTTP
;
143 msgProtocol_
.major
= 1;
144 msgProtocol_
.minor
= static_cast<unsigned int>(verMinor
);
146 debugs(74, 6, "found version=" << msgProtocol_
);
148 debugs(74, DBG_DATA
, "parse remaining buf={length=" << tok
.remaining().length() << ", data='" << tok
.remaining() << "'}");
149 buf_
= tok
.remaining(); // resume checkpoint
150 return parseResponseStatusAndReason(tok
, WspDelim
);
152 } else if (tok
.atEnd())
153 return 0; // need more to be sure we have it all
155 return -1; // invalid version or delimiter, a single SP terminator required
157 } else if (tok
.skip(IcyMagic
)) {
158 debugs(74, 6, "found prefix magic " << IcyMagic
);
159 // ICY Response status-line parse (same as HTTP/1 after the magic version)
160 msgProtocol_
.protocol
= AnyP::PROTO_ICY
;
161 // NP: ICY has no /major.minor details
162 debugs(74, DBG_DATA
, "parse remaining buf={length=" << tok
.remaining().length() << ", data='" << tok
.remaining() << "'}");
163 buf_
= tok
.remaining(); // resume checkpoint
164 return parseResponseStatusAndReason(tok
, WspDelim
);
166 } else if (buf_
.length() > Http1magic
.length() && buf_
.length() > IcyMagic
.length()) {
167 debugs(74, 2, "unknown/missing prefix magic. Interpreting as HTTP/0.9");
168 // found something that looks like an HTTP/0.9 response
169 // Gateway/Transform it into HTTP/1.1
170 msgProtocol_
= Http::ProtocolVersion(1,1);
171 // XXX: probably should use version 0.9 here and upgrade on output,
172 // but the old code did 1.1 transformation now.
173 statusCode_
= Http::scOkay
;
174 static const SBuf
gatewayPhrase("Gatewaying");
175 reasonPhrase_
= gatewayPhrase
;
176 static const SBuf
fakeHttpMimeBlock("X-Transformed-From: HTTP/0.9\r\n"
177 /* Server: visible_appname_string */
178 "Mime-Version: 1.0\r\n"
179 /* Date: squid_curtime */
180 "Expires: -1\r\n\r\n");
181 mimeHeaderBlock_
= fakeHttpMimeBlock
;
182 parsingStage_
= HTTP_PARSE_DONE
;
183 return 1; // no more parsing
186 return 0; // need more to parse anything.
190 Http::One::ResponseParser::parse(const SBuf
&aBuf
)
193 debugs(74, DBG_DATA
, "Parse buf={length=" << aBuf
.length() << ", data='" << aBuf
<< "'}");
195 // stage 1: locate the status-line
196 if (parsingStage_
== HTTP_PARSE_NONE
) {
197 // RFC 7230 explicitly states whether garbage whitespace is to be handled
198 // at each point of the message framing boundaries.
199 // It omits mentioning garbage prior to HTTP Responses.
200 // Therefore, if we receive anything at all treat it as Response message.
202 parsingStage_
= HTTP_PARSE_FIRST
;
207 // stage 2: parse the status-line
208 if (parsingStage_
== HTTP_PARSE_FIRST
) {
209 PROF_start(HttpParserParseReplyLine
);
211 int retcode
= parseResponseFirstLine();
213 // first-line (or a look-alike) found successfully.
215 parsingStage_
= HTTP_PARSE_MIME
;
216 debugs(74, 5, "status-line: retval " << retcode
);
217 debugs(74, 5, "status-line: proto " << msgProtocol_
);
218 debugs(74, 5, "status-line: status-code " << statusCode_
);
219 debugs(74, 5, "status-line: reason-phrase " << reasonPhrase_
);
220 debugs(74, 5, "Parser: bytes processed=" << (aBuf
.length()-buf_
.length()));
221 PROF_stop(HttpParserParseReplyLine
);
223 // syntax errors already
225 parsingStage_
= HTTP_PARSE_DONE
;
226 statusCode_
= Http::scInvalidHeader
;
231 // stage 3: locate the mime header block
232 if (parsingStage_
== HTTP_PARSE_MIME
) {
233 if (!findMimeBlock("Response", Config
.maxReplyHeaderSize
))
237 return !needsMoreData();