]> git.ipfire.org Git - thirdparty/squid.git/blame - src/http/one/ResponseParser.cc
Source Format Enforcement (#532)
[thirdparty/squid.git] / src / http / one / ResponseParser.cc
CommitLineData
ad20e647 1/*
77b1029d 2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
ad20e647
AJ
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
f1d5359e
AJ
9#include "squid.h"
10#include "Debug.h"
11#include "http/one/ResponseParser.h"
12#include "http/ProtocolVersion.h"
417da400 13#include "parser/Tokenizer.h"
f1d5359e
AJ
14#include "profiler/Profiler.h"
15#include "SquidConfig.h"
16
17const SBuf Http::One::ResponseParser::IcyMagic("ICY ");
18
19Http1::Parser::size_type
20Http::One::ResponseParser::firstLineSize() const
21{
22 Http1::Parser::size_type result = 0;
23
24 switch (msgProtocol_.protocol)
25 {
26 case AnyP::PROTO_HTTP:
27 result += Http1magic.length();
28 break;
29 case AnyP::PROTO_ICY:
30 result += IcyMagic.length();
31 break;
32 default: // no other protocols supported
33 return result;
34 }
35 // NP: the parser does not accept >2 DIGIT for version numbers
b8f86fd2 36 if (msgProtocol_.minor > 9)
f1d5359e
AJ
37 result += 2;
38 else
39 result += 1;
40
41 result += 5; /* 5 octets in: SP status SP */
42 result += reasonPhrase_.length();
05f32cc2 43 result += 2; /* CRLF terminator */
f1d5359e
AJ
44 return result;
45}
46
47// NP: we found the protocol version and consumed it already.
48// just need the status code and reason phrase
5aea71e7 49int
417da400 50Http::One::ResponseParser::parseResponseStatusAndReason(Tokenizer &tok, const CharacterSet &WspDelim)
f1d5359e 51{
f1d5359e 52 if (!completedStatus_) {
db6a29e1 53 debugs(74, 9, "seek status-code in: " << tok.remaining().substr(0,10) << "...");
b8f86fd2
AJ
54 /* RFC 7230 section 3.1.2 - status code is 3 DIGIT octets.
55 * There is no limit on what those octets may be.
56 * 000 through 999 are all valid.
57 */
58 int64_t statusValue;
59 if (tok.int64(statusValue, 10, false, 3) && tok.skipOne(WspDelim)) {
f1d5359e 60
b8f86fd2
AJ
61 debugs(74, 6, "found int64 status-code=" << statusValue);
62 statusCode_ = static_cast<Http::StatusCode>(statusValue);
db6a29e1 63
b8f86fd2
AJ
64 buf_ = tok.remaining(); // resume checkpoint
65 completedStatus_ = true;
f1d5359e 66
b8f86fd2
AJ
67 } else if (tok.atEnd()) {
68 debugs(74, 6, "Parser needs more data");
69 return 0; // need more to be sure we have it all
f1d5359e 70
b8f86fd2
AJ
71 } else {
72 debugs(74, 6, "invalid status-line. invalid code.");
73 return -1; // invalid status, a single SP terminator required
74 }
75 // NOTE: any whitespace after the single SP is part of the reason phrase.
f1d5359e
AJ
76 }
77
f1d5359e
AJ
78 /* RFC 7230 says we SHOULD ignore the reason phrase content
79 * but it has a definite valid vs invalid character set.
80 * We interpret the SHOULD as ignoring absence and syntax, but
81 * producing an error if it contains an invalid octet.
82 */
83
db6a29e1
AJ
84 debugs(74, 9, "seek reason-phrase in: " << tok.remaining().substr(0,50) << "...");
85
f1d5359e
AJ
86 // if we got here we are still looking for reason-phrase bytes
87 static const CharacterSet phraseChars = CharacterSet::WSP + CharacterSet::VCHAR + CharacterSet::OBSTEXT;
b8f86fd2 88 (void)tok.prefix(reasonPhrase_, phraseChars); // optional, no error if missing
188ad27f 89 try {
417da400
EB
90 skipLineTerminator(tok);
91 buf_ = tok.remaining(); // resume checkpoint
92 debugs(74, DBG_DATA, Raw("leftovers", buf_.rawContent(), buf_.length()));
93 return 1;
94 } catch (const InsufficientInput &) {
188ad27f 95 reasonPhrase_.clear();
f1d5359e 96 return 0; // need more to be sure we have it all
188ad27f
AJ
97 } catch (const std::exception &ex) {
98 debugs(74, 6, "invalid status-line: " << ex.what());
99 }
b8f86fd2 100 return -1;
f1d5359e
AJ
101}
102
b8f86fd2
AJ
103/**
104 * Attempt to parse the method field out of an HTTP message status-line.
105 *
106 * Governed by:
107 * RFC 1945 section 6.1
108 * RFC 7230 section 2.6, 3.1 and 3.5
109 *
110 * Parsing state is stored between calls. The current implementation uses
111 * checkpoints after each successful status-line field.
112 * The return value tells you whether the parsing is completed or not.
113 *
114 * \retval -1 an error occurred.
115 * \retval 1 successful parse. statusCode_ and maybe reasonPhrase_ are filled and buffer consumed including first delimiter.
116 * \retval 0 more data is needed to complete the parse
117 */
5aea71e7 118int
f1d5359e
AJ
119Http::One::ResponseParser::parseResponseFirstLine()
120{
417da400 121 Tokenizer tok(buf_);
f1d5359e 122
86a8bc7c 123 const CharacterSet &WspDelim = DelimiterCharacters();
b8f86fd2 124
f1d5359e 125 if (msgProtocol_.protocol != AnyP::PROTO_NONE) {
db6a29e1
AJ
126 debugs(74, 6, "continue incremental parse for " << msgProtocol_);
127 debugs(74, DBG_DATA, "parse remaining buf={length=" << tok.remaining().length() << ", data='" << tok.remaining() << "'}");
f1d5359e 128 // we already found the magic, but not the full line. keep going.
b8f86fd2 129 return parseResponseStatusAndReason(tok, WspDelim);
f1d5359e
AJ
130
131 } else if (tok.skip(Http1magic)) {
db6a29e1 132 debugs(74, 6, "found prefix magic " << Http1magic);
f1d5359e
AJ
133 // HTTP Response status-line parse
134
b8f86fd2
AJ
135 // magic contains major version, still need to find minor DIGIT
136 int64_t verMinor;
137 if (tok.int64(verMinor, 10, false, 1) && tok.skipOne(WspDelim)) {
138 msgProtocol_.protocol = AnyP::PROTO_HTTP;
139 msgProtocol_.major = 1;
140 msgProtocol_.minor = static_cast<unsigned int>(verMinor);
f1d5359e 141
b8f86fd2 142 debugs(74, 6, "found version=" << msgProtocol_);
db6a29e1 143
b8f86fd2
AJ
144 debugs(74, DBG_DATA, "parse remaining buf={length=" << tok.remaining().length() << ", data='" << tok.remaining() << "'}");
145 buf_ = tok.remaining(); // resume checkpoint
146 return parseResponseStatusAndReason(tok, WspDelim);
db6a29e1 147
b8f86fd2
AJ
148 } else if (tok.atEnd())
149 return 0; // need more to be sure we have it all
150 else
151 return -1; // invalid version or delimiter, a single SP terminator required
f1d5359e
AJ
152
153 } else if (tok.skip(IcyMagic)) {
db6a29e1 154 debugs(74, 6, "found prefix magic " << IcyMagic);
f1d5359e
AJ
155 // ICY Response status-line parse (same as HTTP/1 after the magic version)
156 msgProtocol_.protocol = AnyP::PROTO_ICY;
157 // NP: ICY has no /major.minor details
db6a29e1 158 debugs(74, DBG_DATA, "parse remaining buf={length=" << tok.remaining().length() << ", data='" << tok.remaining() << "'}");
f1d5359e 159 buf_ = tok.remaining(); // resume checkpoint
b8f86fd2 160 return parseResponseStatusAndReason(tok, WspDelim);
26e3e640
EB
161 } else if (buf_.length() < Http1magic.length() && Http1magic.startsWith(buf_)) {
162 debugs(74, 7, Raw("valid HTTP/1 prefix", buf_.rawContent(), buf_.length()));
163 return 0;
164 } else if (buf_.length() < IcyMagic.length() && IcyMagic.startsWith(buf_)) {
165 debugs(74, 7, Raw("valid ICY prefix", buf_.rawContent(), buf_.length()));
166 return 0;
167 } else {
db6a29e1 168 debugs(74, 2, "unknown/missing prefix magic. Interpreting as HTTP/0.9");
f1d5359e 169 // found something that looks like an HTTP/0.9 response
db6a29e1 170 // Gateway/Transform it into HTTP/1.1
f1d5359e
AJ
171 msgProtocol_ = Http::ProtocolVersion(1,1);
172 // XXX: probably should use version 0.9 here and upgrade on output,
173 // but the old code did 1.1 transformation now.
174 statusCode_ = Http::scOkay;
175 static const SBuf gatewayPhrase("Gatewaying");
176 reasonPhrase_ = gatewayPhrase;
177 static const SBuf fakeHttpMimeBlock("X-Transformed-From: HTTP/0.9\r\n"
178 /* Server: visible_appname_string */
179 "Mime-Version: 1.0\r\n"
180 /* Date: squid_curtime */
181 "Expires: -1\r\n\r\n");
db6a29e1
AJ
182 mimeHeaderBlock_ = fakeHttpMimeBlock;
183 parsingStage_ = HTTP_PARSE_DONE;
f1d5359e
AJ
184 return 1; // no more parsing
185 }
186
26e3e640
EB
187 // unreachable
188 assert(false);
189 return -1;
f1d5359e
AJ
190}
191
192bool
193Http::One::ResponseParser::parse(const SBuf &aBuf)
194{
195 buf_ = aBuf;
196 debugs(74, DBG_DATA, "Parse buf={length=" << aBuf.length() << ", data='" << aBuf << "'}");
197
198 // stage 1: locate the status-line
199 if (parsingStage_ == HTTP_PARSE_NONE) {
200 // RFC 7230 explicitly states whether garbage whitespace is to be handled
201 // at each point of the message framing boundaries.
202 // It omits mentioning garbage prior to HTTP Responses.
203 // Therefore, if we receive anything at all treat it as Response message.
204 if (!buf_.isEmpty())
205 parsingStage_ = HTTP_PARSE_FIRST;
206 else
207 return false;
208 }
209
210 // stage 2: parse the status-line
211 if (parsingStage_ == HTTP_PARSE_FIRST) {
212 PROF_start(HttpParserParseReplyLine);
213
f8cab755 214 const int retcode = parseResponseFirstLine();
f1d5359e
AJ
215
216 // first-line (or a look-alike) found successfully.
617b7cca 217 if (retcode > 0 && parsingStage_ == HTTP_PARSE_FIRST)
f1d5359e
AJ
218 parsingStage_ = HTTP_PARSE_MIME;
219 debugs(74, 5, "status-line: retval " << retcode);
220 debugs(74, 5, "status-line: proto " << msgProtocol_);
221 debugs(74, 5, "status-line: status-code " << statusCode_);
222 debugs(74, 5, "status-line: reason-phrase " << reasonPhrase_);
223 debugs(74, 5, "Parser: bytes processed=" << (aBuf.length()-buf_.length()));
224 PROF_stop(HttpParserParseReplyLine);
225
226 // syntax errors already
227 if (retcode < 0) {
228 parsingStage_ = HTTP_PARSE_DONE;
ada1f18c 229 parseStatusCode = Http::scInvalidHeader;
f1d5359e
AJ
230 return false;
231 }
232 }
233
234 // stage 3: locate the mime header block
235 if (parsingStage_ == HTTP_PARSE_MIME) {
f8cab755 236 if (!grabMimeBlock("Response", Config.maxReplyHeaderSize))
f1d5359e
AJ
237 return false;
238 }
239
240 return !needsMoreData();
241}
1810a0cb 242