Blob


1 /*
2 * Copyright (c) 2023 Omar Polo <op@omarpolo.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include "config.h"
19 #include <sys/uio.h>
21 #include <ctype.h>
22 #include <errno.h>
23 #include <limits.h>
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
30 #include "bufio.h"
31 #include "http.h"
32 #include "log.h"
33 #include "xmalloc.h"
35 #ifndef nitems
36 #define nitems(x) (sizeof(x)/sizeof(x[0]))
37 #endif
39 int
40 http_init(struct client *clt, int fd)
41 {
42 memset(clt, 0, sizeof(*clt));
43 if (bufio_init(&clt->bio) == -1)
44 return -1;
45 bufio_set_fd(&clt->bio, fd);
46 return 0;
47 }
49 int
50 http_parse(struct client *clt)
51 {
52 struct buffer *rbuf = &clt->bio.rbuf;
53 struct request *req = &clt->req;
54 size_t len;
55 uint8_t *endln;
56 char *s, *t, *line;
57 const char *errstr, *m;
59 while (!clt->reqdone) {
60 endln = memmem(rbuf->buf, rbuf->len, "\r\n", 2);
61 if (endln == NULL) {
62 errno = EAGAIN;
63 return -1;
64 }
66 line = rbuf->buf;
67 if (endln == rbuf->buf)
68 clt->reqdone = 1;
70 len = endln - rbuf->buf + 2;
71 while (len > 0 && (line[len - 1] == '\r' ||
72 line[len - 1] == '\n' || line[len - 1] == ' ' ||
73 line[len - 1] == '\t'))
74 line[--len] = '\0';
76 /* first line */
77 if (clt->req.method == METHOD_UNKNOWN) {
78 if (!strncmp("GET ", line, 4)) {
79 req->method = METHOD_GET;
80 s = line + 4;
81 } else if (!strncmp("POST ", line, 5)) {
82 req->method = METHOD_POST;
83 s = line + 5;
84 } else {
85 errno = EINVAL;
86 return -1;
87 }
89 t = strchr(s, ' ');
90 if (t == NULL)
91 t = s;
92 if (*t != '\0')
93 *t++ = '\0';
94 clt->req.path = xstrdup(s);
95 if (!strcmp(t, "HTTP/1.0"))
96 clt->req.version = HTTP_1_0;
97 else if (!strcmp(t, "HTTP/1.1")) {
98 clt->req.version = HTTP_1_1;
99 clt->chunked = 1;
100 } else {
101 log_warnx("unknown http version %s", t);
102 errno = EINVAL;
103 return -1;
107 if (!strncasecmp(line, "Content-Length:", 15)) {
108 line += 15;
109 line += strspn(line, " \t");
110 clt->req.clen = strtonum(line, 0, LONG_MAX,
111 &errstr);
112 if (errstr) {
113 log_warnx("content-length is %s: %s",
114 errstr, line);
115 errno = EINVAL;
116 return -1;
120 buf_drain(rbuf, endln - rbuf->buf + 2);
123 if (req->method == METHOD_GET)
124 m = "GET";
125 else if (req->method == METHOD_POST)
126 m = "POST";
127 else
128 m = "unknown";
129 log_debug("< %s %s", m, req->path);
131 return 0;
134 int
135 http_read(struct client *clt)
137 struct request *req = &clt->req;
138 size_t left;
139 size_t nr;
141 if (req->clen > sizeof(clt->buf) - 1) {
142 log_warnx("POST has more data then what can be accepted");
143 return -1;
146 /* clients may have sent more data than advertised */
147 if (req->clen < clt->len)
148 left = 0;
149 else
150 left = req->clen - clt->len;
152 if (left > 0) {
153 nr = bufio_drain(&clt->bio, clt->buf + clt->len, left);
154 clt->len += nr;
155 if (nr < left) {
156 errno = EAGAIN;
157 return -1;
161 clt->buf[clt->len] = '\0';
162 while (clt->len > 0 && (clt->buf[clt->len - 1] == '\r' ||
163 (clt->buf[clt->len - 1] == '\n')))
164 clt->buf[--clt->len] = '\0';
166 return 0;
169 int
170 http_reply(struct client *clt, int code, const char *reason, const char *ctype)
172 const char *version, *location = NULL;
173 int r;
175 log_debug("> %d %s", code, reason);
177 if (code >= 300 && code < 400) {
178 location = ctype;
179 ctype = "text/html;charset=UTF-8";
182 version = "HTTP/1.1";
183 if (clt->req.version == HTTP_1_0)
184 version = "HTTP/1.0";
186 r = bufio_compose_fmt(&clt->bio, "%s %d %s\r\n"
187 "Connection: close\r\n"
188 "Cache-Control: no-store\r\n"
189 "%s%s%s"
190 "%s%s%s"
191 "%s"
192 "\r\n",
193 version, code, reason,
194 ctype == NULL ? "" : "Content-Type: ",
195 ctype == NULL ? "" : ctype,
196 ctype == NULL ? "" : "\r\n",
197 location == NULL ? "" : "Location: ",
198 location == NULL ? "" : location,
199 location == NULL ? "" : "\r\n",
200 clt->chunked ? "Transfer-Encoding: chunked\r\n" : "");
201 if (r == -1) {
202 clt->err = 1;
203 return -1;
206 if (location) {
207 if (http_writes(clt, "<a href='") == -1 ||
208 http_htmlescape(clt, location) == -1 ||
209 http_writes(clt, "'>") == -1 ||
210 http_htmlescape(clt, reason) == -1 ||
211 http_writes(clt, "</a>") == -1)
212 return -1;
215 bufio_set_chunked(&clt->bio, clt->chunked);
216 return 0;
219 int
220 http_flush(struct client *clt)
222 if (clt->err)
223 return -1;
225 if (clt->len == 0)
226 return 0;
228 if (bufio_compose(&clt->bio, clt->buf, clt->len) == -1) {
229 clt->err = 1;
230 return -1;
233 clt->len = 0;
235 return 0;
238 int
239 http_write(struct client *clt, const char *d, size_t len)
241 size_t avail;
243 if (clt->err)
244 return -1;
246 while (len > 0) {
247 avail = sizeof(clt->buf) - clt->len;
248 if (avail > len)
249 avail = len;
251 memcpy(clt->buf + clt->len, d, avail);
252 clt->len += avail;
253 len -= avail;
254 d += avail;
255 if (clt->len == sizeof(clt->buf)) {
256 if (http_flush(clt) == -1)
257 return -1;
261 return 0;
264 int
265 http_writes(struct client *clt, const char *str)
267 return http_write(clt, str, strlen(str));
270 int
271 http_fmt(struct client *clt, const char *fmt, ...)
273 va_list ap;
274 char *str;
275 int r;
277 va_start(ap, fmt);
278 r = vasprintf(&str, fmt, ap);
279 va_end(ap);
281 if (r == -1) {
282 log_warn("vasprintf");
283 clt->err = 1;
284 return -1;
287 r = http_write(clt, str, r);
288 free(str);
289 return r;
292 int
293 http_urlescape(struct client *clt, const char *str)
295 int r;
296 char tmp[4];
298 for (; *str; ++str) {
299 if (iscntrl((unsigned char)*str) ||
300 isspace((unsigned char)*str) ||
301 *str == '\'' || *str == '"' || *str == '\\') {
302 r = snprintf(tmp, sizeof(tmp), "%%%2X",
303 (unsigned char)*str);
304 if (r < 0 || (size_t)r >= sizeof(tmp)) {
305 log_warn("snprintf failed");
306 clt->err = 1;
307 return -1;
309 if (http_write(clt, tmp, r) == -1)
310 return -1;
311 } else if (http_write(clt, str, 1) == -1)
312 return -1;
315 return 0;
318 int
319 http_htmlescape(struct client *clt, const char *str)
321 int r;
323 for (; *str; ++str) {
324 switch (*str) {
325 case '<':
326 r = http_writes(clt, "&lt;");
327 break;
328 case '>':
329 r = http_writes(clt, "&gt;");
330 break;
331 case '&':
332 r = http_writes(clt, "&gt;");
333 break;
334 case '"':
335 r = http_writes(clt, "&quot;");
336 break;
337 case '\'':
338 r = http_writes(clt, "&apos;");
339 break;
340 default:
341 r = http_write(clt, str, 1);
342 break;
345 if (r == -1)
346 return -1;
349 return 0;
352 int
353 http_close(struct client *clt)
355 if (clt->err)
356 return -1;
357 if (clt->len != 0 && http_flush(clt) == -1)
358 return -1;
359 if (bufio_compose(&clt->bio, NULL, 0) == -1)
360 clt->err = 1;
361 return (clt->err ? -1 : 0);
364 void
365 http_free(struct client *clt)
367 free(clt->req.path);
368 free(clt->req.ctype);
369 free(clt->req.body);
370 bufio_free(&clt->bio);