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