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