2 * Copyright (c) 2023 Omar Polo <op@omarpolo.com>
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.
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.
35 #define nitems(x) (sizeof(x)/sizeof(x[0]))
39 writeall(struct reswriter *res, const char *buf, size_t buflen)
44 for (off = 0; off < buflen; off += nw)
45 if ((nw = write(res->fd, buf + off, buflen - off)) == 0 ||
48 log_warnx("Unexpected EOF");
59 http_parse(struct request *req, int fd)
63 int done = 0, first = 1;
64 char *s, *t, *line, *endln;
65 const char *errstr, *m;
67 memset(req, 0, sizeof(*req));
70 if (req->len == sizeof(req->buf)) {
71 log_warnx("not enough space");
75 avail = sizeof(req->buf) - req->len;
76 nr = read(fd, req->buf + req->len, avail);
78 if (errno == EAGAIN || errno == EINTR)
81 log_warnx("Unexpected EOF");
88 while ((endln = memmem(req->buf, req->len, "\r\n", 2))) {
90 if (endln == req->buf)
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'))
101 if (!strncmp("GET ", line, 4)) {
102 req->method = METHOD_GET;
104 } else if (!strncmp("POST ", line, 5)) {
105 req->method = METHOD_POST;
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;
120 log_warnx("unknown http version: %s",
126 if (!strncasecmp(line, "Content-Length:", 15)) {
128 line += strspn(line, " \t");
129 req->clen = strtonum(line, 0, LONG_MAX,
131 if (errstr != NULL) {
132 log_warnx("content-length is %s: %s",
138 len = endln - req->buf + 2;
139 memmove(req->buf, req->buf + len, req->len - len);
146 if (req->method == METHOD_GET)
148 else if (req->method == METHOD_POST)
152 log_debug("< %s %s", m, req->path);
158 http_read(struct request *req, int fd)
167 if (req->clen > sizeof(req->buf) - 1)
169 if (req->len == req->clen) {
170 req->buf[req->len] = '\0';
173 if (req->len > req->clen) {
174 log_warnx("got more data than what advertised! (%zu vs %zu)",
175 req->len, req->clen);
179 left = req->clen - req->len;
181 nr = read(fd, req->buf + req->len, left);
183 if (nr == -1 && errno == EAGAIN)
186 log_warnx("Unexpected EOF");
195 req->buf[req->len] = '\0';
200 http_response_init(struct reswriter *res, struct request *req, int fd)
202 memset(res, 0, sizeof(*res));
204 res->chunked = req->version == HTTP_1_1;
208 http_reply(struct reswriter *res, int code, const char *reason,
211 const char *location = NULL;
214 res->len = 0; /* discard any leftover from reading */
216 log_debug("> %d %s", code, reason);
218 if (code >= 300 && code < 400) {
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"
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))
241 if (writeall(res, res->buf, r) == -1)
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)
257 http_flush(struct reswriter *res)
272 if (writeall(res, res->buf, res->len) == -1)
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");
285 memset(iov, 0, sizeof(iov));
287 iov[0].iov_base = buf;
290 iov[1].iov_base = res->buf;
291 iov[1].iov_len = res->len;
293 iov[2].iov_base = "\r\n";
296 tot = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len;
298 nw = writev(res->fd, iov, nitems(iov));
300 if (nw == -1 && errno == EAGAIN)
303 log_warnx("Unexpected EOF");
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;
317 nw -= iov[i].iov_len;
327 http_write(struct reswriter *res, const char *d, size_t len)
335 avail = sizeof(res->buf) - res->len;
339 memcpy(res->buf + res->len, d, avail);
343 if (res->len == sizeof(res->buf)) {
344 if (http_flush(res) == -1)
353 http_writes(struct reswriter *res, const char *str)
355 return http_write(res, str, strlen(str));
359 http_fmt(struct reswriter *res, const char *fmt, ...)
366 r = vasprintf(&str, fmt, ap);
370 log_warn("vasprintf");
375 r = http_write(res, str, r);
381 http_urlescape(struct reswriter *res, const char *str)
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");
397 if (http_write(res, tmp, r) == -1)
399 } else if (http_write(res, str, 1) == -1)
407 http_htmlescape(struct reswriter *res, const char *str)
411 for (; *str; ++str) {
414 r = http_writes(res, "<");
417 r = http_writes(res, ">");
420 r = http_writes(res, ">");
423 r = http_writes(res, """);
426 r = http_writes(res, "'");
429 r = http_write(res, str, 1);
441 http_close(struct reswriter *res)
446 return writeall(res, "0\r\n\r\n", 5);
450 http_free_request(struct request *req)