Browse Source

[core] reuse code to parse backend response

reuse code to parse backend response (http_header_parse_hoff())
master
Glenn Strauss 7 months ago
parent
commit
a1eba3c89b
  1. 265
      src/http-header-glue.c
  2. 1
      src/http_cgi.c

265
src/http-header-glue.c

@ -684,93 +684,93 @@ void http_response_upgrade_read_body_unknown(request_st * const r) {
}
static int http_response_process_headers(request_st * const r, http_response_opts * const opts, buffer * const hdrs) {
char *ns;
const char *s;
int line = 0;
int status_is_set = 0;
for (s = hdrs->ptr; NULL != (ns = strchr(s, '\n')); s = ns + 1, ++line) {
const char *key, *value;
int key_len;
enum http_header_e id;
/* strip the \n */
ns[0] = '\0';
if (ns > s && ns[-1] == '\r') ns[-1] = '\0';
if (0 == line && (ns - s) >= 12 && 0 == memcmp(s, "HTTP/", 5)) {
/* non-parsed headers ... we parse them anyway */
/* (accept HTTP/2.0 and HTTP/3.0 from naive non-proxy backends) */
if ((s[5] == '1' || opts->backend != BACKEND_PROXY) && s[6] == '.'
&& (s[7] == '1' || s[7] == '0') && s[8] == ' ') {
/* after the space should be a status code for us */
int status = http_header_str_to_code(s+9);
if (status >= 100 && status < 1000) {
status_is_set = 1;
light_bset(r->resp_htags, HTTP_HEADER_STATUS);
r->http_status = status;
} /* else we expected 3 digits and didn't get them */
}
if (0 == r->http_status) {
log_error(r->conf.errh, __FILE__, __LINE__,
"invalid HTTP status line: %s", s);
r->http_status = 502; /* Bad Gateway */
r->handler_module = NULL;
return -1;
}
static int http_response_process_headers(request_st * const restrict r, http_response_opts * const restrict opts, char * const restrict s, const unsigned short hoff[8192], const int is_nph) {
int i = 1;
if (is_nph) {
/* non-parsed headers ... we parse them anyway */
/* (accept HTTP/2.0 and HTTP/3.0 from naive non-proxy backends) */
/* (http_header_str_to_code() expects certain chars after code) */
if (s[12] == '\r' || s[12] == '\n') s[12] = '\0';/*(caller checked 12)*/
if ((s[5] == '1' || opts->backend != BACKEND_PROXY) && s[6] == '.'
&& (s[7] == '1' || s[7] == '0') && s[8] == ' ') {
/* after the space should be a status code for us */
int status = http_header_str_to_code(s+9);
if (status >= 100 && status < 1000) {
r->http_status = status;
opts->local_redir = 0; /*(disable; status was set)*/
i = 2;
} /* else we expected 3 digits and didn't get them */
}
continue;
if (0 == r->http_status) {
log_error(r->conf.errh, __FILE__, __LINE__,
"invalid HTTP status line: %s", s);
r->http_status = 502; /* Bad Gateway */
r->handler_module = NULL;
return 0;
}
}
else if (__builtin_expect( (opts->backend == BACKEND_PROXY), 0)) {
/* invalid response Status-Line from HTTP proxy */
r->http_status = 502; /* Bad Gateway */
r->handler_module = NULL;
return 0;
}
for (; i < hoff[0]; ++i) {
const char *k = s+hoff[i], *value;
char *end = s+hoff[i+1]-1; /*('\n')*/
/* strip the \r?\n */
if (end > k && end[-1] == '\r') --end;
end[0] = '\0'; /* for http_header_str_to_code(), strstr() */
/*(XXX: not done; could remove trailing whitespace)*/
/* parse the headers */
key = s;
if (NULL == (value = strchr(s, ':'))) {
if (NULL == (value = memchr(k, ':', end - k))) {
/* we expect: "<key>: <value>\r\n" */
continue;
}
key_len = value - key;
if (0 == key_len) continue; /*(already ignored when writing response)*/
const uint32_t klen = (uint32_t)(value - k);
if (0 == klen) continue; /*(already ignored when writing response)*/
do { ++value; } while (*value == ' ' || *value == '\t'); /* skip LWS */
id = http_header_hkey_get(key, key_len);
if (opts->authorizer) {
if (0 == r->http_status || 200 == r->http_status) {
if (id == HTTP_HEADER_STATUS) {
int status = http_header_str_to_code(value);
if (status >= 100 && status < 1000) {
r->http_status = status;
} else {
r->http_status = 502; /* Bad Gateway */
break;
}
const enum http_header_e id = http_header_hkey_get(k, klen);
if (opts->authorizer && (0 == r->http_status || 200 == r->http_status)){
if (id == HTTP_HEADER_STATUS) {
int status = http_header_str_to_code(value);
if (status >= 100 && status < 1000) {
r->http_status = status;
opts->local_redir = 0; /*(disable; status was set)*/
}
else if (id == HTTP_HEADER_OTHER && key_len > 9
&& (key[0] & 0xdf) == 'V'
&& buffer_eq_icase_ssn(key,
CONST_STR_LEN("Variable-"))) {
http_header_env_append(r, key + 9, key_len - 9, value, strlen(value));
else {
r->http_status = 502; /* Bad Gateway */
break; /*(do not unset r->handler_module)*/
}
continue;
}
else if (id == HTTP_HEADER_OTHER && klen > 9 && (k[0] & 0xdf) == 'V'
&& buffer_eq_icase_ssn(k, CONST_STR_LEN("Variable-"))) {
http_header_env_append(r, k + 9, klen - 9, value, end - value);
}
continue;
}
switch (id) {
case HTTP_HEADER_STATUS:
{
if (opts->backend == BACKEND_PROXY) break; /*(pass w/o parse)*/
if (opts->backend != BACKEND_PROXY) {
int status = http_header_str_to_code(value);
if (status >= 100 && status < 1000) {
r->http_status = status;
status_is_set = 1;
} else {
opts->local_redir = 0; /*(disable; status was set)*/
}
else {
r->http_status = 502;
r->handler_module = NULL;
}
continue; /* do not send Status to client */
}
} /*(else pass w/o parse for BACKEND_PROXY)*/
break;
case HTTP_HEADER_UPGRADE:
/*(technically, should also verify Connection: upgrade)*/
@ -791,8 +791,7 @@ static int http_response_process_headers(request_st * const r, http_response_opt
if (*value == '+') ++value;
if (!r->resp_decode_chunked
&& !light_btst(r->resp_htags, HTTP_HEADER_CONTENT_LENGTH)) {
const char *err = ns;
if (err[-1] == '\0') --err; /*(skip one '\0', trailing whitespace)*/
const char *err = end;
while (err > value && (err[-1] == ' ' || err[-1] == '\t')) --err;
if (err <= value) continue; /*(might error 502 Bad Gateway)*/
uint32_t vlen = (uint32_t)(err - value);
@ -832,16 +831,23 @@ static int http_response_process_headers(request_st * const r, http_response_opt
* A server MUST NOT send this header field. */
/* (not bothering to remove HTTP2-Settings from Connection) */
continue;
case HTTP_HEADER_OTHER:
/* ignore invalid headers with whitespace between label and ':'
* (if less strict behavior is desired, check and correct above
* this switch() statement, but not for BACKEND_PROXY) */
if (k[klen-1] == ' ' || k[klen-1] == '\t')
continue;
break;
default:
break;
}
http_header_response_insert(r, id, key, key_len, value, strlen(value));
http_header_response_insert(r, id, k, klen, value, end - value);
}
/* CGI/1.1 rev 03 - 7.2.1.2 */
/* (proxy requires Status-Line, so never true for proxy)*/
if (!status_is_set && light_btst(r->resp_htags, HTTP_HEADER_LOCATION)) {
if (0 == r->http_status && light_btst(r->resp_htags, HTTP_HEADER_LOCATION)){
r->http_status = 302;
}
@ -902,19 +908,7 @@ http_response_check_1xx (request_st * const r, buffer * const restrict b, uint32
}
__attribute_hot__
__attribute_pure__
static const char *
http_response_end_of_header (const char * const restrict ptr)
{
/* find \n(\r)?\n sequence */
for (const char *n=ptr-1, *nn=NULL; NULL != (n = strchr(n+1, '\n')); nn=n) {
if (n - nn == 2 ? n[-1] == '\r' : n - nn == 1) return n+1;
}
return NULL;
}
__attribute_noinline__
handler_t http_response_parse_headers(request_st * const r, http_response_opts * const opts, buffer * const b) {
/**
* possible formats of response headers:
@ -939,78 +933,61 @@ handler_t http_response_parse_headers(request_st * const r, http_response_opts *
const char *bstart;
uint32_t blen;
do {
blen = buffer_string_length(b);
/*("HTTP/1.1 200 " is at least 13 chars + \r\n, but accept w/o final ' ')*/
const int is_nph = (blen >= 12 && 0 == memcmp(b->ptr, "HTTP/", 5));
int is_header_end = 0;
uint32_t i = 0;
if (b->ptr[0] == '\n' || (b->ptr[0] == '\r' && b->ptr[1] == '\n')) {
/* no HTTP headers */
i = (b->ptr[0] == '\n') ? 0 : 1;
is_header_end = 1;
} else if (is_nph || b->ptr[(i = strcspn(b->ptr, ":\n"))] == ':') {
/* HTTP headers */
const char *n = http_response_end_of_header(b->ptr+i+1);
if (n) {
i = (uint32_t)(n - b->ptr - 1);
is_header_end = 1;
}
} else if (i == blen) { /* (no newline yet; partial header line?) */
} else if (opts->backend == BACKEND_CGI) {
/* no HTTP headers, but a body (special-case for CGI compat) */
/* no colon found; does not appear to be HTTP headers */
if (0 != http_chunk_append_buffer(r, b)) {
return HANDLER_ERROR;
}
r->http_status = 200; /* OK */
r->resp_body_started = 1;
return HANDLER_GO_ON;
} else {
/* invalid response headers */
r->http_status = 502; /* Bad Gateway */
r->handler_module = NULL;
return HANDLER_FINISHED;
}
if (!is_header_end) {
if (blen > MAX_HTTP_RESPONSE_FIELD_SIZE) {
do {
uint32_t header_len, is_nph = 0;
unsigned short hoff[8192]; /* max num header lines + 3; 16k on stack */
hoff[0] = 1; /* number of lines */
hoff[1] = 0; /* base offset for all lines */
hoff[2] = 0; /* offset from base for 2nd line; init 0 to detect '\n' */
blen = buffer_string_length(b);
header_len = http_header_parse_hoff(b->ptr, blen, hoff);
if ((header_len ? header_len : blen) > MAX_HTTP_RESPONSE_FIELD_SIZE) {
log_error(r->conf.errh, __FILE__, __LINE__,
"response headers too large for %s", r->uri.path.ptr);
r->http_status = 502; /* Bad Gateway */
r->handler_module = NULL;
return HANDLER_FINISHED;
}
return HANDLER_GO_ON;
}
/* the body starts after the EOL */
bstart = b->ptr + (i + 1);
blen -= (i + 1);
/* strip the last \r?\n */
if (i > 0 && (b->ptr[i - 1] == '\r')) {
i--;
}
buffer_string_set_length(b, i);
if (hoff[2]) { /*(at least one newline found if offset is non-zero)*/
/*("HTTP/1.1 200 " is at least 13 chars + \r\n; 12 w/o final ' ')*/
is_nph = hoff[2] >= 12 && 0 == memcmp(b->ptr, "HTTP/", 5);
if (!is_nph) {
const char * colon = memchr(b->ptr, ':', hoff[2]-1);
if (__builtin_expect( (NULL == colon), 0)) {
if (hoff[2] <= 2 && (1 == hoff[2] || b->ptr[0] == '\r')) {
/* no HTTP headers */
}
else if (opts->backend == BACKEND_CGI) {
/* no HTTP headers, but body (special-case for CGI)*/
/* no colon found; does not appear to be HTTP headers */
if (0 != http_chunk_append_buffer(r, b)) {
return HANDLER_ERROR;
}
r->http_status = 200; /* OK */
r->resp_body_started = 1;
return HANDLER_GO_ON;
}
else {
/* invalid response headers */
r->http_status = 502; /* Bad Gateway */
r->handler_module = NULL;
return HANDLER_FINISHED;
}
}
}
} /* else no newline yet; partial header line?) */
if (0 == header_len) /* headers incomplete */
return HANDLER_GO_ON;
if (opts->backend == BACKEND_PROXY && !is_nph) {
/* invalid response Status-Line from HTTP proxy */
r->http_status = 502; /* Bad Gateway */
r->handler_module = NULL;
return HANDLER_FINISHED;
}
/* the body starts after the EOL */
bstart = b->ptr + header_len;
blen -= header_len;
if (0 != http_response_process_headers(r, opts, b)) {
return HANDLER_ERROR;
}
if (0 != http_response_process_headers(r, opts, b->ptr, hoff, is_nph))
return HANDLER_ERROR;
} while (r->http_status < 200
&& http_response_check_1xx(r, b, bstart - b->ptr, blen));
} while (r->http_status < 200
&& http_response_check_1xx(r, b, bstart - b->ptr, blen));
r->resp_body_started = 1;

1
src/http_cgi.c

@ -52,7 +52,6 @@ http_cgi_local_redir (request_st * const r)
|| ( vb->ptr[ulen] != '\0'
&& vb->ptr[ulen] != '/'
&& vb->ptr[ulen] != '?'))
&& !light_btst(r->resp_htags, HTTP_HEADER_STATUS)
&& 1 == r->resp_headers.used /*"Location"; no "Status" or NPH response*/
&& r->http_status >= 300 && r->http_status < 400) {
if (++r->loops_per_request > 5) {

Loading…
Cancel
Save