xref: /lighttpd1.4/src/request.c (revision e11f72b1)
1 #include "first.h"
2 
3 #include "request.h"
4 #include "burl.h"
5 #include "http_header.h"
6 #include "http_kv.h"
7 #include "log.h"
8 #include "sock_addr.h"
9 
10 #include <limits.h>
11 #include <stdlib.h>
12 #include <string.h>
13 
14 static int request_check_hostname(buffer * const host) {
15 	enum { DOMAINLABEL, TOPLABEL } stage = TOPLABEL;
16 	size_t i;
17 	int label_len = 0;
18 	size_t host_len, hostport_len;
19 	char *colon;
20 	int is_ip = -1; /* -1 don't know yet, 0 no, 1 yes */
21 	int level = 0;
22 
23 	/*
24 	 *       hostport      = host [ ":" port ]
25 	 *       host          = hostname | IPv4address | IPv6address
26 	 *       hostname      = *( domainlabel "." ) toplabel [ "." ]
27 	 *       domainlabel   = alphanum | alphanum *( alphanum | "-" ) alphanum
28 	 *       toplabel      = alpha | alpha *( alphanum | "-" ) alphanum
29 	 *       IPv4address   = 1*digit "." 1*digit "." 1*digit "." 1*digit
30 	 *       IPv6address   = "[" ... "]"
31 	 *       port          = *digit
32 	 */
33 
34 	/* IPv6 adress */
35 	if (host->ptr[0] == '[') {
36 		char *c = host->ptr + 1;
37 		int colon_cnt = 0;
38 
39 		/* check the address inside [...] */
40 		for (; *c && *c != ']'; c++) {
41 			if (*c == ':') {
42 				if (++colon_cnt > 7) {
43 					return -1;
44 				}
45 			} else if (!light_isxdigit(*c) && '.' != *c) {
46 				return -1;
47 			}
48 		}
49 
50 		/* missing ] */
51 		if (!*c) {
52 			return -1;
53 		}
54 
55 		/* check port */
56 		if (*(c+1) == ':') {
57 			for (c += 2; *c; c++) {
58 				if (!light_isdigit(*c)) {
59 					return -1;
60 				}
61 			}
62 		}
63 		else if ('\0' != *(c+1)) {
64 			/* only a port is allowed to follow [...] */
65 			return -1;
66 		}
67 		return 0;
68 	}
69 
70 	hostport_len = host_len = buffer_string_length(host);
71 
72 	if (NULL != (colon = memchr(host->ptr, ':', host_len))) {
73 		char *c = colon + 1;
74 
75 		/* check portnumber */
76 		for (; *c; c++) {
77 			if (!light_isdigit(*c)) return -1;
78 		}
79 
80 		/* remove the port from the host-len */
81 		host_len = colon - host->ptr;
82 	}
83 
84 	/* Host is empty */
85 	if (host_len == 0) return -1;
86 
87 	/* if the hostname ends in a "." strip it */
88 	if (host->ptr[host_len-1] == '.') {
89 		/* shift port info one left */
90 		if (NULL != colon) memmove(colon-1, colon, hostport_len - host_len);
91 		buffer_string_set_length(host, --hostport_len);
92 		if (--host_len == 0) return -1;
93 	}
94 
95 
96 	/* scan from the right and skip the \0 */
97 	for (i = host_len; i-- > 0; ) {
98 		const char c = host->ptr[i];
99 
100 		switch (stage) {
101 		case TOPLABEL:
102 			if (c == '.') {
103 				/* only switch stage, if this is not the last character */
104 				if (i != host_len - 1) {
105 					if (label_len == 0) {
106 						return -1;
107 					}
108 
109 					/* check the first character at right of the dot */
110 					if (is_ip == 0) {
111 						if (!light_isalnum(host->ptr[i+1])) {
112 							return -1;
113 						}
114 					} else if (!light_isdigit(host->ptr[i+1])) {
115 						is_ip = 0;
116 					} else if ('-' == host->ptr[i+1]) {
117 						return -1;
118 					} else {
119 						/* just digits */
120 						is_ip = 1;
121 					}
122 
123 					stage = DOMAINLABEL;
124 
125 					label_len = 0;
126 					level++;
127 				} else if (i == 0) {
128 					/* just a dot and nothing else is evil */
129 					return -1;
130 				}
131 			} else if (i == 0) {
132 				/* the first character of the hostname */
133 				if (!light_isalnum(c)) {
134 					return -1;
135 				}
136 				label_len++;
137 			} else {
138 				if (c != '-' && !light_isalnum(c)) {
139 					return -1;
140 				}
141 				if (is_ip == -1) {
142 					if (!light_isdigit(c)) is_ip = 0;
143 				}
144 				label_len++;
145 			}
146 
147 			break;
148 		case DOMAINLABEL:
149 			if (is_ip == 1) {
150 				if (c == '.') {
151 					if (label_len == 0) {
152 						return -1;
153 					}
154 
155 					label_len = 0;
156 					level++;
157 				} else if (!light_isdigit(c)) {
158 					return -1;
159 				} else {
160 					label_len++;
161 				}
162 			} else {
163 				if (c == '.') {
164 					if (label_len == 0) {
165 						return -1;
166 					}
167 
168 					/* c is either - or alphanum here */
169 					if ('-' == host->ptr[i+1]) {
170 						return -1;
171 					}
172 
173 					label_len = 0;
174 					level++;
175 				} else if (i == 0) {
176 					if (!light_isalnum(c)) {
177 						return -1;
178 					}
179 					label_len++;
180 				} else {
181 					if (c != '-' && !light_isalnum(c)) {
182 						return -1;
183 					}
184 					label_len++;
185 				}
186 			}
187 
188 			break;
189 		}
190 	}
191 
192 	/* a IP has to consist of 4 parts */
193 	if (is_ip == 1 && level != 3) {
194 		return -1;
195 	}
196 
197 	if (label_len == 0) {
198 		return -1;
199 	}
200 
201 	return 0;
202 }
203 
204 int http_request_host_normalize(buffer * const b, const int scheme_port) {
205     /*
206      * check for and canonicalize numeric IP address and portnum (optional)
207      * (IP address may be followed by ":portnum" (optional))
208      * - IPv6: "[...]"
209      * - IPv4: "x.x.x.x"
210      * - IPv4: 12345678   (32-bit decimal number)
211      * - IPv4: 012345678  (32-bit octal number)
212      * - IPv4: 0x12345678 (32-bit hex number)
213      *
214      * allow any chars (except ':' and '\0' and stray '[' or ']')
215      *   (other code may check chars more strictly or more pedantically)
216      * ':'  delimits (optional) port at end of string
217      * "[]" wraps IPv6 address literal
218      * '\0' should have been rejected earlier were it present
219      *
220      * any chars includes, but is not limited to:
221      * - allow '-' any where, even at beginning of word
222      *     (security caution: might be confused for cmd flag if passed to shell)
223      * - allow all-digit TLDs
224      *     (might be mistaken for IPv4 addr by inet_aton()
225      *      unless non-digits appear in subdomain)
226      */
227 
228     /* Note: not using getaddrinfo() since it does not support "[]" around IPv6
229      * and is not as lenient as inet_aton() and inet_addr() for IPv4 strings.
230      * Not using inet_pton() (when available) on IPv4 for similar reasons. */
231 
232     const char * const p = b->ptr;
233     const size_t blen = buffer_string_length(b);
234     long port = 0;
235 
236     if (*p != '[') {
237         char * const colon = (char *)memchr(p, ':', blen);
238         if (colon) {
239             if (*p == ':') return -1; /*(empty host then port, or naked IPv6)*/
240             if (colon[1] != '\0') {
241                 char *e;
242                 port = strtol(colon+1, &e, 0); /*(allow decimal, octal, hex)*/
243                 if (0 < port && port <= USHRT_MAX && *e == '\0') {
244                     /* valid port */
245                 } else {
246                     return -1;
247                 }
248             } /*(else ignore stray colon at string end)*/
249             buffer_string_set_length(b, (size_t)(colon - p)); /*(remove port str)*/
250         }
251 
252         if (light_isdigit(*p)) do {
253             /* (IPv4 address literal or domain starting w/ digit (e.g. 3com))*/
254             /* (check one-element cache of normalized IPv4 address string) */
255             static struct { char s[INET_ADDRSTRLEN]; size_t n; } laddr;
256             size_t n = colon ? (size_t)(colon - p) : blen;
257             sock_addr addr;
258             if (n == laddr.n && 0 == memcmp(p, laddr.s, n)) break;
259             if (1 == sock_addr_inet_pton(&addr, p, AF_INET, 0)) {
260                 sock_addr_inet_ntop_copy_buffer(b, &addr);
261                 n = buffer_string_length(b);
262                 if (n < sizeof(laddr.s)) memcpy(laddr.s, b->ptr, (laddr.n = n));
263             }
264         } while (0);
265     } else do { /* IPv6 addr */
266       #if defined(HAVE_IPV6) && defined(HAVE_INET_PTON)
267 
268         /* (check one-element cache of normalized IPv4 address string) */
269         static struct { char s[INET6_ADDRSTRLEN]; size_t n; } laddr;
270         sock_addr addr;
271         char *bracket = b->ptr+blen-1;
272         char *percent = strchr(b->ptr+1, '%');
273         size_t len;
274         int rc;
275         char buf[INET6_ADDRSTRLEN+16]; /*(+16 for potential %interface name)*/
276         if (blen <= 2) return -1; /*(invalid "[]")*/
277         if (*bracket != ']') {
278             bracket = (char *)memchr(b->ptr+1, ']', blen-1);
279             if (NULL == bracket || bracket[1] != ':'  || bracket - b->ptr == 1){
280                return -1;
281             }
282             if (bracket[2] != '\0') { /*(ignore stray colon at string end)*/
283                 char *e;
284                 port = strtol(bracket+2, &e, 0); /*(allow decimal, octal, hex)*/
285                 if (0 < port && port <= USHRT_MAX && *e == '\0') {
286                     /* valid port */
287                 } else {
288                     return -1;
289                 }
290             }
291         }
292 
293         len = (size_t)((percent ? percent : bracket) - (b->ptr+1));
294         if (laddr.n == len && 0 == memcmp(laddr.s, b->ptr+1, len)) {
295             /* truncate after ']' and re-add normalized port, if needed */
296             buffer_string_set_length(b, (size_t)(bracket - b->ptr + 1));
297             break;
298         }
299 
300         *bracket = '\0';/*(terminate IPv6 string)*/
301         if (percent) *percent = '\0'; /*(remove %interface from address)*/
302         rc = sock_addr_inet_pton(&addr, b->ptr+1, AF_INET6, 0);
303         if (percent) *percent = '%'; /*(restore %interface)*/
304         *bracket = ']'; /*(restore bracket)*/
305         if (1 != rc) return -1;
306 
307         sock_addr_inet_ntop(&addr, buf, sizeof(buf));
308         len = strlen(buf);
309         if (percent) {
310             if (percent > bracket) return -1;
311             if (len + (size_t)(bracket - percent) >= sizeof(buf)) return -1;
312             if (len < sizeof(laddr.s)) memcpy(laddr.s, buf, (laddr.n = len));
313             memcpy(buf+len, percent, (size_t)(bracket - percent));
314             len += (size_t)(bracket - percent);
315         }
316         buffer_string_set_length(b, 1); /* truncate after '[' */
317         buffer_append_string_len(b, buf, len);
318         buffer_append_string_len(b, CONST_STR_LEN("]"));
319 
320       #else
321 
322         return -1;
323 
324       #endif
325     } while (0);
326 
327     if (0 != port && port != scheme_port) {
328         buffer_append_string_len(b, CONST_STR_LEN(":"));
329         buffer_append_int(b, (int)port);
330     }
331 
332     return 0;
333 }
334 
335 int http_request_host_policy (buffer * const b, const unsigned int http_parseopts, const int scheme_port) {
336     return (((http_parseopts & HTTP_PARSEOPT_HOST_STRICT)
337              && 0 != request_check_hostname(b))
338             || ((http_parseopts & HTTP_PARSEOPT_HOST_NORMALIZE)
339                 && 0 != http_request_host_normalize(b, scheme_port)));
340 }
341 
342 __attribute_pure__ /*(could be even more strict and use __attribute_const__)*/
343 static int request_uri_is_valid_char(const unsigned char c) {
344 	return (c > 32 && c != 127 && c != 255);
345 }
346 
347 __attribute_cold__
348 __attribute_noinline__
349 static int http_request_header_line_invalid(request_st * const restrict r, const int status, const char * const restrict msg) {
350     if (r->conf.log_request_header_on_error) {
351         if (msg) log_error(r->conf.errh, __FILE__, __LINE__, "%s", msg);
352     }
353     return status;
354 }
355 
356 __attribute_cold__
357 __attribute_noinline__
358 static int http_request_header_char_invalid(request_st * const restrict r, const char ch, const char * const restrict msg) {
359     if (r->conf.log_request_header_on_error) {
360         if ((unsigned char)ch > 32 && ch != 127) {
361             log_error(r->conf.errh, __FILE__, __LINE__, "%s ('%c')", msg, ch);
362         }
363         else {
364             log_error(r->conf.errh, __FILE__, __LINE__, "%s (0x%x)", msg, ch);
365         }
366     }
367     return 400;
368 }
369 
370 /* add header to list of headers
371  * certain headers are also parsed
372  * might drop a header if deemed unnecessary/broken
373  *
374  * returns 0 on success, HTTP status on error
375  */
376 static int http_request_parse_single_header(request_st * const restrict r, const enum http_header_e id, const char * const restrict k, const size_t klen, const char * const restrict v, const size_t vlen) {
377     buffer **saveb = NULL;
378 
379     /*
380      * Note: k might not be '\0'-terminated
381      * Note: v is not '\0'-terminated, and ends with whitespace
382      *   (one of '\r' '\n' ' ' '\t')
383      */
384 
385     switch (id) {
386       /*case HTTP_HEADER_OTHER:*/
387       default:
388         break;
389       case HTTP_HEADER_HOST:
390         if (!(r->rqst_htags & HTTP_HEADER_HOST)) {
391             saveb = &r->http_host;
392             if (vlen >= 1024) { /*(expecting < 256)*/
393                 return http_request_header_line_invalid(r, 400, "uri-authority too long -> 400");
394             }
395         }
396         else if (NULL != r->http_host
397                  && buffer_is_equal_string(r->http_host, v, vlen)) {
398             /* ignore all Host: headers if match authority in request line */
399             return 0; /* ignore header */
400         }
401         else {
402             return http_request_header_line_invalid(r, 400, "duplicate Host header -> 400");
403         }
404         break;
405       case HTTP_HEADER_CONNECTION:
406         /* "Connection: close" is common case if header is present */
407         if ((vlen == 5 && buffer_eq_icase_ssn(v, CONST_STR_LEN("close")))
408             || http_header_str_contains_token(v,vlen,CONST_STR_LEN("close"))) {
409             r->keep_alive = 0;
410             break;
411         }
412         if (http_header_str_contains_token(v,vlen,CONST_STR_LEN("keep-alive"))){
413             r->keep_alive = 1;
414             break;
415         }
416         break;
417       case HTTP_HEADER_CONTENT_TYPE:
418         if (r->rqst_htags & HTTP_HEADER_CONTENT_TYPE) {
419             return http_request_header_line_invalid(r, 400, "duplicate Content-Type header -> 400");
420         }
421         break;
422       case HTTP_HEADER_IF_NONE_MATCH:
423         /* if dup, only the first one will survive */
424         if (r->rqst_htags & HTTP_HEADER_IF_NONE_MATCH) {
425             return 0; /* ignore header */
426         }
427         break;
428       case HTTP_HEADER_CONTENT_LENGTH:
429         if (!(r->rqst_htags & HTTP_HEADER_CONTENT_LENGTH)) {
430             /*(trailing whitespace was removed from vlen)*/
431             char *err;
432             off_t clen = strtoll(v, &err, 10);
433             if (clen >= 0 && err == v+vlen) {
434                 /* (set only if not set to -1 by Transfer-Encoding: chunked) */
435                 if (0 == r->reqbody_length) r->reqbody_length = clen;
436             }
437             else {
438                 return http_request_header_line_invalid(r, 400, "invalid Content-Length header -> 400");
439             }
440         }
441         else {
442             return http_request_header_line_invalid(r, 400, "duplicate Content-Length header -> 400");
443         }
444         break;
445       case HTTP_HEADER_IF_MODIFIED_SINCE:
446         if (r->rqst_htags & HTTP_HEADER_IF_MODIFIED_SINCE) {
447             /* Proxies sometimes send dup headers
448              * if they are the same we ignore the second
449              * if not, we raise an error */
450             const buffer *vb =
451               http_header_request_get(r, HTTP_HEADER_IF_MODIFIED_SINCE,
452                                       CONST_STR_LEN("If-Modified-Since"));
453             if (vb && buffer_is_equal_caseless_string(vb, v, vlen)) {
454                 /* ignore it if they are the same */
455                 return 0; /* ignore header */
456             }
457             else {
458                 return http_request_header_line_invalid(r, 400, "duplicate If-Modified-Since header -> 400");
459             }
460         }
461         break;
462       case HTTP_HEADER_TRANSFER_ENCODING:
463         if (HTTP_VERSION_1_0 == r->http_version) {
464             return http_request_header_line_invalid(r, 400, "HTTP/1.0 with Transfer-Encoding (bad HTTP/1.0 proxy?) -> 400");
465         }
466 
467         if (!buffer_eq_icase_ss(v, vlen, CONST_STR_LEN("chunked"))) {
468             /* Transfer-Encoding might contain additional encodings,
469              * which are not currently supported by lighttpd */
470             return http_request_header_line_invalid(r, 501, NULL); /* Not Implemented */
471         }
472         r->reqbody_length = -1;
473 
474         /* Transfer-Encoding is a hop-by-hop header,
475          * which must not be blindly forwarded to backends */
476         return 0; /* skip header */
477     }
478 
479     http_header_request_append(r, id, k, klen, v, vlen);
480 
481     if (saveb) {
482         *saveb = http_header_request_get(r, id, k, klen);
483     }
484 
485     return 0;
486 }
487 
488 __attribute_cold__
489 static int http_request_parse_proto_loose(request_st * const restrict r, const char * const restrict ptr, const size_t len, const unsigned int http_parseopts) {
490     const char * proto = memchr(ptr, ' ', len);
491     if (NULL == proto)
492         return http_request_header_line_invalid(r, 400, "incomplete request line -> 400");
493     proto = memchr(proto+1, ' ', len - (proto+1 - ptr));
494     if (NULL == proto)
495         return http_request_header_line_invalid(r, 400, "incomplete request line -> 400");
496     ++proto;
497 
498     if (proto[0]=='H' && proto[1]=='T' && proto[2]=='T' && proto[3]=='P' && proto[4] == '/') {
499         if (proto[5] == '1' && proto[6] == '.' && (proto[7] == '1' || proto[7] == '0')) {
500             /* length already checked before calling this routine */
501             /* (len != (size_t)(proto - ptr + 8)) */
502             if (http_parseopts & HTTP_PARSEOPT_HEADER_STRICT) /*(http_header_strict)*/
503                 return http_request_header_line_invalid(r, 400, "incomplete request line -> 400");
504             r->http_version = (proto[7] == '1') ? HTTP_VERSION_1_1 : HTTP_VERSION_1_0;
505         }
506         else
507             return http_request_header_line_invalid(r, 505, "unknown HTTP version -> 505");
508     }
509     else
510         return http_request_header_line_invalid(r, 400, "unknown protocol -> 400");
511 
512     /* keep-alive default: HTTP/1.1 -> true; HTTP/1.0 -> false */
513     r->keep_alive = (HTTP_VERSION_1_0 != r->http_version);
514 
515     return 0;
516 }
517 
518 __attribute_cold__
519 static const char * http_request_parse_reqline_uri(request_st * const restrict r, const char * const restrict uri, const size_t len, const unsigned int http_parseopts) {
520     const char *nuri;
521     if ((len > 7 && buffer_eq_icase_ssn(uri, "http://", 7)
522         && NULL != (nuri = memchr(uri + 7, '/', len-7)))
523        ||
524        (len > 8 && buffer_eq_icase_ssn(uri, "https://", 8)
525         && NULL != (nuri = memchr(uri + 8, '/', len-8)))) {
526         const char * const host = uri + (uri[4] == ':' ? 7 : 8);
527         const size_t hostlen = nuri - host;
528         if (0 == hostlen || hostlen >= 1024) { /*(expecting < 256)*/
529             http_request_header_line_invalid(r, 400, "uri-authority empty or too long -> 400");
530             return NULL;
531         }
532         /* Insert as host header */
533         http_header_request_set(r, HTTP_HEADER_HOST, CONST_STR_LEN("Host"), host, hostlen);
534         r->http_host = http_header_request_get(r, HTTP_HEADER_HOST, CONST_STR_LEN("Host"));
535         return nuri;
536     } else if (!(http_parseopts & HTTP_PARSEOPT_HEADER_STRICT) /*(!http_header_strict)*/
537            || (HTTP_METHOD_CONNECT == r->http_method && (uri[0] == ':' || light_isdigit(uri[0])))
538            || (HTTP_METHOD_OPTIONS == r->http_method && uri[0] == '*' && 1 == len)) {
539         /* (permitted) */
540         return uri;
541     } else {
542         http_request_header_line_invalid(r, 400, "request-URI parse error -> 400");
543         return NULL;
544     }
545 }
546 
547 static int http_request_parse_reqline(request_st * const restrict r, const char * const restrict ptr, const unsigned short * const restrict hoff, const unsigned int http_parseopts) {
548     size_t len = hoff[2];
549 
550     /* parse the first line of the request
551      * <method> <uri> <protocol>\r\n
552      * */
553     if (len < 13) /* minimum len with (!http_header_strict): "x x HTTP/1.0\n" */
554         return http_request_header_line_invalid(r, 400, "invalid request line (too short) -> 400");
555     if (ptr[len-2] == '\r')
556         len-=2;
557     else if (!(http_parseopts & HTTP_PARSEOPT_HEADER_STRICT)) /*(!http_header_strict)*/
558         len-=1;
559     else
560         return http_request_header_line_invalid(r, 400, "missing CR before LF in header -> 400");
561 
562     /*
563      * RFC7230:
564      *   HTTP-version  = HTTP-name "/" DIGIT "." DIGIT
565      *   HTTP-name     = %x48.54.54.50 ; "HTTP", case-sensitive
566      */
567 
568     /* protocol is expected to be " HTTP/1.1" or " HTTP/1.0" at end of line */
569     union proto_un {
570       char c[8];
571       uint64_t u;
572     };
573     static const union proto_un http_1_1 = {{'H','T','T','P','/','1','.','1'}};
574     static const union proto_un http_1_0 = {{'H','T','T','P','/','1','.','0'}};
575     const char *p = ptr + len - 8;
576     union proto_un proto8;
577     proto8.c[0]=p[0]; proto8.c[1]=p[1]; proto8.c[2]=p[2]; proto8.c[3]=p[3];
578     proto8.c[4]=p[4]; proto8.c[5]=p[5]; proto8.c[6]=p[6]; proto8.c[7]=p[7];
579     if (p[-1] == ' ' && http_1_1.u == proto8.u) {
580         r->http_version = HTTP_VERSION_1_1;
581         r->keep_alive = 1; /* keep-alive default: HTTP/1.1 -> true */
582     }
583     else if (p[-1] == ' ' && http_1_0.u == proto8.u) {
584         r->http_version = HTTP_VERSION_1_0;
585         r->keep_alive = 0; /* keep-alive default: HTTP/1.0 -> false */
586     }
587     else {
588         int status = http_request_parse_proto_loose(r,ptr,len,http_parseopts);
589         if (0 != status) return status;
590         /*(space char must exist if http_request_parse_proto_loose() succeeds)*/
591         for (p = ptr + len - 9; p[-1] != ' '; --p) ;
592     }
593 
594     /* method is expected to be a short string in the general case */
595     size_t i = 0;
596     while (ptr[i] != ' ') ++i;
597   #if 0 /*(space must exist if protocol was parsed successfully)*/
598     while (i < len && ptr[i] != ' ') ++i;
599     if (ptr[i] != ' ')
600         return http_request_header_line_invalid(r, 400, "incomplete request line -> 400");
601   #endif
602 
603     r->http_method = get_http_method_key(ptr, i);
604     if (HTTP_METHOD_UNSET == r->http_method)
605         return http_request_header_line_invalid(r, 501, "unknown http-method -> 501");
606 
607     const char *uri = ptr + i + 1;
608 
609     if (uri == p)
610         return http_request_header_line_invalid(r, 400, "no uri specified -> 400");
611     len = (size_t)(p - uri - 1);
612 
613     if (*uri != '/') { /* (common case: (*uri == '/')) */
614         uri = http_request_parse_reqline_uri(r, uri, len, http_parseopts);
615         if (NULL == uri) return 400;
616         len = (size_t)(p - uri - 1);
617     }
618 
619     if (0 == len)
620         return http_request_header_line_invalid(r, 400, "no uri specified -> 400");
621 
622     /* check uri for invalid characters */
623     if (http_parseopts & HTTP_PARSEOPT_HEADER_STRICT) { /* http_header_strict */
624         if ((http_parseopts & HTTP_PARSEOPT_URL_NORMALIZE_CTRLS_REJECT)) {
625             /* URI will be checked in http_response_prepare() */
626         }
627         else {
628             for (i = 0; i < len; ++i) {
629                 if (!request_uri_is_valid_char(uri[i]))
630                     return http_request_header_char_invalid(r, uri[i], "invalid character in URI -> 400");
631             }
632         }
633     }
634     else {
635         /* check entire set of request headers for '\0' */
636         if (NULL != memchr(ptr, '\0', hoff[hoff[0]]))
637             return http_request_header_char_invalid(r, '\0', "invalid character in header -> 400");
638     }
639 
640     buffer_copy_string_len(&r->target, uri, len);
641     buffer_copy_string_len(&r->target_orig, uri, len);
642     return 0;
643 }
644 
645 int http_request_parse_target(request_st * const r, int scheme_port) {
646     /* URI is parsed into components at start of request and may
647      * also be re-parsed upon HANDLER_COMEBACK during the request
648      * r->target is expected to be a "/url-part?query-part"
649      *   (and *not* a fully-qualified URI starting https://...)
650      * r->uri.authority is expected to be parsed elsewhere into r->http_host
651      */
652 
653     /**
654      * prepare strings
655      *
656      * - uri.path
657      * - uri.query
658      *
659      */
660 
661     /**
662      * Name according to RFC 2396
663      *
664      * - scheme
665      * - authority
666      * - path
667      * - query
668      *
669      * (scheme)://(authority)(path)?(query)#fragment
670      *
671      */
672 
673     /* take initial scheme value from connection-level state
674      * (request r->uri.scheme can be overwritten for later,
675      *  for example by mod_extforward or mod_magnet) */
676     if (scheme_port == 443)
677         buffer_copy_string_len(&r->uri.scheme, CONST_STR_LEN("https"));
678     else
679         buffer_copy_string_len(&r->uri.scheme, CONST_STR_LEN("http"));
680 
681     buffer * const target = &r->target;
682     if (r->http_method == HTTP_METHOD_CONNECT
683         || (r->http_method == HTTP_METHOD_OPTIONS
684             && target->ptr[0] == '*'
685             && target->ptr[1] == '\0')) {
686         /* CONNECT ... (or) OPTIONS * ... */
687         buffer_copy_buffer(&r->uri.path, target);
688         buffer_clear(&r->uri.query);
689         return 0;
690     }
691 
692     char *qstr;
693     if (r->conf.http_parseopts & HTTP_PARSEOPT_URL_NORMALIZE) {
694         /*uint32_t len = (uint32_t)buffer_string_length(target);*/
695         int qs = burl_normalize(target, r->tmp_buf, r->conf.http_parseopts);
696         if (-2 == qs) {
697             log_error(r->conf.errh, __FILE__, __LINE__,
698               "invalid character in URI -> 400 %s",
699               target->ptr);
700             return 400; /* Bad Request */
701         }
702         qstr = (-1 == qs) ? NULL : target->ptr+qs;
703       #if 0  /* future: might enable here, or below for all requests */
704         /* (Note: total header size not recalculated on HANDLER_COMEBACK
705          *  even if other request headers changed during processing)
706          * (If (0 != r->loops_per_request), then the generated
707          *  request is too large.  Should a different error be returned?) */
708         r->rqst_header_len -= len;
709         len = buffer_string_length(target);
710         r->rqst_header_len += len;
711         if (len > MAX_HTTP_REQUEST_URI) {
712             return 414; /* 414 Request-URI Too Long */
713         }
714         if (r->rqst_header_len > MAX_HTTP_REQUEST_HEADER) {
715             log_error(r->conf.errh, __FILE__, __LINE__,
716               "request header fields too large: %u -> 431",
717               r->rqst_header_len);
718             return 431; /* Request Header Fields Too Large */
719         }
720       #endif
721     }
722     else {
723         size_t rlen = buffer_string_length(target);
724         qstr = memchr(target->ptr, '#', rlen);/* discard fragment */
725         if (qstr) {
726             rlen = (size_t)(qstr - target->ptr);
727             buffer_string_set_length(target, rlen);
728         }
729         qstr = memchr(target->ptr, '?', rlen);
730     }
731 
732     /** extract query string from target */
733     const char * const pstr = target->ptr;
734     const uint32_t rlen = buffer_string_length(target);
735     uint32_t plen;
736     if (NULL != qstr) {
737         plen = (uint32_t)(qstr - pstr);
738         buffer_copy_string_len(&r->uri.query, qstr + 1, rlen - plen - 1);
739     }
740     else {
741         plen = rlen;
742         buffer_clear(&r->uri.query);
743     }
744     buffer_copy_string_len(&r->uri.path, pstr, plen);
745 
746     /* decode url to path
747      *
748      * - decode url-encodings  (e.g. %20 -> ' ')
749      * - remove path-modifiers (e.g. /../)
750      */
751 
752     buffer_urldecode_path(&r->uri.path);
753     buffer_path_simplify(&r->uri.path, &r->uri.path);
754     if (r->uri.path.ptr[0] != '/') {
755         log_error(r->conf.errh, __FILE__, __LINE__,
756           "uri-path does not begin with '/': %s -> 400", r->uri.path.ptr);
757         return 400; /* Bad Request */
758     }
759 
760     return 0;
761 }
762 
763 __attribute_cold__
764 __attribute_noinline__
765 static int http_request_parse_header_other(request_st * const restrict r, const char * const restrict k, const int klen, const unsigned int http_header_strict) {
766     for (int i = 0; i < klen; ++i) {
767         if (light_isalpha(k[i]) || k[i] == '-') continue; /*(common cases)*/
768         /**
769          * 1*<any CHAR except CTLs or separators>
770          * CTLs == 0-31 + 127, CHAR = 7-bit ascii (0..127)
771          *
772          */
773         switch(k[i]) {
774         case ' ':
775         case '\t':
776             return http_request_header_line_invalid(r, 400, "WS character in key -> 400");
777         case '(':
778         case ')':
779         case '<':
780         case '>':
781         case '@':
782         case ',':
783         case ';':
784         case '\\':
785         case '\"':
786         case '/':
787         case '[':
788         case ']':
789         case '?':
790         case '=':
791         case '{':
792         case '}':
793             return http_request_header_char_invalid(r, k[i], "invalid character in header key -> 400");
794         default:
795             if (http_header_strict ? (k[i] < 32 || ((unsigned char *)k)[i] >= 127) : k[i] == '\0')
796                 return http_request_header_char_invalid(r, k[i], "invalid character in header key -> 400");
797             break; /* ok */
798         }
799     }
800     return 0;
801 }
802 
803 static int http_request_parse_headers(request_st * const restrict r, char * const restrict ptr, const unsigned short * const restrict hoff, const unsigned int http_parseopts) {
804     const unsigned int http_header_strict = (http_parseopts & HTTP_PARSEOPT_HEADER_STRICT);
805 
806   #if 0 /*(not checked here; will later result in invalid label for HTTP header)*/
807     int i = hoff[2];
808 
809     if (ptr[i] == ' ' || ptr[i] == '\t') {
810         return http_request_header_line_invalid(r, 400, "WS at the start of first line -> 400");
811     }
812   #endif
813 
814     for (int i = 2; i < hoff[0]; ++i) {
815         const char *k = ptr + hoff[i];
816         /* one past last line hoff[hoff[0]] is to final "\r\n" */
817         char *end = ptr + hoff[i+1];
818 
819         const char *colon = memchr(k, ':', end - k);
820         if (NULL == colon)
821             return http_request_header_line_invalid(r, 400, "invalid header missing ':' -> 400");
822 
823         const char *v = colon + 1;
824 
825         /* RFC7230 Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
826          * 3.2.4.  Field Parsing
827          * [...]
828          * No whitespace is allowed between the header field-name and colon.  In
829          * the past, differences in the handling of such whitespace have led to
830          * security vulnerabilities in request routing and response handling.  A
831          * server MUST reject any received request message that contains
832          * whitespace between a header field-name and colon with a response code
833          * of 400 (Bad Request).  A proxy MUST remove any such whitespace from a
834          * response message before forwarding the message downstream.
835          */
836         /* (line k[-1] is always preceded by a '\n',
837          *  including first header after request-line,
838          *  so no need to check colon != k) */
839         if (colon[-1] == ' ' || colon[-1] == '\t') {
840             if (http_header_strict) {
841                 return http_request_header_line_invalid(r, 400, "invalid whitespace between field-name and colon -> 400");
842             }
843             else {
844                 /* remove trailing whitespace from key(if !http_header_strict)*/
845                 do { --colon; } while (colon[-1] == ' ' || colon[-1] == '\t');
846             }
847         }
848 
849         const int klen = (int)(colon - k);
850         if (0 == klen)
851             return http_request_header_line_invalid(r, 400, "invalid header key -> 400");
852         const enum http_header_e id = http_header_hkey_get(k, klen);
853 
854         if (id == HTTP_HEADER_OTHER) {
855             for (int j = 0; j < klen; ++j) {
856                 if (light_isalpha(k[j]) || k[j] == '-') continue; /*(common cases)*/
857                 if (0 != http_request_parse_header_other(r, k+j, klen-j, http_header_strict))
858                     return 400;
859                 break;
860             }
861         }
862 
863         /* remove leading whitespace from value */
864         while (*v == ' ' || *v == '\t') ++v;
865 
866         for (; i+1 <= hoff[0]; ++i) {
867             end = ptr + hoff[i+1];
868             if (end[0] != ' ' && end[0] != '\t') break;
869 
870             /* line folding */
871           #ifdef __COVERITY__
872             force_assert(end - k >= 2);
873           #endif
874             if (end[-2] == '\r')
875                 end[-2] = ' ';
876             else if (http_header_strict)
877                 return http_request_header_line_invalid(r, 400, "missing CR before LF in header -> 400");
878             end[-1] = ' ';
879         }
880       #ifdef __COVERITY__
881         /*(buf holding k has non-zero request-line, so end[-2] valid)*/
882         force_assert(end >= k + 2);
883       #endif
884         if (end[-2] == '\r')
885             --end;
886         else if (http_header_strict)
887             return http_request_header_line_invalid(r, 400, "missing CR before LF in header -> 400");
888         /* remove trailing whitespace from value (+ remove '\r\n') */
889         /* (line k[-1] is always preceded by a '\n',
890          *  including first header after request-line,
891          *  so no need to check (end != k)) */
892         do { --end; } while (end[-1] == ' ' || end[-1] == '\t');
893 
894         const int vlen = (int)(end - v);
895         /* empty header-fields are not allowed by HTTP-RFC, we just ignore them */
896         if (vlen <= 0) continue; /* ignore header */
897 
898         if (http_header_strict) {
899             for (int j = 0; j < vlen; ++j) {
900                 if ((((unsigned char *)v)[j] < 32 && v[j] != '\t') || v[j]==127)
901                     return http_request_header_char_invalid(r, v[j], "invalid character in header -> 400");
902             }
903         } /* else URI already checked in http_request_parse_reqline() for any '\0' */
904 
905         int status = http_request_parse_single_header(r, id, k, (size_t)klen, v, (size_t)vlen);
906         if (0 != status) return status;
907     }
908 
909     return 0;
910 }
911 
912 int http_request_parse(request_st * const restrict r, char * const restrict hdrs, const unsigned short * const restrict hoff, const int scheme_port) {
913     /*
914      * Request: "^(GET|POST|HEAD|...) ([^ ]+(\\?[^ ]+|)) (HTTP/1\\.[01])$"
915      * Header : "^([-a-zA-Z]+): (.+)$"
916      * End    : "^$"
917      */
918 
919     int status;
920     const unsigned int http_parseopts = r->conf.http_parseopts;
921 
922     status = http_request_parse_reqline(r, hdrs, hoff, http_parseopts);
923     if (0 != status) return status;
924 
925     status = http_request_parse_target(r, scheme_port);
926     if (0 != status) return status;
927 
928     status = http_request_parse_headers(r, hdrs, hoff, http_parseopts);
929     if (0 != status) return status;
930 
931     /*(r->http_host might not be set until after parsing request headers)*/
932     buffer_copy_buffer(&r->uri.authority, r->http_host);/*(copy even if empty)*/
933     buffer_to_lower(&r->uri.authority);
934 
935     /* post-processing */
936 
937     /* check hostname field if it is set */
938     if (r->http_host) {
939         if (0 != http_request_host_policy(r->http_host,
940                                           http_parseopts, scheme_port))
941             return http_request_header_line_invalid(r, 400, "Invalid Hostname -> 400");
942     }
943     else {
944         if (r->http_version == HTTP_VERSION_1_1)
945             return http_request_header_line_invalid(r, 400, "HTTP/1.1 but Host missing -> 400");
946     }
947 
948     if (0 == r->reqbody_length) {
949         /* POST requires Content-Length (or Transfer-Encoding)
950          * (-1 == r->reqbody_length when Transfer-Encoding: chunked)*/
951         if (HTTP_METHOD_POST == r->http_method
952             && !(r->rqst_htags & HTTP_HEADER_CONTENT_LENGTH)) {
953             return http_request_header_line_invalid(r, 411, "POST-request, but content-length missing -> 411");
954         }
955     }
956     else {
957         /* (-1 == r->reqbody_length when Transfer-Encoding: chunked)*/
958         if (-1 == r->reqbody_length
959             && (r->rqst_htags & HTTP_HEADER_CONTENT_LENGTH)) {
960             /* RFC7230 Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
961              * 3.3.3.  Message Body Length
962              * [...]
963              * If a message is received with both a Transfer-Encoding and a
964              * Content-Length header field, the Transfer-Encoding overrides the
965              * Content-Length.  Such a message might indicate an attempt to
966              * perform request smuggling (Section 9.5) or response splitting
967              * (Section 9.4) and ought to be handled as an error.  A sender MUST
968              * remove the received Content-Length field prior to forwarding such
969              * a message downstream.
970              */
971             const unsigned int http_header_strict =
972               (http_parseopts & HTTP_PARSEOPT_HEADER_STRICT);
973             if (http_header_strict) {
974                 return http_request_header_line_invalid(r, 400, "invalid Transfer-Encoding + Content-Length -> 400");
975             }
976             else {
977                 /* ignore Content-Length */
978                 http_header_request_unset(r, HTTP_HEADER_CONTENT_LENGTH, CONST_STR_LEN("Content-Length"));
979             }
980         }
981         if (http_method_get_or_head(r->http_method)
982             && !(http_parseopts & HTTP_PARSEOPT_METHOD_GET_BODY)) {
983             return http_request_header_line_invalid(r, 400, "GET/HEAD with content-length -> 400");
984         }
985     }
986 
987     return 0;
988 }
989