Squid Web Cache master
Loading...
Searching...
No Matches
RequestParser.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2026 The Squid Software Foundation and contributors
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
9#include "squid.h"
10#include "debug/Stream.h"
13#include "parser/Tokenizer.h"
14#include "SquidConfig.h"
15
16Http1::Parser::size_type
18{
19 // RFC 7230 section 2.6
20 /* method SP request-target SP "HTTP/" DIGIT "." DIGIT CRLF */
21 return method_.image().length() + uri_.length() + 12;
22}
23
37void
39{
41 if (Config.onoff.relaxed_header_parser < 0 && (buf_[0] == '\r' || buf_[0] == '\n'))
42 debugs(74, DBG_IMPORTANT, "WARNING: Invalid HTTP Request: " <<
43 "CRLF bytes received ahead of request-line. " <<
44 "Ignored due to relaxed_header_parser.");
45 // Be tolerant of prefix empty lines
46 // ie any series of either \n or \r\n with no other characters and no repeated \r
47 while (!buf_.isEmpty() && (buf_[0] == '\n' ||
48 (buf_[0] == '\r' && buf_.length() > 1 && buf_[1] == '\n'))) {
49 buf_.consume(1);
50 }
51 }
52}
53
61bool
63{
64 // method field is a sequence of TCHAR.
65 // Limit to 32 characters to prevent overly long sequences of non-HTTP
66 // being sucked in before mismatch is detected. 32 is itself annoyingly
67 // big but there are methods registered by IANA that reach 17 bytes:
68 // http://www.iana.org/assignments/http-methods
69 static const size_t maxMethodLength = 32; // TODO: make this configurable?
70
71 SBuf methodFound;
72 if (!tok.prefix(methodFound, CharacterSet::TCHAR, maxMethodLength)) {
73 debugs(33, ErrorLevel(), "ERROR: invalid request-line: missing or malformed method");
74 parseStatusCode = Http::scBadRequest;
75 return false;
76 }
77 method_ = HttpRequestMethod(methodFound);
78
79 if (!skipDelimiter(tok.skipAll(DelimiterCharacters()), "after method"))
80 return false;
81
82 return true;
83}
84
86static const CharacterSet &
88{
89 /* RFC 3986 section 2:
90 * "
91 * A URI is composed from a limited set of characters consisting of
92 * digits, letters, and a few graphic symbols.
93 * "
94 */
95 static const CharacterSet UriChars =
96 CharacterSet("URI-Chars","") +
97 // RFC 3986 section 2.2 - reserved characters
98 CharacterSet("gen-delims", ":/?#[]@") +
99 CharacterSet("sub-delims", "!$&'()*+,;=") +
100 // RFC 3986 section 2.3 - unreserved characters
102 // RFC 3986 section 2.1 - percent encoding "%" HEXDIG
103 CharacterSet("pct-encoded", "%") +
105
106 return UriChars;
107}
108
110const CharacterSet &
112{
114#if USE_HTTP_VIOLATIONS
115 static const CharacterSet RelaxedExtended =
117 // accept whitespace (extended), it will be dealt with later
118 DelimiterCharacters() +
119 // RFC 2396 unwise character set which must never be transmitted
120 // in un-escaped form. But many web services do anyway.
121 CharacterSet("RFC2396-unwise","\"\\|^<>`{}") +
122 // UTF-8 because we want to be future-proof
123 CharacterSet("UTF-8", 128, 255);
124
125 return RelaxedExtended;
126#else
127 static const CharacterSet RelaxedCompliant =
129 // accept whitespace (extended), it will be dealt with later.
130 DelimiterCharacters();
131
132 return RelaxedCompliant;
133#endif
134 }
135
136 // strict parse only accepts what the RFC say we can
137 return UriValidCharacters();
138}
139
140bool
142{
143 const auto maxUriLength = String::RawSizeMaxXXX();
144
145 SBuf uriFound;
146 if (!tok.prefix(uriFound, RequestTargetCharacters())) {
147 parseStatusCode = Http::scBadRequest;
148 debugs(33, ErrorLevel(), "ERROR: invalid request-line: missing or malformed URI");
149 return false;
150 }
151
152 if (uriFound.length() > maxUriLength) {
153 // RFC 7230 section 3.1.1 mandatory (MUST) 414 response
154 parseStatusCode = Http::scUriTooLong;
155 debugs(33, ErrorLevel(), "ERROR: invalid request-line: " << uriFound.length() <<
156 "-byte URI exceeds " << maxUriLength << "-byte limit");
157 return false;
158 }
159
160 uri_ = uriFound;
161 return true;
162}
163
164bool
166{
167 static const SBuf http1p0("HTTP/1.0");
168 static const SBuf http1p1("HTTP/1.1");
169 const auto savedTok = tok;
170
171 // Optimization: Expect (and quickly parse) HTTP/1.1 or HTTP/1.0 in
172 // the vast majority of cases.
173 if (tok.skipSuffix(http1p1)) {
174 msgProtocol_ = Http::ProtocolVersion(1, 1);
175 return true;
176 } else if (tok.skipSuffix(http1p0)) {
177 msgProtocol_ = Http::ProtocolVersion(1, 0);
178 return true;
179 } else {
180 // RFC 7230 section 2.6:
181 // HTTP-version = HTTP-name "/" DIGIT "." DIGIT
182 static const CharacterSet period("Decimal point", ".");
183 static const SBuf proto("HTTP/");
184 SBuf majorDigit;
185 SBuf minorDigit;
186 if (tok.suffix(minorDigit, CharacterSet::DIGIT) &&
187 tok.skipOneTrailing(period) &&
188 tok.suffix(majorDigit, CharacterSet::DIGIT) &&
189 tok.skipSuffix(proto)) {
190 const bool multiDigits = majorDigit.length() > 1 || minorDigit.length() > 1;
191 // use '0.0' for unsupported multiple digit version numbers
192 const unsigned int major = multiDigits ? 0 : (*majorDigit.rawContent() - '0');
193 const unsigned int minor = multiDigits ? 0 : (*minorDigit.rawContent() - '0');
194 msgProtocol_ = Http::ProtocolVersion(major, minor);
195 return true;
196 }
197 }
198
199 // A GET request might use HTTP/0.9 syntax
200 if (method_ == Http::METHOD_GET) {
201 // RFC 1945 - no HTTP version field at all
202 tok = savedTok; // in case the URI ends with a digit
203 // report this assumption as an error if configured to triage parsing
204 debugs(33, ErrorLevel(), "assuming HTTP/0.9 request-line");
205 msgProtocol_ = Http::ProtocolVersion(0,9);
206 return true;
207 }
208
209 debugs(33, ErrorLevel(), "ERROR: invalid request-line: not HTTP");
210 parseStatusCode = Http::scBadRequest;
211 return false;
212}
213
219bool
220Http::One::RequestParser::skipDelimiter(const size_t count, const char *where)
221{
222 if (count <= 0) {
223 debugs(33, ErrorLevel(), "ERROR: invalid request-line: missing delimiter " << where);
224 parseStatusCode = Http::scBadRequest;
225 return false;
226 }
227
228 // tolerant parser allows multiple whitespace characters between request-line fields
229 if (count > 1 && !Config.onoff.relaxed_header_parser) {
230 debugs(33, ErrorLevel(), "ERROR: invalid request-line: too many delimiters " << where);
231 parseStatusCode = Http::scBadRequest;
232 return false;
233 }
234
235 return true;
236}
237
239bool
241{
243 (void)tok.skipAllTrailing(CharacterSet::CR); // optional; multiple OK
244 } else {
245 if (!tok.skipOneTrailing(CharacterSet::CR)) {
246 debugs(33, ErrorLevel(), "ERROR: invalid request-line: missing CR before LF");
247 parseStatusCode = Http::scBadRequest;
248 return false;
249 }
250 }
251 return true;
252}
253
265int
267{
268 debugs(74, 5, "parsing possible request: buf.length=" << buf_.length());
269 debugs(74, DBG_DATA, buf_);
270
271 SBuf line;
272
273 // Earlier, skipGarbageLines() took care of any leading LFs (if allowed).
274 // Now, the request line has to end at the first LF.
275 static const CharacterSet lineChars = CharacterSet::LF.complement("notLF");
276 Tokenizer lineTok(buf_);
277 if (!lineTok.prefix(line, lineChars) || !lineTok.skip('\n')) {
278 if (buf_.length() >= Config.maxRequestHeaderSize) {
279 /* who should we blame for our failure to parse this line? */
280
281 Tokenizer methodTok(buf_);
282 if (!parseMethodField(methodTok))
283 return -1; // blame a bad method (or its delimiter)
284
285 // assume it is the URI
286 debugs(74, ErrorLevel(), "ERROR: invalid request-line: URI exceeds " <<
287 Config.maxRequestHeaderSize << "-byte limit");
288 parseStatusCode = Http::scUriTooLong;
289 return -1;
290 }
291 debugs(74, 5, "Parser needs more data");
292 return 0;
293 }
294
295 Tokenizer tok(line);
296
297 if (!parseMethodField(tok))
298 return -1;
299
300 /* now parse backwards, to leave just the URI */
301 if (!skipTrailingCrs(tok))
302 return -1;
303
304 if (!parseHttpVersionField(tok))
305 return -1;
306
307 if (!http0() && !skipDelimiter(tok.skipAllTrailing(DelimiterCharacters()), "before protocol version"))
308 return -1;
309
310 /* parsed everything before and after the URI */
311
312 if (!parseUriField(tok))
313 return -1;
314
315 if (!tok.atEnd()) {
316 debugs(33, ErrorLevel(), "ERROR: invalid request-line: garbage after URI");
317 parseStatusCode = Http::scBadRequest;
318 return -1;
319 }
320
321 parseStatusCode = Http::scOkay;
322 buf_ = lineTok.remaining(); // incremental parse checkpoint
323 return 1;
324}
325
326bool
328{
329 const bool result = doParse(aBuf);
330 if (preserveParsed_) {
331 assert(aBuf.length() >= remaining().length());
332 parsed_.append(aBuf.substr(0, aBuf.length() - remaining().length())); // newly parsed bytes
333 }
334
335 return result;
336}
337
338// raw is not a reference because a reference might point back to our own buf_ or parsed_
339bool
341{
342 buf_ = aBuf;
343 debugs(74, DBG_DATA, "Parse buf={length=" << aBuf.length() << ", data='" << aBuf << "'}");
344
345 // stage 1: locate the request-line
346 if (parsingStage_ == HTTP_PARSE_NONE) {
347 skipGarbageLines();
348
349 // if we hit something before EOS treat it as a message
350 if (!buf_.isEmpty())
351 parsingStage_ = HTTP_PARSE_FIRST;
352 else
353 return false;
354 }
355
356 // stage 2: parse the request-line
357 if (parsingStage_ == HTTP_PARSE_FIRST) {
358 const int retcode = parseRequestFirstLine();
359
360 // first-line (or a look-alike) found successfully.
361 if (retcode > 0) {
362 parsingStage_ = HTTP_PARSE_MIME;
363 }
364
365 debugs(74, 5, "request-line: retval " << retcode << ": line={" << aBuf.length() << ", data='" << aBuf << "'}");
366 debugs(74, 5, "request-line: method: " << method_);
367 debugs(74, 5, "request-line: url: " << uri_);
368 debugs(74, 5, "request-line: proto: " << msgProtocol_);
369 debugs(74, 5, "Parser: bytes processed=" << (aBuf.length()-buf_.length()));
370
371 // syntax errors already
372 if (retcode < 0) {
373 parsingStage_ = HTTP_PARSE_DONE;
374 return false;
375 }
376 }
377
378 // stage 3: locate the mime header block
379 if (parsingStage_ == HTTP_PARSE_MIME) {
380 // HTTP/1.x request-line is valid and parsing completed.
381 if (!grabMimeBlock("Request", Config.maxRequestHeaderSize)) {
382 if (parseStatusCode == Http::scHeaderTooLarge)
383 parseStatusCode = Http::scRequestHeaderFieldsTooLarge;
384 return false;
385 }
386 }
387
388 return !needsMoreData();
389}
390
static const CharacterSet & UriValidCharacters()
the characters which truly are valid within URI
class SquidConfig Config
#define assert(EX)
Definition assert.h:17
optimized set of C chars, with quick membership test and merge support
CharacterSet complement(const char *complementLabel=nullptr) const
static const CharacterSet TCHAR
static const CharacterSet DIGIT
static const CharacterSet HEXDIG
static const CharacterSet LF
static const CharacterSet CR
static const CharacterSet & RFC3986_UNRESERVED()
allowed URI characters that do not have a reserved purpose, RFC 3986
const SBuf & image() const
::Parser::Tokenizer Tokenizer
Definition Parser.h:44
bool parseMethodField(Tokenizer &)
bool doParse(const SBuf &aBuf)
called from parse() to do the parsing
Http1::Parser::size_type firstLineSize() const override
size in bytes of the first line including CRLF terminator
static const CharacterSet & RequestTargetCharacters()
characters which Squid will accept in the HTTP request-target (URI)
bool parse(const SBuf &aBuf) override
bool skipDelimiter(const size_t count, const char *where)
bool parseHttpVersionField(Tokenizer &)
HttpRequestMethod method_
what request method has been found on the first line
bool parseUriField(Tokenizer &)
SBuf uri_
raw copy of the original client request-line URI field
bool skipTrailingCrs(Tokenizer &tok)
Parse CRs at the end of request-line, just before the terminating LF.
Definition SBuf.h:94
const char * rawContent() const
Definition SBuf.cc:509
size_type length() const
Returns the number of bytes stored in SBuf.
Definition SBuf.h:419
SBuf substr(size_type pos, size_type n=npos) const
Definition SBuf.cc:576
struct SquidConfig::@90 onoff
size_t maxRequestHeaderSize
int relaxed_header_parser
static size_type RawSizeMaxXXX()
Definition SquidString.h:76
#define DBG_DATA
Definition Stream.h:40
#define DBG_IMPORTANT
Definition Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
@ HTTP_PARSE_FIRST
HTTP/1 message first-line.
Definition Parser.h:24
@ HTTP_PARSE_DONE
parsed a message header, or reached a terminal syntax error
Definition Parser.h:29
@ HTTP_PARSE_MIME
HTTP/1 mime-header block.
Definition Parser.h:28
@ HTTP_PARSE_NONE
initialized, but nothing usefully parsed yet
Definition Parser.h:23
int ErrorLevel()
the right debugs() level for logging HTTP violation messages
Definition Parser.cc:269
@ scUriTooLong
Definition StatusCode.h:59
@ scHeaderTooLarge
Header too large to process.
Definition StatusCode.h:89
@ scBadRequest
Definition StatusCode.h:45
@ scOkay
Definition StatusCode.h:27
@ scRequestHeaderFieldsTooLarge
Definition StatusCode.h:71
@ METHOD_GET
Definition MethodType.h:25
AnyP::ProtocolVersion ProtocolVersion()
Definition parse.c:160