[core] parse h2 pseudo-headers

master
Glenn Strauss 3 years ago
parent 4c699be6c6
commit f37c90bccc

@ -551,6 +551,162 @@ static const char * http_request_parse_reqline_uri(request_st * const restrict r
}
}
static int http_request_parse_pseudohdrs(request_st * const restrict r, const char * const restrict ptr, const unsigned short * const restrict hoff, const unsigned int http_parseopts) {
/* HTTP/2 request pseudo-header fields */
const unsigned int http_header_strict = (http_parseopts & HTTP_PARSEOPT_HEADER_STRICT);
int scheme = 0;
uint32_t ulen = 0, alen = 0;
const char *uri = NULL;
for (int i = 1; i < hoff[0]; ++i) {
const char *k = ptr + ((i > 1) ? hoff[i] : 0);
/* one past last line hoff[hoff[0]] is to final "\r\n" */
const char *end = ptr + hoff[i+1];
if (*k != ':')
break;
++k;
const char *colon = memchr(k, ':', end - k);
if (NULL == colon)
return http_request_header_line_invalid(r, 400, "invalid header missing ':' -> 400");
const int klen = (int)(colon - k);
if (0 == klen)
return http_request_header_line_invalid(r, 400, "invalid header key -> 400");
const char *v = colon + 1;
/* remove leading whitespace from value */
while (*v == ' ' || *v == '\t') ++v;
#ifdef __COVERITY__
/*(ptr has at least ::\r\n by now, so end[-2] valid)*/
force_assert(end >= k + 1);
#endif
/* remove trailing whitespace from value (+ remove '\r\n') */
if (end[-2] == '\r')
--end;
else if (http_header_strict)
return http_request_header_line_invalid(r, 400, "missing CR before LF in header -> 400");
--end;
while (v > end && (end[-1] == ' ' || end[-1] == '\t')) --end;
const int vlen = (int)(end - v);
if (vlen <= 0)
return http_request_header_line_invalid(r, 400, "invalid pseudo-header -> 400");
switch (klen) {
case 4:
if (0 == memcmp(k, "path", 4)) {
if (NULL != uri)
return http_request_header_line_invalid(r, 400, "repeated pseudo-header -> 400");
uri = v;
ulen = (uint32_t)vlen;
continue;
}
break;
case 6:
if (0 == memcmp(k, "method", 6)) {
if (HTTP_METHOD_UNSET != r->http_method)
return http_request_header_line_invalid(r, 400, "repeated pseudo-header -> 400");
r->http_method = get_http_method_key(v, vlen);
if (HTTP_METHOD_UNSET == r->http_method)
return http_request_header_line_invalid(r, 501, "unknown http-method -> 501");
continue;
}
else if (0 == memcmp(k, "scheme", 6)) {
if (scheme)
return http_request_header_line_invalid(r, 400, "repeated pseudo-header -> 400");
switch (vlen) { /*(validated, but then value is ignored)*/
case 5: /* "https" */
if (v[4]!='s') break;
__attribute_fallthrough__
case 4: /* "http" */
if (v[0]=='h' && v[1]=='t' && v[2]=='t' && v[3]=='p') {
scheme = 1;
continue;
}
break;
default:
break;
}
return http_request_header_line_invalid(r, 400, "unknown pseudo-header scheme -> 400");
}
break;
case 9:
if (0 == memcmp(k, "authority", 9)) {
if (r->http_host)
return http_request_header_line_invalid(r, 400, "repeated pseudo-header -> 400");
if (vlen >= 1024) /*(expecting < 256)*/
return http_request_header_line_invalid(r, 400, "invalid pseudo-header authority too long -> 400");
alen = (uint32_t)vlen;
/* insert as host header */
http_header_request_set(r, HTTP_HEADER_HOST, CONST_STR_LEN("host"), v, vlen);
r->http_host = http_header_request_get(r, HTTP_HEADER_HOST, CONST_STR_LEN("Host"));
continue;
}
break;
default:
break;
}
return http_request_header_line_invalid(r, 400, "invalid pseudo-header -> 400");
}
/* :method is required to indicate method
* CONNECT method must have :method and :authority
* All other methods must have at least :method :scheme :path */
if (HTTP_METHOD_UNSET == r->http_method)
return http_request_header_line_invalid(r, 400, "missing pseudo-header method -> 400");
if (HTTP_METHOD_CONNECT != r->http_method) {
if (!scheme)
return http_request_header_line_invalid(r, 400, "missing pseudo-header scheme -> 400");
if (NULL == uri)
return http_request_header_line_invalid(r, 400, "missing pseudo-header path -> 400");
if (*uri != '/') { /* (common case: (*uri == '/')) */
if (*uri != '*' || ulen != 1 || HTTP_METHOD_OPTIONS != r->http_method)
return http_request_header_line_invalid(r, 400, "invalid pseudo-header path -> 400");
}
}
else { /* HTTP_METHOD_CONNECT */
if (NULL == r->http_host)
return http_request_header_line_invalid(r, 400, "missing pseudo-header authority -> 400");
if (NULL != uri || scheme)
return http_request_header_line_invalid(r, 400, "invalid pseudo-header with CONNECT -> 400");
/*(reuse uri and ulen to assign to r->target)*/
uri = r->http_host->ptr;
ulen = alen;
}
/* r->http_host, if set, is checked with http_request_host_policy()
* in http_request_parse() */
/* copied from end of http_request_parse_reqline() */
/* check uri for invalid characters */
if (http_header_strict) {
if ((http_parseopts & HTTP_PARSEOPT_URL_NORMALIZE_CTRLS_REJECT)) {
/* URI will be checked in http_response_prepare() */
}
else {
for (uint32_t i = 0; i < ulen; ++i) {
if (!request_uri_is_valid_char(uri[i]))
return http_request_header_char_invalid(r, uri[i], "invalid character in URI -> 400");
}
}
}
else {
/* check entire set of request headers for '\0' */
if (NULL != memchr(ptr, '\0', hoff[hoff[0]]))
return http_request_header_char_invalid(r, '\0', "invalid character in header -> 400");
}
buffer_copy_string_len(&r->target, uri, ulen);
buffer_copy_string_len(&r->target_orig, uri, ulen);
return 0;
}
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) {
size_t len = hoff[2];
@ -818,7 +974,15 @@ static int http_request_parse_headers(request_st * const restrict r, char * cons
}
#endif
for (int i = 2; i < hoff[0]; ++i) {
int i = 2;
if (r->http_version >= HTTP_VERSION_2) {
/* CONNECT must have :method and :authority
* All other methods must have at least :method :scheme :path */
i += (r->http_method != HTTP_METHOD_CONNECT) ? 2 : 1;
while (ptr[hoff[i]] == ':') ++i;
}
for (; i < hoff[0]; ++i) {
const char *k = ptr + hoff[i];
/* one past last line hoff[hoff[0]] is to final "\r\n" */
char *end = ptr + hoff[i+1];
@ -926,7 +1090,9 @@ int http_request_parse(request_st * const restrict r, char * const restrict hdrs
int status;
const unsigned int http_parseopts = r->conf.http_parseopts;
status = http_request_parse_reqline(r, hdrs, hoff, http_parseopts);
status = (r->http_version >= HTTP_VERSION_2)
? http_request_parse_pseudohdrs(r, hdrs, hoff, http_parseopts)
: http_request_parse_reqline(r, hdrs, hoff, http_parseopts);
if (0 != status) return status;
status = http_request_parse_target(r, scheme_port);
@ -948,7 +1114,7 @@ int http_request_parse(request_st * const restrict r, char * const restrict hdrs
return http_request_header_line_invalid(r, 400, "Invalid Hostname -> 400");
}
else {
if (r->http_version == HTTP_VERSION_1_1)
if (r->http_version >= HTTP_VERSION_1_1)
return http_request_header_line_invalid(r, 400, "HTTP/1.1 but Host missing -> 400");
}

Loading…
Cancel
Save