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.
36 #define nitems(x) (sizeof(x)/sizeof(x[0]))
40 http_init(struct client *clt, int fd)
42 memset(clt, 0, sizeof(*clt));
43 if (bufio_init(&clt->bio) == -1)
45 bufio_set_fd(&clt->bio, fd);
50 http_parse(struct client *clt)
52 struct buffer *rbuf = &clt->bio.rbuf;
53 struct request *req = &clt->req;
57 const char *errstr, *m;
59 while (!clt->reqdone) {
60 endln = memmem(rbuf->buf, rbuf->len, "\r\n", 2);
67 if (endln == rbuf->buf)
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'))
77 if (clt->req.method == METHOD_UNKNOWN) {
78 if (!strncmp("GET ", line, 4)) {
79 req->method = METHOD_GET;
81 } else if (!strncmp("POST ", line, 5)) {
82 req->method = METHOD_POST;
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;
101 log_warnx("unknown http version %s", t);
107 if (!strncasecmp(line, "Content-Length:", 15)) {
109 line += strspn(line, " \t");
110 clt->req.clen = strtonum(line, 0, LONG_MAX,
113 log_warnx("content-length is %s: %s",
120 buf_drain(rbuf, endln - rbuf->buf + 2);
123 if (req->method == METHOD_GET)
125 else if (req->method == METHOD_POST)
129 log_debug("< %s %s", m, req->path);
135 http_read(struct client *clt)
137 struct request *req = &clt->req;
141 if (req->clen > sizeof(clt->buf) - 1) {
142 log_warnx("POST has more data then what can be accepted");
146 /* clients may have sent more data than advertised */
147 if (req->clen < clt->len)
150 left = req->clen - clt->len;
153 nr = bufio_drain(&clt->bio, clt->buf + clt->len, left);
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';
170 http_reply(struct client *clt, int code, const char *reason, const char *ctype)
172 const char *version, *location = NULL;
175 log_debug("> %d %s", code, reason);
177 if (code >= 300 && code < 400) {
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"
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" : "");
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)
215 bufio_set_chunked(&clt->bio, clt->chunked);
220 http_flush(struct client *clt)
228 if (bufio_compose(&clt->bio, clt->buf, clt->len) == -1) {
239 http_write(struct client *clt, const char *d, size_t len)
247 avail = sizeof(clt->buf) - clt->len;
251 memcpy(clt->buf + clt->len, d, avail);
255 if (clt->len == sizeof(clt->buf)) {
256 if (http_flush(clt) == -1)
265 http_writes(struct client *clt, const char *str)
267 return http_write(clt, str, strlen(str));
271 http_fmt(struct client *clt, const char *fmt, ...)
278 r = vasprintf(&str, fmt, ap);
282 log_warn("vasprintf");
287 r = http_write(clt, str, r);
293 http_urlescape(struct client *clt, const char *str)
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");
309 if (http_write(clt, tmp, r) == -1)
311 } else if (http_write(clt, str, 1) == -1)
319 http_htmlescape(struct client *clt, const char *str)
323 for (; *str; ++str) {
326 r = http_writes(clt, "<");
329 r = http_writes(clt, ">");
332 r = http_writes(clt, ">");
335 r = http_writes(clt, """);
338 r = http_writes(clt, "'");
341 r = http_write(clt, str, 1);
353 http_close(struct client *clt)
357 if (clt->len != 0 && http_flush(clt) == -1)
359 if (bufio_compose(&clt->bio, NULL, 0) == -1)
361 return (clt->err ? -1 : 0);
365 http_free(struct client *clt)
368 free(clt->req.ctype);
370 bufio_free(&clt->bio);