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 "xmalloc.h"
44 #ifndef nitems
45 #define nitems(x) (sizeof(x)/sizeof(x[0]))
46 #endif
48 int
49 http_init(struct client *clt, int fd)
50 {
51 memset(clt, 0, sizeof(*clt));
52 if (bufio_init(&clt->bio) == -1)
53 return -1;
54 bufio_set_fd(&clt->bio, fd);
55 return 0;
56 }
58 int
59 http_parse(struct client *clt)
60 {
61 struct buffer *rbuf = &clt->bio.rbuf;
62 struct request *req = &clt->req;
63 size_t len;
64 uint8_t *endln;
65 char *frag, *query, *http, *line;
66 const char *errstr, *m;
68 while (!clt->reqdone) {
69 endln = memmem(rbuf->buf, rbuf->len, "\r\n", 2);
70 if (endln == NULL) {
71 errno = EAGAIN;
72 return -1;
73 }
75 line = rbuf->buf;
76 if (endln == rbuf->buf)
77 clt->reqdone = 1;
79 len = endln - rbuf->buf + 2;
80 while (len > 0 && (line[len - 1] == '\r' ||
81 line[len - 1] == '\n' || line[len - 1] == ' ' ||
82 line[len - 1] == '\t'))
83 line[--len] = '\0';
85 /* first line */
86 if (clt->req.method == METHOD_UNKNOWN) {
87 if (!strncmp("GET ", line, 4)) {
88 req->method = METHOD_GET;
89 line += 4;
90 } else if (!strncmp("POST ", line, 5)) {
91 req->method = METHOD_POST;
92 line += 5;
93 } else {
94 errno = EINVAL;
95 return -1;
96 }
98 if ((http = strchr(line, ' ')) == NULL)
99 http = line;
100 if (*http != '\0')
101 *http++ = '\0';
103 if ((query = strchr(line, '?')))
104 *query = '\0';
105 if ((frag = strchr(line, '#')))
106 *frag = '\0';
108 clt->req.path = xstrdup(line);
110 if (!strcmp(http, "HTTP/1.0"))
111 clt->req.version = HTTP_1_0;
112 else if (!strcmp(http, "HTTP/1.1")) {
113 clt->req.version = HTTP_1_1;
114 clt->chunked = 1;
115 } else {
116 log_warnx("unknown http version %s", http);
117 errno = EINVAL;
118 return -1;
121 line = http; /* so that no header below matches */
124 if (!strncasecmp(line, "Content-Length:", 15)) {
125 line += 15;
126 line += strspn(line, " \t");
127 clt->req.clen = strtonum(line, 0, LONG_MAX,
128 &errstr);
129 if (errstr) {
130 log_warnx("content-length is %s: %s",
131 errstr, line);
132 errno = EINVAL;
133 return -1;
137 buf_drain(rbuf, endln - rbuf->buf + 2);
140 if (req->method == METHOD_GET)
141 m = "GET";
142 else if (req->method == METHOD_POST)
143 m = "POST";
144 else
145 m = "unknown";
146 log_debug("< %s %s HTTP/%s", m, req->path,
147 req->version == HTTP_1_1 ? "1.1" : "1.0");
149 return 0;
152 int
153 http_read(struct client *clt)
155 struct request *req = &clt->req;
156 size_t left;
157 size_t nr;
159 if (req->clen > sizeof(clt->buf) - 1) {
160 log_warnx("POST has more data then what can be accepted");
161 return -1;
164 /* clients may have sent more data than advertised */
165 if (req->clen < clt->len)
166 left = 0;
167 else
168 left = req->clen - clt->len;
170 if (left > 0) {
171 nr = bufio_drain(&clt->bio, clt->buf + clt->len, left);
172 clt->len += nr;
173 if (nr < left) {
174 errno = EAGAIN;
175 return -1;
179 clt->buf[clt->len] = '\0';
180 while (clt->len > 0 && (clt->buf[clt->len - 1] == '\r' ||
181 (clt->buf[clt->len - 1] == '\n')))
182 clt->buf[--clt->len] = '\0';
184 return 0;
187 int
188 http_reply(struct client *clt, int code, const char *reason, const char *ctype)
190 const char *version, *location = NULL;
191 int r;
193 log_debug("> %d %s", code, reason);
195 if (code >= 300 && code < 400) {
196 location = ctype;
197 ctype = "text/html;charset=UTF-8";
200 version = "HTTP/1.1";
201 if (clt->req.version == HTTP_1_0)
202 version = "HTTP/1.0";
204 r = bufio_compose_fmt(&clt->bio, "%s %d %s\r\n"
205 "Connection: close\r\n"
206 "Cache-Control: no-store\r\n"
207 "%s%s%s"
208 "%s%s%s"
209 "%s"
210 "\r\n",
211 version, code, reason,
212 ctype == NULL ? "" : "Content-Type: ",
213 ctype == NULL ? "" : ctype,
214 ctype == NULL ? "" : "\r\n",
215 location == NULL ? "" : "Location: ",
216 location == NULL ? "" : location,
217 location == NULL ? "" : "\r\n",
218 clt->chunked ? "Transfer-Encoding: chunked\r\n" : "");
219 if (r == -1) {
220 clt->err = 1;
221 return -1;
224 bufio_set_chunked(&clt->bio, clt->chunked);
226 if (location) {
227 if (http_writes(clt, "<a href='") == -1 ||
228 http_htmlescape(clt, location) == -1 ||
229 http_writes(clt, "'>") == -1 ||
230 http_htmlescape(clt, reason) == -1 ||
231 http_writes(clt, "</a>") == -1)
232 return -1;
235 return 0;
238 int
239 http_flush(struct client *clt)
241 if (clt->err)
242 return -1;
244 if (clt->len == 0)
245 return 0;
247 if (bufio_compose(&clt->bio, clt->buf, clt->len) == -1) {
248 clt->err = 1;
249 return -1;
252 clt->len = 0;
254 return 0;
257 int
258 http_write(struct client *clt, const char *d, size_t len)
260 size_t avail;
262 if (clt->err)
263 return -1;
265 while (len > 0) {
266 avail = sizeof(clt->buf) - clt->len;
267 if (avail > len)
268 avail = len;
270 memcpy(clt->buf + clt->len, d, avail);
271 clt->len += avail;
272 len -= avail;
273 d += avail;
274 if (clt->len == sizeof(clt->buf)) {
275 if (http_flush(clt) == -1)
276 return -1;
280 return 0;
283 int
284 http_writes(struct client *clt, const char *str)
286 return http_write(clt, str, strlen(str));
289 int
290 http_fmt(struct client *clt, const char *fmt, ...)
292 va_list ap;
293 char *str;
294 int r;
296 va_start(ap, fmt);
297 r = vasprintf(&str, fmt, ap);
298 va_end(ap);
300 if (r == -1) {
301 log_warn("vasprintf");
302 clt->err = 1;
303 return -1;
306 r = http_write(clt, str, r);
307 free(str);
308 return r;
311 int
312 http_urlescape(struct client *clt, const char *str)
314 int r;
315 char tmp[4];
317 for (; *str; ++str) {
318 if (iscntrl((unsigned char)*str) ||
319 isspace((unsigned char)*str) ||
320 *str == '\'' || *str == '"' || *str == '\\') {
321 r = snprintf(tmp, sizeof(tmp), "%%%2X",
322 (unsigned char)*str);
323 if (r < 0 || (size_t)r >= sizeof(tmp)) {
324 log_warn("snprintf failed");
325 clt->err = 1;
326 return -1;
328 if (http_write(clt, tmp, r) == -1)
329 return -1;
330 } else if (http_write(clt, str, 1) == -1)
331 return -1;
334 return 0;
337 int
338 http_htmlescape(struct client *clt, const char *str)
340 int r;
342 for (; *str; ++str) {
343 switch (*str) {
344 case '<':
345 r = http_writes(clt, "&lt;");
346 break;
347 case '>':
348 r = http_writes(clt, "&gt;");
349 break;
350 case '&':
351 r = http_writes(clt, "&gt;");
352 break;
353 case '"':
354 r = http_writes(clt, "&quot;");
355 break;
356 case '\'':
357 r = http_writes(clt, "&apos;");
358 break;
359 default:
360 r = http_write(clt, str, 1);
361 break;
364 if (r == -1)
365 return -1;
368 return 0;
371 int
372 http_close(struct client *clt)
374 if (clt->err)
375 return -1;
376 if (clt->len != 0 && http_flush(clt) == -1)
377 return -1;
378 if (bufio_compose(&clt->bio, NULL, 0) == -1)
379 clt->err = 1;
380 return (clt->err ? -1 : 0);
383 void
384 http_free(struct client *clt)
386 free(clt->req.path);
387 free(clt->req.ctype);
388 free(clt->req.body);
389 bufio_free(&clt->bio);