Blob


1 /*
2 * This is free and unencumbered software released into the public domain.
3 *
4 * Anyone is free to copy, modify, publish, use, compile, sell, or
5 * distribute this software, either in source code form or as a compiled
6 * binary, for any purpose, commercial or non-commercial, and by any
7 * means.
8 *
9 * In jurisdictions that recognize copyright laws, the author or authors
10 * of this software dedicate any and all copyright interest in the
11 * software to the public domain. We make this dedication for the benefit
12 * of the public at large and to the detriment of our heirs and
13 * successors. We intend this dedication to be an overt act of
14 * relinquishment in perpetuity of all present and future rights to this
15 * software under copyright law.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
21 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 * OTHER DEALINGS IN THE SOFTWARE.
24 */
26 #include "config.h"
28 #include <sys/uio.h>
30 #include <ctype.h>
31 #include <errno.h>
32 #include <limits.h>
33 #include <stdarg.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
39 #include "bufio.h"
40 #include "http.h"
41 #include "log.h"
42 #include "ws.h"
43 #include "xmalloc.h"
45 #ifndef nitems
46 #define nitems(x) (sizeof(x)/sizeof(x[0]))
47 #endif
49 #define HTTP_MAX_UPLOAD 4096
51 int
52 http_init(struct client *clt, int fd)
53 {
54 memset(clt, 0, sizeof(*clt));
55 if (bufio_init(&clt->bio) == -1)
56 return -1;
57 bufio_set_fd(&clt->bio, fd);
58 return 0;
59 }
61 int
62 http_parse(struct client *clt)
63 {
64 struct buf *rbuf = &clt->bio.rbuf;
65 struct request *req = &clt->req;
66 size_t len;
67 uint8_t *endln;
68 char *frag, *query, *http, *line;
69 const char *errstr, *m;
71 while (!clt->reqdone) {
72 endln = memmem(rbuf->buf, rbuf->len, "\r\n", 2);
73 if (endln == NULL) {
74 errno = EAGAIN;
75 return -1;
76 }
78 line = rbuf->buf;
79 if (endln == rbuf->buf)
80 clt->reqdone = 1;
82 len = endln - rbuf->buf + 2;
83 while (len > 0 && (line[len - 1] == '\r' ||
84 line[len - 1] == '\n' || line[len - 1] == ' ' ||
85 line[len - 1] == '\t'))
86 line[--len] = '\0';
88 /* first line */
89 if (clt->req.method == METHOD_UNKNOWN) {
90 if (!strncmp("GET ", line, 4)) {
91 req->method = METHOD_GET;
92 line += 4;
93 } else if (!strncmp("POST ", line, 5)) {
94 req->method = METHOD_POST;
95 line += 5;
96 } else {
97 errno = EINVAL;
98 return -1;
99 }
101 if ((http = strchr(line, ' ')) == NULL)
102 http = line;
103 if (*http != '\0')
104 *http++ = '\0';
106 if ((query = strchr(line, '?')))
107 *query = '\0';
108 if ((frag = strchr(line, '#')))
109 *frag = '\0';
111 clt->req.path = xstrdup(line);
113 if (!strcmp(http, "HTTP/1.0"))
114 clt->req.version = HTTP_1_0;
115 else if (!strcmp(http, "HTTP/1.1")) {
116 clt->req.version = HTTP_1_1;
117 clt->chunked = 1;
118 } else {
119 log_warnx("unknown http version %s", http);
120 errno = EINVAL;
121 return -1;
124 line = http; /* so that no header below matches */
127 if (!strncasecmp(line, "Content-Length:", 15)) {
128 line += 15;
129 line += strspn(line, " \t");
130 clt->req.clen = strtonum(line, 0, HTTP_MAX_UPLOAD,
131 &errstr);
132 if (errstr) {
133 log_warnx("content-length is %s: %s",
134 errstr, line);
135 errno = EINVAL;
136 return -1;
140 if (!strncasecmp(line, "Connection:", 11)) {
141 line += 11;
142 line += strspn(line, " \t");
143 if (!strcasecmp(line, "upgrade"))
144 req->flags |= R_CONNUPGR;
147 if (!strncasecmp(line, "Upgrade:", 8)) {
148 line += 8;
149 line += strspn(line, " \t");
150 if (!strcasecmp(line, "websocket"))
151 req->flags |= R_UPGRADEWS;
154 if (!strncasecmp(line, "Sec-WebSocket-Version:", 22)) {
155 line += 22;
156 line += strspn(line, " \t");
157 if (strcmp(line, "13") != 0) {
158 log_warnx("unsupported websocket version %s",
159 line);
160 errno = EINVAL;
161 return -1;
163 req->flags |= R_WSVERSION;
166 if (!strncasecmp(line, "Sec-WebSocket-Key:", 18)) {
167 line += 18;
168 line += strspn(line, " \t");
169 req->secret = xstrdup(line);
172 buf_drain(rbuf, endln - rbuf->buf + 2);
175 if (req->method == METHOD_GET)
176 m = "GET";
177 else if (req->method == METHOD_POST)
178 m = "POST";
179 else
180 m = "unknown";
181 log_debug("< %s %s HTTP/%s", m, req->path,
182 req->version == HTTP_1_1 ? "1.1" : "1.0");
184 return 0;
187 int
188 http_read(struct client *clt)
190 struct request *req = &clt->req;
191 struct buf *rbuf = &clt->bio.rbuf;
192 size_t left;
194 /* clients may have sent more data than advertised */
195 if (req->clen < rbuf->len)
196 left = 0;
197 else
198 left = req->clen - rbuf->len;
200 if (left > 0) {
201 errno = EAGAIN;
202 return -1;
205 buf_write(rbuf, "", 1); /* append a NUL byte */
206 while (rbuf->len > 0 && (rbuf->buf[rbuf->len - 1] == '\r' ||
207 (rbuf->buf[rbuf->len - 1] == '\n')))
208 rbuf->buf[--rbuf->len] = '\0';
210 return 0;
213 void
214 http_postdata(struct client *clt, char **data, size_t *len)
216 if (data)
217 *data = clt->bio.rbuf.buf;
218 if (len)
219 *len = clt->bio.rbuf.len;
222 int
223 http_reply(struct client *clt, int code, const char *reason,
224 const char *ctype)
226 const char *version, *location = NULL;
227 char b32[32] = "";
229 log_debug("> %d %s", code, reason);
231 if (code == 101) {
232 if (ws_accept_hdr(clt->req.secret, b32, sizeof(b32)) == -1) {
233 clt->err = 1;
234 return -1;
236 free(clt->req.secret);
237 clt->req.secret = NULL;
239 clt->chunked = 0;
242 if (code >= 300 && code < 400) {
243 location = ctype;
244 ctype = "text/html;charset=UTF-8";
247 version = "HTTP/1.1";
248 if (clt->req.version == HTTP_1_0)
249 version = "HTTP/1.0";
251 if (http_fmt(clt, "%s %d %s\r\n"
252 "Connection: close\r\n"
253 "Cache-Control: no-store\r\n",
254 version, code, reason) == -1)
255 goto err;
256 if (ctype != NULL &&
257 http_fmt(clt, "Content-Type: %s\r\n", ctype) == -1)
258 goto err;
259 if (location != NULL &&
260 http_fmt(clt, "Location: %s\r\n", location) == -1)
261 goto err;
262 if (clt->chunked &&
263 http_writes(clt, "Transfer-Encoding: chunked\r\n") == -1)
264 goto err;
265 if (code == 101) {
266 if (http_fmt(clt, "Upgrade: websocket\r\n"
267 "Connection: Upgrade\r\n"
268 "Sec-WebSocket-Accept: %s\r\n", b32) == -1)
269 goto err;
271 if (http_write(clt, "\r\n", 2) == -1)
272 goto err;
274 bufio_set_chunked(&clt->bio, clt->chunked);
276 if (location) {
277 if (http_writes(clt, "<a href='") == -1 ||
278 http_htmlescape(clt, location) == -1 ||
279 http_writes(clt, "'>") == -1 ||
280 http_htmlescape(clt, reason) == -1 ||
281 http_writes(clt, "</a>") == -1)
282 return -1;
285 return 0;
287 err:
288 clt->err = 1;
289 return -1;
292 int
293 http_flush(struct client *clt)
295 if (clt->err)
296 return -1;
298 if (clt->len == 0)
299 return 0;
301 if (bufio_compose(&clt->bio, clt->buf, clt->len) == -1) {
302 clt->err = 1;
303 return -1;
306 clt->len = 0;
308 return 0;
311 int
312 http_write(struct client *clt, const char *d, size_t len)
314 size_t avail;
316 if (clt->err)
317 return -1;
319 if (!clt->bio.chunked) {
320 if (bufio_compose(&clt->bio, d, len) == -1) {
321 clt->err = 1;
322 return -1;
324 return 0;
327 if (clt->buf == NULL) {
328 clt->cap = 1024;
329 if ((clt->buf = malloc(clt->cap)) == NULL) {
330 clt->err = 1;
331 return -1;
335 while (len > 0) {
336 avail = clt->cap - clt->len;
337 if (avail > len)
338 avail = len;
340 memcpy(clt->buf + clt->len, d, avail);
341 clt->len += avail;
342 len -= avail;
343 d += avail;
344 if (clt->len == clt->cap) {
345 if (http_flush(clt) == -1)
346 return -1;
350 return 0;
353 int
354 http_writes(struct client *clt, const char *str)
356 return http_write(clt, str, strlen(str));
359 int
360 http_fmt(struct client *clt, const char *fmt, ...)
362 va_list ap;
363 char *str;
364 int r;
366 va_start(ap, fmt);
367 r = vasprintf(&str, fmt, ap);
368 va_end(ap);
370 if (r == -1) {
371 log_warn("vasprintf");
372 clt->err = 1;
373 return -1;
376 r = http_write(clt, str, r);
377 free(str);
378 return r;
381 int
382 http_urlescape(struct client *clt, const char *str)
384 int r;
385 char tmp[4];
387 for (; *str; ++str) {
388 if (iscntrl((unsigned char)*str) ||
389 isspace((unsigned char)*str) ||
390 *str == '\'' || *str == '"' || *str == '\\') {
391 r = snprintf(tmp, sizeof(tmp), "%%%2X",
392 (unsigned char)*str);
393 if (r < 0 || (size_t)r >= sizeof(tmp)) {
394 log_warn("snprintf failed");
395 clt->err = 1;
396 return -1;
398 if (http_write(clt, tmp, r) == -1)
399 return -1;
400 } else if (http_write(clt, str, 1) == -1)
401 return -1;
404 return 0;
407 int
408 http_htmlescape(struct client *clt, const char *str)
410 int r;
412 for (; *str; ++str) {
413 switch (*str) {
414 case '<':
415 r = http_writes(clt, "&lt;");
416 break;
417 case '>':
418 r = http_writes(clt, "&gt;");
419 break;
420 case '&':
421 r = http_writes(clt, "&gt;");
422 break;
423 case '"':
424 r = http_writes(clt, "&quot;");
425 break;
426 case '\'':
427 r = http_writes(clt, "&apos;");
428 break;
429 default:
430 r = http_write(clt, str, 1);
431 break;
434 if (r == -1)
435 return -1;
438 return 0;
441 int
442 http_close(struct client *clt)
444 if (clt->err)
445 return -1;
446 if (clt->len != 0 && http_flush(clt) == -1)
447 return -1;
448 if (bufio_compose(&clt->bio, NULL, 0) == -1)
449 clt->err = 1;
450 return (clt->err ? -1 : 0);
453 void
454 http_free(struct client *clt)
456 free(clt->buf);
457 free(clt->req.path);
458 free(clt->req.secret);
459 free(clt->req.ctype);
460 free(clt->req.body);
461 bufio_free(&clt->bio);