Update http header parser

This commit is contained in:
2022-08-16 18:35:03 +02:00
parent 50bb537074
commit bc1c6d3498
10 changed files with 306 additions and 96 deletions

@ -41,9 +41,9 @@ char *fastcgi_add_param(char *buf, const char *key, const char *value) {
ptr += 4;
}
memcpy(ptr, key, key_len);
strcpy(ptr, key);
ptr += key_len;
memcpy(ptr, value, val_len);
strcpy(ptr, value);
ptr += val_len;
return ptr;
@ -149,19 +149,21 @@ int fastcgi_init(fastcgi_conn *conn, int mode, unsigned int client_num, unsigned
param_ptr = fastcgi_add_param(param_ptr, "PATH_INFO", buf0);
//param_ptr = fastcgi_add_param(param_ptr, "AUTH_TYPE", "");
char *content_length = http_get_header_field(&req->hdr, "Content-Length");
const char *content_length = http_get_header_field(&req->hdr, "Content-Length");
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_LENGTH", content_length != NULL ? content_length : "");
char *content_type = http_get_header_field(&req->hdr, "Content-Type");
const char *content_type = http_get_header_field(&req->hdr, "Content-Type");
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_TYPE", content_type != NULL ? content_type : "");
if (client_geoip != NULL) {
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_INFO", client_geoip);
}
for (int i = 0; i < req->hdr.field_num; i++) {
const http_field *f = &req->hdr.fields[i];
const char *name = http_field_get_name(f);
char *ptr = buf0;
ptr += sprintf(ptr, "HTTP_");
for (int j = 0; j < strlen(req->hdr.fields[i][0]); j++, ptr++) {
char ch = req->hdr.fields[i][0][j];
for (int j = 0; j < strlen(name); j++, ptr++) {
char ch = name[j];
if ((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) {
ch = ch;
} else if (ch >= 'a' && ch <= 'z') {
@ -172,7 +174,7 @@ int fastcgi_init(fastcgi_conn *conn, int mode, unsigned int client_num, unsigned
ptr[0] = ch;
ptr[1] = 0;
}
param_ptr = fastcgi_add_param(param_ptr, buf0, req->hdr.fields[i][1]);
param_ptr = fastcgi_add_param(param_ptr, buf0, http_field_get_value(f));
}
unsigned short param_len = param_ptr - param_buf - sizeof(header);

@ -11,8 +11,10 @@
#include <string.h>
void http_to_camel_case(char *str, int mode) {
char last = '-';
char ch;
if (mode == HTTP_PRESERVE)
return;
char ch, last = '-';
for (int i = 0; i < strlen(str); i++) {
ch = str[i];
if (mode == HTTP_CAMEL && last == '-' && ch >= 'a' && ch <= 'z') {
@ -24,12 +26,47 @@ void http_to_camel_case(char *str, int mode) {
}
}
const char *http_field_get_name(const http_field *field) {
if (field->type == HTTP_FIELD_NORMAL) {
return field->normal.name;
} else if (field->type == HTTP_FIELD_EX_VALUE) {
return field->ex_value.name;
} else if (field->type == HTTP_FIELD_EX_NAME) {
return field->ex_name.name;
}
return NULL;
}
const char *http_field_get_value(const http_field *field) {
if (field->type == HTTP_FIELD_NORMAL) {
return field->normal.value;
} else if (field->type == HTTP_FIELD_EX_VALUE) {
return field->ex_value.value;
} else if (field->type == HTTP_FIELD_EX_NAME) {
return field->ex_name.value;
}
return NULL;
}
void http_free_hdr(http_hdr *hdr) {
for (int i = 0; i < hdr->field_num; i++) {
free(hdr->fields[i][0]);
free(hdr->fields[i][1]);
http_field *f = &hdr->fields[i];
if (f->type == HTTP_FIELD_NORMAL) {
f->normal.name[0] = 0;
f->normal.value[0] = 0;
} else if (f->type == HTTP_FIELD_EX_VALUE) {
f->ex_value.name[0] = 0;
free(f->ex_value.value);
f->ex_value.value = NULL;
} else if (f->type == HTTP_FIELD_EX_NAME) {
free(f->ex_name.name);
free(f->ex_name.value);
f->ex_name.name = NULL;
f->ex_name.value = NULL;
}
}
hdr->field_num = 0;
hdr->last_field_num = -1;
}
void http_free_req(http_req *req) {
@ -43,44 +80,63 @@ void http_free_res(http_res *res) {
}
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) {
char *pos1 = memchr(buf, ':', end_ptr - buf);
char *pos2;
if (hdr->last_field_num > hdr->field_num) {
print(ERR_STR "Unable to parse header: Invalid state" CLR_STR);
return 3;
}
char *pos1 = (char *) buf, *pos2 = (char *) end_ptr;
if (buf[0] == ' ' || buf[0] == '\t') {
if (hdr->last_field_num == -1) {
print(ERR_STR "Unable to parse header" CLR_STR);
return 3;
}
http_field *f = &hdr->fields[(int) hdr->last_field_num];
str_trim_lws(&pos1, &pos2);
http_append_to_header_field(f, pos1, pos2 - pos1);
return 0;
}
pos1 = memchr(buf, ':', end_ptr - buf);
if (pos1 == NULL) {
print(ERR_STR "Unable to parse header" CLR_STR);
return 3;
}
long len = pos1 - buf;
hdr->fields[(int) hdr->field_num][0] = malloc(len + 1);
sprintf(hdr->fields[(int) hdr->field_num][0], "%.*s", (int) len, buf);
http_to_camel_case(hdr->fields[(int) hdr->field_num][0], HTTP_CAMEL);
long len1 = pos1 - buf - 1;
pos1++;
pos2 = (char *) end_ptr - 1;
while (pos1[0] == ' ') pos1++;
while (pos2[0] == ' ') pos2--;
len = pos2 - pos1 + 1;
str_trim_lws(&pos1, &pos2);
long len2 = pos2 - pos1;
if (len <= 0) {
hdr->fields[(int) hdr->field_num][1] = malloc(1);
hdr->fields[(int) hdr->field_num][1][0] = 0;
char field_num = hdr->field_num;
int found = http_get_header_field_num_len(hdr, buf, len1);
if (found == -1) {
if (http_add_header_field_len(hdr, buf, len1, pos1, len2 < 0 ? 0 : len2) != 0) {
print(ERR_STR "Unable to parse header: Too many header fields" CLR_STR);
return 3;
}
} else {
hdr->fields[(int) hdr->field_num][1] = malloc(len + 1);
sprintf(hdr->fields[(int) hdr->field_num][1], "%.*s", (int) len, pos1);
field_num = (char) found;
http_append_to_header_field(&hdr->fields[found], ", ", 2);
http_append_to_header_field(&hdr->fields[found], pos1, len2);
}
hdr->field_num++;
hdr->last_field_num = (char) field_num;
return 0;
}
int http_receive_request(sock *client, http_req *req) {
long rcv_len, len;
char *ptr, *pos0, *pos1, *pos2;
char buf[CLIENT_MAX_HEADER_SIZE];
char *ptr, *pos0 = buf, *pos1, *pos2;
memset(buf, 0, sizeof(buf));
memset(req->method, 0, sizeof(req->method));
memset(req->version, 0, sizeof(req->version));
req->uri = NULL;
req->hdr.field_num = 0;
req->hdr.last_field_num = -1;
while (1) {
rcv_len = sock_recv(client, buf, CLIENT_MAX_HEADER_SIZE, 0);
@ -164,31 +220,85 @@ int http_receive_request(sock *client, http_req *req) {
return 0;
}
char *http_get_header_field(const http_hdr *hdr, const char *field_name) {
char field_name_1[256], field_name_2[256];
strcpy(field_name_1, field_name);
http_to_camel_case(field_name_1, HTTP_LOWER);
for (int i = 0; i < hdr->field_num; i++) {
strcpy(field_name_2, hdr->fields[i][0]);
http_to_camel_case(field_name_2, HTTP_LOWER);
if (strcmp(field_name_1, field_name_2) == 0) {
return hdr->fields[i][1];
}
}
return NULL;
const char *http_get_header_field(const http_hdr *hdr, const char *field_name) {
return http_get_header_field_len(hdr, field_name, strlen(field_name));
}
void http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value) {
size_t len_name = strlen(field_name);
size_t len_value = strlen(field_value);
char *_field_name = malloc(len_name + 1);
char *_field_value = malloc(len_value + 1);
strcpy(_field_name, field_name);
strcpy(_field_value, field_value);
http_to_camel_case(_field_name, HTTP_PRESERVE);
hdr->fields[(int) hdr->field_num][0] = _field_name;
hdr->fields[(int) hdr->field_num][1] = _field_value;
const char *http_get_header_field_len(const http_hdr *hdr, const char *field_name, unsigned long len) {
return http_field_get_value(&hdr->fields[http_get_header_field_num_len(hdr, field_name, len)]);
}
int http_get_header_field_num(const http_hdr *hdr, const char *field_name) {
return http_get_header_field_num_len(hdr, field_name, strlen(field_name));
}
int http_get_header_field_num_len(const http_hdr *hdr, const char *field_name, unsigned long len) {
char field_name_1[256], field_name_2[256];
memcpy(field_name_1, field_name, len);
http_to_camel_case(field_name_1, HTTP_LOWER);
for (int i = 0; i < hdr->field_num; i++) {
strcpy(field_name_2, http_field_get_name(&hdr->fields[i]));
http_to_camel_case(field_name_2, HTTP_LOWER);
if (strcmp(field_name_1, field_name_2) == 0) {
return i;
}
}
return -1;
}
int http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value) {
return http_add_header_field_len(hdr, field_name, strlen(field_name), field_value, strlen(field_value));
}
int http_add_header_field_len(http_hdr *hdr, const char *name, unsigned long name_len, const char *value, unsigned long value_len) {
if (hdr->field_num >= HTTP_MAX_HEADER_FIELD_NUM)
return -1;
http_field *f = &hdr->fields[(int) hdr->field_num];
if (name_len < sizeof(f->normal.name) && value_len < sizeof(f->normal.value)) {
f->type = HTTP_FIELD_NORMAL;
strncpy(f->normal.name, name, name_len);
strncpy(f->normal.value, value, value_len);
http_to_camel_case(f->normal.name, HTTP_PRESERVE);
} else if (name_len < sizeof(f->ex_value.name)) {
f->type = HTTP_FIELD_EX_VALUE;
f->ex_value.value = malloc(value_len + 1);
strncpy(f->ex_value.name, name, name_len);
strncpy(f->ex_value.value, value, value_len);
http_to_camel_case(f->ex_value.name, HTTP_PRESERVE);
} else {
f->type = HTTP_FIELD_EX_NAME;
f->ex_name.name = malloc(name_len + 1);
f->ex_name.value = malloc(value_len + 1);
strncpy(f->ex_name.name, name, name_len);
strncpy(f->ex_name.value, value, value_len);
http_to_camel_case(f->ex_name.name, HTTP_PRESERVE);
}
hdr->field_num++;
return 0;
}
void http_append_to_header_field(http_field *field, const char *value, unsigned long len) {
if (field->type == HTTP_FIELD_NORMAL) {
unsigned long total_len = strlen(field->normal.value) + len + 1;
if (total_len < sizeof(field->normal.value)) {
strncat(field->normal.value, value, len);
} else {
field->type = HTTP_FIELD_EX_VALUE;
char *new = malloc(total_len);
strcpy(new, field->normal.value);
strncat(new, value, len);
field->ex_value.value = new;
}
} else if (field->type == HTTP_FIELD_EX_VALUE) {
field->ex_value.value = realloc(field->ex_value.value, strlen(field->ex_value.value) + len + 1);
strncat(field->ex_value.value, value, len);
} else if (field->type == HTTP_FIELD_EX_NAME) {
field->ex_name.value = realloc(field->ex_name.value, strlen(field->ex_name.value) + len + 1);
strncat(field->ex_name.value, value, len);
}
}
void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode) {
@ -203,12 +313,10 @@ void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode) {
diff = -1;
}
for (; i < hdr->field_num && i >= 0; i += diff) {
strcpy(field_name_2, hdr->fields[i][0]);
strcpy(field_name_2, http_field_get_name(&hdr->fields[i]));
http_to_camel_case(field_name_2, HTTP_LOWER);
if (strcmp(field_name_1, field_name_2) == 0) {
for (int j = i; j < hdr->field_num - 1; j++) {
memcpy(hdr->fields[j], hdr->fields[j + 1], sizeof(hdr->fields[0]));
}
memmove(&hdr->fields[i], &hdr->fields[i + 1], sizeof(hdr->fields[0]) * (hdr->field_num - i));
hdr->field_num--;
if (mode == HTTP_REMOVE_ALL) {
i -= diff;
@ -223,7 +331,8 @@ int http_send_response(sock *client, http_res *res) {
char buf[CLIENT_MAX_HEADER_SIZE];
long off = sprintf(buf, "HTTP/%s %03i %s\r\n", res->version, res->status->code, res->status->msg);
for (int i = 0; i < res->hdr.field_num; i++) {
off += sprintf(buf + off, "%s: %s\r\n", res->hdr.fields[i][0], res->hdr.fields[i][1]);
const http_field *f = &res->hdr.fields[i];
off += sprintf(buf + off, "%s: %s\r\n", http_field_get_name(f), http_field_get_value(f));
}
off += sprintf(buf + off, "\r\n");
if (sock_send(client, buf, off, 0) < 0) {
@ -236,7 +345,8 @@ int http_send_request(sock *server, http_req *req) {
char buf[CLIENT_MAX_HEADER_SIZE];
long off = sprintf(buf, "%s %s HTTP/%s\r\n", req->method, req->uri, req->version);
for (int i = 0; i < req->hdr.field_num; i++) {
off += sprintf(buf + off, "%s: %s\r\n", req->hdr.fields[i][0], req->hdr.fields[i][1]);
const http_field *f = &req->hdr.fields[i];
off += sprintf(buf + off, "%s: %s\r\n", http_field_get_name(f), http_field_get_value(f));
}
off += sprintf(buf + off, "\r\n");
long ret = sock_send(server, buf, off, 0);
@ -314,9 +424,9 @@ const http_doc_info *http_get_status_info(const http_status *status) {
}
int http_get_compression(const http_req *req, const http_res *res) {
char *accept_encoding = http_get_header_field(&req->hdr, "Accept-Encoding");
char *content_type = http_get_header_field(&res->hdr, "Content-Type");
char *content_encoding = http_get_header_field(&res->hdr, "Content-Encoding");
const char *accept_encoding = http_get_header_field(&req->hdr, "Accept-Encoding");
const char *content_type = http_get_header_field(&res->hdr, "Content-Type");
const char *content_encoding = http_get_header_field(&res->hdr, "Content-Encoding");
if (mime_is_compressible(content_type) && content_encoding == NULL && accept_encoding != NULL) {
if (strstr(accept_encoding, "br") != NULL) {
return COMPRESS_BR;

@ -18,6 +18,10 @@
#define HTTP_REMOVE_ALL 1
#define HTTP_REMOVE_LAST 2
#define HTTP_FIELD_NORMAL 0
#define HTTP_FIELD_EX_VALUE 1
#define HTTP_FIELD_EX_NAME 2
#define HTTP_1XX_STR "\x1B[1;32m"
#define HTTP_2XX_STR "\x1B[1;32m"
#define HTTP_3XX_STR "\x1B[1;33m"
@ -30,6 +34,7 @@
#define HTTP_COLOR_ERROR "#C00000"
#define CLIENT_MAX_HEADER_SIZE 8192
#define HTTP_MAX_HEADER_FIELD_NUM 64
#ifndef SERVER_STR
# define SERVER_STR "Necronda"
@ -57,9 +62,28 @@ typedef struct {
const char *doc;
} http_doc_info;
typedef struct {
char type;
union {
struct {
char name[64];
char value[192];
} normal;
struct {
char name[192];
char *value;
} ex_value;
struct {
char *name;
char *value;
} ex_name;
};
} http_field;
typedef struct {
char field_num;
char *fields[64][2];
char last_field_num;
http_field fields[HTTP_MAX_HEADER_FIELD_NUM];
} http_hdr;
typedef struct {
@ -102,6 +126,10 @@ extern const char http_info_icon[];
void http_to_camel_case(char *str, int mode);
const char *http_field_get_name(const http_field *field);
const char *http_field_get_value(const http_field *field);
void http_free_hdr(http_hdr *hdr);
void http_free_req(http_req *req);
@ -112,9 +140,19 @@ int http_receive_request(sock *client, http_req *req);
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) ;
char *http_get_header_field(const http_hdr *hdr, const char *field_name);
const char *http_get_header_field(const http_hdr *hdr, const char *field_name);
void http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value);
const char *http_get_header_field_len(const http_hdr *hdr, const char *field_name, unsigned long len);
int http_get_header_field_num(const http_hdr *hdr, const char *field_name);
int http_get_header_field_num_len(const http_hdr *hdr, const char *field_name, unsigned long len);
int http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value);
int http_add_header_field_len(http_hdr *hdr, const char *name, unsigned long name_len, const char *value, unsigned long value_len);
void http_append_to_header_field(http_field *field, const char *value, unsigned long len);
void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode);

@ -35,7 +35,7 @@ int rev_proxy_request_header(http_req *req, int enc) {
http_remove_header_field(&req->hdr, "Connection", HTTP_REMOVE_ALL);
http_add_header_field(&req->hdr, "Connection", "keep-alive");
char *via = http_get_header_field(&req->hdr, "Via");
const char *via = http_get_header_field(&req->hdr, "Via");
sprintf(buf1, "HTTP/%s %s", req->version, SERVER_NAME);
if (via == NULL) {
http_add_header_field(&req->hdr, "Via", buf1);
@ -49,8 +49,8 @@ int rev_proxy_request_header(http_req *req, int enc) {
http_add_header_field(&req->hdr, "Via", buf2);
}
char *host = http_get_header_field(&req->hdr, "Host");
char *forwarded = http_get_header_field(&req->hdr, "Forwarded");
const char *host = http_get_header_field(&req->hdr, "Host");
const char *forwarded = http_get_header_field(&req->hdr, "Forwarded");
int client_ipv6 = strchr(client_addr_str, ':') != NULL;
int server_ipv6 = strchr(server_addr_str, ':') != NULL;
@ -74,7 +74,7 @@ int rev_proxy_request_header(http_req *req, int enc) {
http_add_header_field(&req->hdr, "Forwarded", buf2);
}
char *xff = http_get_header_field(&req->hdr, "X-Forwarded-For");
const char *xff = http_get_header_field(&req->hdr, "X-Forwarded-For");
if (xff == NULL) {
http_add_header_field(&req->hdr, "X-Forwarded-For", client_addr_str);
} else {
@ -83,7 +83,7 @@ int rev_proxy_request_header(http_req *req, int enc) {
http_add_header_field(&req->hdr, "X-Forwarded-For", buf1);
}
char *xfh = http_get_header_field(&req->hdr, "X-Forwarded-Host");
const char *xfh = http_get_header_field(&req->hdr, "X-Forwarded-Host");
if (xfh == NULL) {
if (forwarded == NULL) {
http_add_header_field(&req->hdr, "X-Forwarded-Host", host);
@ -104,7 +104,7 @@ int rev_proxy_request_header(http_req *req, int enc) {
}
}
char *xfp = http_get_header_field(&req->hdr, "X-Forwarded-Proto");
const char *xfp = http_get_header_field(&req->hdr, "X-Forwarded-Proto");
if (xfp == NULL) {
if (forwarded == NULL) {
http_add_header_field(&req->hdr, "X-Forwarded-Proto", enc ? "https" : "http");
@ -133,7 +133,7 @@ int rev_proxy_response_header(http_req *req, http_res *res, host_config *conf) {
char buf2[256];
int p_len;
char *via = http_get_header_field(&res->hdr, "Via");
const char *via = http_get_header_field(&res->hdr, "Via");
p_len = snprintf(buf1, sizeof(buf1), "HTTP/%s %s", req->version, SERVER_NAME);
if (p_len < 0 || p_len >= sizeof(buf1)) {
print(ERR_STR "Appended part of header field 'Via' too long" CLR_STR);
@ -151,7 +151,7 @@ int rev_proxy_response_header(http_req *req, http_res *res, host_config *conf) {
http_add_header_field(&res->hdr, "Via", buf2);
}
char *location = http_get_header_field(&res->hdr, "Location");
const char *location = http_get_header_field(&res->hdr, "Location");
if (location != NULL) {
char *hostnames[] = {conf->name, conf->rev_proxy.hostname};
for (int i = 0; i < sizeof(hostnames) / sizeof(hostnames[0]); i++) {
@ -307,7 +307,7 @@ int rev_proxy_init(http_req *req, http_res *res, http_status_ctx *ctx, host_conf
goto proxy_err;
}
char *content_length = http_get_header_field(&req->hdr, "Content-Length");
const char *content_length = http_get_header_field(&req->hdr, "Content-Length");
if (content_length != NULL) {
unsigned long content_len = strtoul(content_length, NULL, 10);
if (client->buf_len - client->buf_off > 0) {
@ -455,7 +455,7 @@ int rev_proxy_init(http_req *req, http_res *res, http_status_ctx *ctx, host_conf
int rev_proxy_send(sock *client, unsigned long len_to_send, int flags) {
// TODO handle websockets
long ret;
long ret = 0;
char buffer[CHUNK_SIZE];
char comp_out[CHUNK_SIZE];
char buf[256];
@ -498,6 +498,7 @@ int rev_proxy_send(sock *client, unsigned long len_to_send, int flags) {
if (len_to_send == 0 && (flags & REV_PROXY_COMPRESS)) {
finish_comp = 1;
len = 0;
ptr = NULL;
goto out;
finish:
compress_free(&comp_ctx);
@ -519,8 +520,7 @@ int rev_proxy_send(sock *client, unsigned long len_to_send, int flags) {
long buf_len = len;
if (flags & REV_PROXY_COMPRESS) {
avail_out = sizeof(comp_out);
compress_compress(&comp_ctx, next_in + len - avail_in, &avail_in, comp_out, &avail_out,
finish_comp);
compress_compress(&comp_ctx, next_in + len - avail_in, &avail_in, comp_out, &avail_out, finish_comp);
ptr = comp_out;
buf_len = (int) (sizeof(comp_out) - avail_out);
snd_len += (long) (len - avail_in);

@ -136,10 +136,39 @@ int mime_is_compressible(const char *type) {
int strcpy_rem_webroot(char *dst, const char *src, long len, const char *webroot) {
strncpy(dst, src, len);
if (webroot == NULL) return 0;
if (webroot == NULL)
return 0;
char *pos;
const unsigned long webroot_len = strlen(webroot);
if (webroot_len == 0)
return 0;
while ((pos = strstr(dst, webroot)) != NULL) {
strcpy(pos, pos + strlen(webroot));
strcpy(pos, pos + webroot_len);
}
return 0;
}
int str_trim(char **start, char **end) {
if (start == NULL || end == NULL || *start == NULL || *end == NULL)
return -1;
(*end)--;
while (*start[0] == ' ' || *start[0] == '\t' || *start[0] == '\r' || *start[0] == '\n') (*start)++;
while (*end[0] == ' ' || *end[0] == '\t' || *end[0] == '\r' || *end[0] == '\n') (*end)--;
(*end)++;
return 0;
}
int str_trim_lws(char **start, char **end) {
if (start == NULL || end == NULL || *start == NULL || *end == NULL)
return -1;
(*end)--;
while (*start[0] == ' ' || *start[0] == '\t') (*start)++;
while (*end[0] == ' ' || *end[0] == '\t') (*end)--;
(*end)++;
return 0;
}

@ -42,4 +42,8 @@ int mime_is_compressible(const char *type);
int strcpy_rem_webroot(char *dst, const char *str, long len, const char *webroot);
int str_trim(char **start, char **end);
int str_trim_lws(char **start, char **end);
#endif //NECRONDA_SERVER_UTILS_H