1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
|
/* copyright (c) 2022 - 2025 grunfink et al. / MIT license */
#ifndef _XS_HTTPD_H
#define _XS_HTTPD_H
xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size);
void xs_httpd_response(FILE *f, int status, const char *status_text,
const xs_dict *headers, const xs_val *body, int b_size);
#ifdef XS_IMPLEMENTATION
xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size)
/* processes an httpd connection */
{
xs *q_vars = NULL;
xs *p_vars = NULL;
xs *l1;
const char *v;
char *saveptr;
xs_socket_timeout(fileno(f), 2.0, 0.0);
/* read the first line and split it */
l1 = xs_strip_i(xs_readline(f));
char *raw_path;
const char *mtd;
const char *proto;
if (!(mtd = strtok_r(l1, " ", &saveptr)) ||
!(raw_path = strtok_r(NULL, " ", &saveptr)) ||
!(proto = strtok_r(NULL, " ", &saveptr)) ||
strtok_r(NULL, " ", &saveptr))
return NULL;
if (!xs_is_string(mtd) || !xs_is_string(raw_path) || !xs_is_string(proto))
return NULL;
xs_dict *req = xs_dict_new();
req = xs_dict_append(req, "method", mtd);
req = xs_dict_append(req, "raw_path", raw_path);
req = xs_dict_append(req, "proto", proto);
{
char *q = strchr(raw_path, '?');
/* get the variables */
if (q) {
*q++ = '\0';
q_vars = xs_url_vars(q);
}
/* store the path */
req = xs_dict_append(req, "path", raw_path);
}
/* read the headers */
for (;;) {
xs *l;
l = xs_strip_i(xs_readline(f));
/* done with the header? */
if (strcmp(l, "") == 0)
break;
/* split header and content */
char *cnt = strchr(l, ':');
if (!cnt)
continue;
*cnt++ = '\0';
cnt += strspn(cnt, " \r\n\t\v\f");
l = xs_rstrip_chars_i(l, " \r\n\t\v\f");
if (!xs_is_string(cnt))
continue;
req = xs_dict_append(req, xs_tolower_i(l), cnt);
}
xs_socket_timeout(fileno(f), 5.0, 0.0);
if ((v = xs_dict_get(req, "content-length")) != NULL) {
/* if it has a payload, load it */
*p_size = atoi(v);
*payload = xs_read(f, p_size);
}
else if ((v = xs_dict_get(req, "transfer-encoding")) != NULL &&
xs_startswith(v, "chunked")) {
/* handle chunked transfer encoding */
xs_str *body = xs_str_new(NULL);
for (;;) {
xs *line = xs_strip_i(xs_readline(f));
/* parse chunk size (in hex) */
int chunk_size = strtol(line, NULL, 16);
if (chunk_size <= 0)
break;
/* read chunk data */
xs *chunk = xs_read(f, &chunk_size);
if (chunk == NULL)
break;
body = xs_append_m(body, chunk, chunk_size);
/* read trailing \r\n after chunk data */
xs *dummy = xs_readline(f);
}
*p_size = xs_size(body) - 1; /* subtract trailing null */
*payload = body;
}
v = xs_dict_get(req, "content-type");
if (*payload && v && strcmp(v, "application/x-www-form-urlencoded") == 0) {
p_vars = xs_url_vars(*payload);
}
else
if (*payload && v && xs_startswith(v, "multipart/form-data")) {
p_vars = xs_multipart_form_data(*payload, *p_size, v);
}
else
p_vars = xs_dict_new();
req = xs_dict_append(req, "q_vars", q_vars);
req = xs_dict_append(req, "p_vars", p_vars);
if (errno)
req = xs_free(req);
return req;
}
void xs_httpd_response(FILE *f, int status, const char *status_text,
const xs_dict *headers, const xs_val *body, int b_size)
/* sends an httpd response */
{
fprintf(f, "HTTP/1.1 %d %s\r\n", status, status_text ? status_text : "");
const xs_str *k;
const xs_val *v;
xs_dict_foreach(headers, k, v) {
fprintf(f, "%s: %s\r\n", k, v);
}
if (b_size != 0)
fprintf(f, "content-length: %d\r\n", b_size);
fprintf(f, "\r\n");
if (body != NULL && b_size != 0)
fwrite(body, b_size, 1, f);
}
#endif /* XS_IMPLEMENTATION */
#endif /* XS_HTTPD_H */
|