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 "http.h"
31 #include "log.h"
32 #include "xmalloc.h"
34 #ifndef nitems
35 #define nitems(x) (sizeof(x)/sizeof(x[0]))
36 #endif
38 static int
39 writeall(struct reswriter *res, const char *buf, size_t buflen)
40 {
41 ssize_t nw;
42 size_t off;
44 for (off = 0; off < buflen; off += nw)
45 if ((nw = write(res->fd, buf + off, buflen - off)) == 0 ||
46 nw == -1) {
47 if (nw == 0)
48 log_warnx("Unexpected EOF");
49 else
50 log_warn("write");
51 res->err = 1;
52 return -1;
53 }
55 return 0;
56 }
58 int
59 http_parse(struct request *req, int fd)
60 {
61 ssize_t nr;
62 size_t avail, len;
63 int done = 0, first = 1;
64 char *s, *t, *line, *endln;
65 const char *errstr, *m;
67 memset(req, 0, sizeof(*req));
69 while (!done) {
70 if (req->len == sizeof(req->buf)) {
71 log_warnx("not enough space");
72 return -1;
73 }
75 avail = sizeof(req->buf) - req->len;
76 nr = read(fd, req->buf + req->len, avail);
77 if (nr <= 0) {
78 if (errno == EAGAIN || errno == EINTR)
79 continue;
80 if (nr == 0)
81 log_warnx("Unexpected EOF");
82 else
83 log_warn("read");
84 return -1;
85 }
86 req->len += nr;
88 while ((endln = memmem(req->buf, req->len, "\r\n", 2))) {
89 line = req->buf;
90 if (endln == req->buf)
91 done = 1;
93 len = endln - req->buf + 2;
94 while (len > 0 && (line[len - 1] == '\r' ||
95 line[len - 1] == '\n' || line[len - 1] == ' ' ||
96 line[len - 1] == '\t'))
97 line[--len] = '\0';
99 if (first) {
100 first = 0;
101 if (!strncmp("GET ", line, 4)) {
102 req->method = METHOD_GET;
103 s = line + 4;
104 } else if (!strncmp("POST ", line, 5)) {
105 req->method = METHOD_POST;
106 s = line + 5;
109 t = strchr(s, ' ');
110 if (t == NULL)
111 t = s;
112 if (*t != '\0')
113 *t++ = '\0';
114 req->path = xstrdup(s);
115 if (!strcmp("HTTP/1.0", t))
116 req->version = HTTP_1_0;
117 else if (!strcmp("HTTP/1.1", t))
118 req->version = HTTP_1_1;
119 else {
120 log_warnx("unknown http version: %s",
121 t);
122 return -1;
126 if (!strncasecmp(line, "Content-Length:", 15)) {
127 line += 15;
128 line += strspn(line, " \t");
129 req->clen = strtonum(line, 0, LONG_MAX,
130 &errstr);
131 if (errstr != NULL) {
132 log_warnx("content-length is %s: %s",
133 errstr, line);
134 return -1;
138 len = endln - req->buf + 2;
139 memmove(req->buf, req->buf + len, req->len - len);
140 req->len -= len;
141 if (done)
142 break;
146 if (req->method == METHOD_GET)
147 m = "GET";
148 else if (req->method == METHOD_POST)
149 m = "POST";
150 else
151 m = "unknown";
152 log_debug("< %s %s", m, req->path);
154 return 0;
157 int
158 http_read(struct request *req, int fd)
160 size_t left;
161 ssize_t nr;
163 /* drop \r\n */
164 if (req->len > 2)
165 req->len -= 2;
167 if (req->clen > sizeof(req->buf) - 1)
168 return -1;
169 if (req->len == req->clen) {
170 req->buf[req->len] = '\0';
171 return 0;
173 if (req->len > req->clen) {
174 log_warnx("got more data than what advertised! (%zu vs %zu)",
175 req->len, req->clen);
176 return -1;
179 left = req->clen - req->len;
180 while (left > 0) {
181 nr = read(fd, req->buf + req->len, left);
182 if (nr <= 0) {
183 if (nr == -1 && errno == EAGAIN)
184 continue;
185 if (nr == 0)
186 log_warnx("Unexpected EOF");
187 else
188 log_warn("read");
189 return -1;
191 req->len += nr;
192 left -= nr;
195 req->buf[req->len] = '\0';
196 return 0;
199 void
200 http_response_init(struct reswriter *res, struct request *req, int fd)
202 memset(res, 0, sizeof(*res));
203 res->fd = fd;
204 res->chunked = req->version == HTTP_1_1;
207 int
208 http_reply(struct reswriter *res, int code, const char *reason,
209 const char *ctype)
211 const char *location = NULL;
212 int r;
214 res->len = 0; /* discard any leftover from reading */
216 log_debug("> %d %s", code, reason);
218 if (code >= 300 && code < 400) {
219 location = ctype;
220 ctype = "text/html;charset=UTF-8";
223 r = snprintf(res->buf, sizeof(res->buf), "HTTP/1.1 %d %s\r\n"
224 "Connection: close\r\n"
225 "Cache-Control: no-store\r\n"
226 "%s%s%s"
227 "%s%s%s"
228 "%s"
229 "\r\n",
230 code, reason,
231 ctype == NULL ? "" : "Content-Type: ",
232 ctype == NULL ? "" : ctype,
233 ctype == NULL ? "" : "\r\n",
234 location == NULL ? "" : "Location: ",
235 location == NULL ? "" : location,
236 location == NULL ? "" : "\r\n",
237 res->chunked ? "Transfer-Encoding: chunked\r\n" : "");
238 if (r < 0 || (size_t)r >= sizeof(res->buf))
239 return -1;
241 if (writeall(res, res->buf, r) == -1)
242 return -1;
244 if (location) {
245 if (http_writes(res, "<a href='") == -1 ||
246 http_htmlescape(res, location) == -1 ||
247 http_writes(res, "'>") == -1 ||
248 http_htmlescape(res, reason) == -1 ||
249 http_writes(res, "</a>") == -1)
250 return -1;
253 return 0;
256 int
257 http_flush(struct reswriter *res)
259 struct iovec iov[3];
260 char buf[64];
261 ssize_t nw;
262 size_t i, tot;
263 int r;
265 if (res->err)
266 return -1;
268 if (res->len == 0)
269 return 0;
271 if (!res->chunked) {
272 if (writeall(res, res->buf, res->len) == -1)
273 return -1;
274 res->len = 0;
275 return 0;
278 r = snprintf(buf, sizeof(buf), "%zx\r\n", res->len);
279 if (r < 0 || (size_t)r >= sizeof(buf)) {
280 log_warn("snprintf failed");
281 res->err = 1;
282 return -1;
285 memset(iov, 0, sizeof(iov));
287 iov[0].iov_base = buf;
288 iov[0].iov_len = r;
290 iov[1].iov_base = res->buf;
291 iov[1].iov_len = res->len;
293 iov[2].iov_base = "\r\n";
294 iov[2].iov_len = 2;
296 tot = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len;
297 while (tot > 0) {
298 nw = writev(res->fd, iov, nitems(iov));
299 if (nw <= 0) {
300 if (nw == -1 && errno == EAGAIN)
301 continue;
302 if (nw == 0)
303 log_warnx("Unexpected EOF");
304 else
305 log_warn("writev");
306 res->err = 1;
307 return -1;
310 tot -= nw;
311 for (i = 0; i < nitems(iov); ++i) {
312 if (nw < iov[i].iov_len) {
313 iov[i].iov_base += nw;
314 iov[i].iov_len -= nw;
315 break;
317 nw -= iov[i].iov_len;
318 iov[i].iov_len = 0;
322 res->len = 0;
323 return 0;
326 int
327 http_write(struct reswriter *res, const char *d, size_t len)
329 size_t avail;
331 if (res->err)
332 return -1;
334 while (len > 0) {
335 avail = sizeof(res->buf) - res->len;
336 if (avail > len)
337 avail = len;
339 memcpy(res->buf + res->len, d, avail);
340 res->len += avail;
341 len -= avail;
342 d += avail;
343 if (res->len == sizeof(res->buf)) {
344 if (http_flush(res) == -1)
345 return -1;
349 return 0;
352 int
353 http_writes(struct reswriter *res, const char *str)
355 return http_write(res, str, strlen(str));
358 int
359 http_fmt(struct reswriter *res, const char *fmt, ...)
361 va_list ap;
362 char *str;
363 int r;
365 va_start(ap, fmt);
366 r = vasprintf(&str, fmt, ap);
367 va_end(ap);
369 if (r == -1) {
370 log_warn("vasprintf");
371 res->err = 1;
372 return -1;
375 r = http_write(res, str, r);
376 free(str);
377 return r;
380 int
381 http_urlescape(struct reswriter *res, const char *str)
383 int r;
384 char tmp[4];
386 for (; *str; ++str) {
387 if (iscntrl((unsigned char)*str) ||
388 isspace((unsigned char)*str) ||
389 *str == '\'' || *str == '"' || *str == '\\') {
390 r = snprintf(tmp, sizeof(tmp), "%%%2X",
391 (unsigned char)*str);
392 if (r < 0 || (size_t)r >= sizeof(tmp)) {
393 log_warn("snprintf failed");
394 res->err = 1;
395 return -1;
397 if (http_write(res, tmp, r) == -1)
398 return -1;
399 } else if (http_write(res, str, 1) == -1)
400 return -1;
403 return 0;
406 int
407 http_htmlescape(struct reswriter *res, const char *str)
409 int r;
411 for (; *str; ++str) {
412 switch (*str) {
413 case '<':
414 r = http_writes(res, "&lt;");
415 break;
416 case '>':
417 r = http_writes(res, "&gt;");
418 break;
419 case '&':
420 r = http_writes(res, "&gt;");
421 break;
422 case '"':
423 r = http_writes(res, "&quot;");
424 break;
425 case '\'':
426 r = http_writes(res, "&apos;");
427 break;
428 default:
429 r = http_write(res, str, 1);
430 break;
433 if (r == -1)
434 return -1;
437 return 0;
440 int
441 http_close(struct reswriter *res)
443 if (!res->chunked)
444 return 0;
446 return writeall(res, "0\r\n\r\n", 5);
449 void
450 http_free_request(struct request *req)
452 free(req->path);
453 free(req->ctype);