Always send the entire buffer in mtwrite.
[ashd.git] / src / htparser.c
CommitLineData
f0bbedf7
FT
1/*
2 ashd - A Sane HTTP Daemon
3 Copyright (C) 2008 Fredrik Tolf <fredrik@dolda2000.com>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#include <stdlib.h>
20#include <unistd.h>
21#include <stdio.h>
f4cdf919 22#include <string.h>
f4cdf919
FT
23#include <sys/socket.h>
24#include <netinet/in.h>
9d87a119 25#include <arpa/inet.h>
f4cdf919 26#include <errno.h>
f0bbedf7
FT
27
28#ifdef HAVE_CONFIG_H
29#include <config.h>
30#endif
31#include <utils.h>
f4cdf919 32#include <mt.h>
83723896 33#include <mtio.h>
f4cdf919 34#include <log.h>
66987955 35#include <req.h>
9d87a119 36#include <proc.h>
f4cdf919 37
83723896 38static int plex;
f4cdf919
FT
39
40static int listensock4(int port)
41{
42 struct sockaddr_in name;
43 int fd;
44 int valbuf;
45
46 memset(&name, 0, sizeof(name));
47 name.sin_family = AF_INET;
48 name.sin_port = htons(port);
49 if((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
50 return(-1);
51 valbuf = 1;
52 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &valbuf, sizeof(valbuf));
53 if(bind(fd, (struct sockaddr *)&name, sizeof(name))) {
54 close(fd);
55 return(-1);
56 }
57 if(listen(fd, 16) < 0) {
58 close(fd);
59 return(-1);
60 }
61 return(fd);
62}
63
64static int listensock6(int port)
65{
66 struct sockaddr_in6 name;
67 int fd;
68 int valbuf;
69
70 memset(&name, 0, sizeof(name));
71 name.sin6_family = AF_INET6;
72 name.sin6_port = htons(port);
73 if((fd = socket(PF_INET6, SOCK_STREAM, 0)) < 0)
74 return(-1);
75 valbuf = 1;
76 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &valbuf, sizeof(valbuf));
77 if(bind(fd, (struct sockaddr *)&name, sizeof(name))) {
78 close(fd);
79 return(-1);
80 }
81 if(listen(fd, 16) < 0) {
82 close(fd);
83 return(-1);
84 }
85 return(fd);
86}
87
5fc1bf9f 88static struct hthead *parsereq(FILE *in)
66987955 89{
5fc1bf9f
FT
90 struct hthead *req;
91 struct charbuf method, url, ver;
92 int c;
66987955 93
5fc1bf9f
FT
94 req = NULL;
95 bufinit(method);
96 bufinit(url);
97 bufinit(ver);
98 while(1) {
99 c = getc(in);
100 if(c == ' ') {
101 break;
102 } else if((c == EOF) || (c < 32) || (c >= 128)) {
103 goto fail;
104 } else {
105 bufadd(method, c);
66987955 106 }
c9955b14 107 }
c9955b14 108 while(1) {
5fc1bf9f
FT
109 c = getc(in);
110 if(c == ' ') {
c9955b14 111 break;
5fc1bf9f
FT
112 } else if((c == EOF) || (c < 32)) {
113 goto fail;
114 } else {
115 bufadd(url, c);
66987955
FT
116 }
117 }
66987955 118 while(1) {
5fc1bf9f
FT
119 c = getc(in);
120 if(c == 10) {
66987955 121 break;
5fc1bf9f
FT
122 } else if(c == 13) {
123 } else if((c == EOF) || (c < 32) || (c >= 128)) {
c9955b14 124 goto fail;
5fc1bf9f
FT
125 } else {
126 bufadd(ver, c);
127 }
66987955 128 }
5fc1bf9f
FT
129 bufadd(method, 0);
130 bufadd(url, 0);
131 bufadd(ver, 0);
132 req = mkreq(method.b, url.b, ver.b);
133 if(parseheaders(req, in))
134 goto fail;
135 goto out;
c9955b14
FT
136
137fail:
5fc1bf9f
FT
138 if(req != NULL) {
139 freehthead(req);
140 req = NULL;
141 }
142out:
143 buffree(method);
144 buffree(url);
145 buffree(ver);
146 return(req);
66987955
FT
147}
148
5fc1bf9f 149static struct hthead *parseresp(FILE *in)
9d87a119 150{
5fc1bf9f 151 struct hthead *req;
9d87a119 152 int code;
5fc1bf9f
FT
153 struct charbuf ver, msg;
154 int c;
9d87a119 155
5fc1bf9f
FT
156 req = NULL;
157 bufinit(ver);
158 bufinit(msg);
159 code = 0;
9d87a119 160 while(1) {
5fc1bf9f
FT
161 c = getc(in);
162 if(c == ' ') {
9d87a119 163 break;
5fc1bf9f 164 } else if((c == EOF) || (c < 32) || (c >= 128)) {
9d87a119 165 goto fail;
5fc1bf9f
FT
166 } else {
167 bufadd(ver, c);
168 }
169 }
170 while(1) {
171 c = getc(in);
172 if(c == ' ') {
173 break;
174 } else if((c == EOF) || (c < '0') || (c > '9')) {
9d87a119 175 goto fail;
5fc1bf9f
FT
176 } else {
177 code = (code * 10) + (c - '0');
178 }
179 }
180 while(1) {
181 c = getc(in);
182 if(c == 10) {
183 break;
184 } else if(c == 13) {
185 } else if((c == EOF) || (c < 32)) {
9d87a119 186 goto fail;
5fc1bf9f
FT
187 } else {
188 bufadd(msg, c);
189 }
9d87a119 190 }
5fc1bf9f
FT
191 bufadd(msg, 0);
192 bufadd(ver, 0);
193 req = mkresp(code, msg.b, ver.b);
194 if(parseheaders(req, in))
195 goto fail;
196 goto out;
9d87a119
FT
197
198fail:
5fc1bf9f
FT
199 if(req != NULL) {
200 freehthead(req);
201 req = NULL;
202 }
203out:
204 buffree(msg);
205 buffree(ver);
206 return(req);
9d87a119
FT
207}
208
5fc1bf9f 209static off_t passdata(FILE *in, FILE *out, off_t max)
9d87a119 210{
5fc1bf9f
FT
211 size_t read;
212 off_t total;
213 char buf[8192];
214
215 total = 0;
f9255ddd 216 while(!feof(in) && ((max < 0) || (total < max))) {
5fc1bf9f
FT
217 read = sizeof(buf);
218 if(max >= 0)
a701d7b7 219 read = min(max - total, read);
5fc1bf9f
FT
220 read = fread(buf, 1, read, in);
221 if(ferror(in))
222 return(-1);
223 if(fwrite(buf, 1, read, out) != read)
224 return(-1);
225 total += read;
9d87a119 226 }
5fc1bf9f
FT
227 return(total);
228}
229
230static int passchunks(FILE *in, FILE *out)
231{
232 char buf[8192];
233 size_t read;
234
235 do {
236 read = fread(buf, 1, sizeof(buf), in);
237 if(ferror(in))
238 return(-1);
f9255ddd 239 fprintf(out, "%zx\r\n", read);
5fc1bf9f
FT
240 if(fwrite(buf, 1, read, out) != read)
241 return(-1);
242 fprintf(out, "\r\n");
243 } while(read > 0);
244 return(0);
245}
246
247static int hasheader(struct hthead *head, char *name, char *val)
248{
249 char *hd;
250
251 if((hd = getheader(head, name)) == NULL)
252 return(0);
253 return(!strcasecmp(hd, val));
9d87a119
FT
254}
255
66987955
FT
256static void serve(struct muth *muth, va_list args)
257{
258 vavar(int, fd);
9d87a119 259 vavar(struct sockaddr_storage, name);
af34331c 260 int pfds[2];
5fc1bf9f 261 FILE *in, *out;
9d87a119 262 struct hthead *req, *resp;
9d87a119 263 char nmbuf[256];
5fc1bf9f
FT
264 char *hd, *p;
265 off_t dlen;
66987955 266
5fc1bf9f
FT
267 in = mtstdopen(fd, 1, 60, "r+");
268 out = NULL;
3c296bd4 269 req = resp = NULL;
66987955 270 while(1) {
5fc1bf9f
FT
271 if((req = parsereq(in)) == NULL)
272 break;
273 replrest(req, req->url);
9e9eca79
FT
274 if(req->rest[0] == '/')
275 replrest(req, req->rest + 1);
edad3c6a
FT
276 if((p = strchr(req->rest, '?')) != NULL)
277 *p = 0;
9d87a119 278
9d87a119
FT
279 if(name.ss_family == AF_INET) {
280 headappheader(req, "X-Ash-Address", inet_ntop(AF_INET, &((struct sockaddr_in *)&name)->sin_addr, nmbuf, sizeof(nmbuf)));
281 headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in *)&name)->sin_port)));
282 } else if(name.ss_family == AF_INET6) {
283 headappheader(req, "X-Ash-Address", inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&name)->sin6_addr, nmbuf, sizeof(nmbuf)));
284 headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in6 *)&name)->sin6_port)));
285 }
5fc1bf9f 286
46c3d430 287 if(block(plex, EV_WRITE, 60) <= 0)
5fc1bf9f 288 break;
af34331c 289 if(socketpair(PF_UNIX, SOCK_STREAM, 0, pfds))
5fc1bf9f 290 break;
af34331c 291 if(sendreq(plex, req, pfds[0]))
5fc1bf9f 292 break;
af34331c 293 close(pfds[0]);
5fc1bf9f 294 out = mtstdopen(pfds[1], 1, 600, "r+");
a0327573 295
a0327573
FT
296 if((hd = getheader(req, "content-length")) != NULL) {
297 dlen = atoo(hd);
a06a2fbd 298 if(dlen > 0) {
5fc1bf9f
FT
299 if(passdata(in, out, dlen) != dlen)
300 break;
a06a2fbd 301 }
a0327573 302 }
5fc1bf9f
FT
303 if(fflush(out))
304 break;
d93d9a05 305 /* Make sure to send EOF */
5fc1bf9f 306 shutdown(pfds[1], SHUT_WR);
9d87a119 307
f9255ddd
FT
308 if((resp = parseresp(out)) == NULL)
309 break;
5fc1bf9f
FT
310 replstr(&resp->ver, req->ver);
311
312 if(!strcmp(req->ver, "HTTP/1.0")) {
313 writeresp(in, resp);
314 fprintf(in, "\r\n");
9d87a119 315 if((hd = getheader(resp, "content-length")) != NULL) {
5fc1bf9f
FT
316 dlen = passdata(out, in, -1);
317 if(dlen != atoo(hd))
318 break;
319 if(!hasheader(req, "connection", "keep-alive"))
9d87a119 320 break;
5fc1bf9f
FT
321 } else {
322 passdata(out, in, -1);
323 break;
9d87a119 324 }
5fc1bf9f
FT
325 if(hasheader(req, "connection", "close") || hasheader(resp, "connection", "close"))
326 break;
327 } else if(!strcmp(req->ver, "HTTP/1.1")) {
328 if((hd = getheader(resp, "content-length")) != NULL) {
329 writeresp(in, resp);
330 fprintf(in, "\r\n");
331 dlen = passdata(out, in, -1);
332 if(dlen != atoo(hd))
333 break;
334 } else if(!getheader(resp, "transfer-encoding")) {
335 headappheader(resp, "Transfer-Encoding", "chunked");
336 writeresp(in, resp);
337 fprintf(in, "\r\n");
338 if(passchunks(out, in))
339 break;
340 } else {
341 writeresp(in, resp);
342 fprintf(in, "\r\n");
343 passdata(out, in, -1);
9d87a119 344 break;
5fc1bf9f
FT
345 }
346 if(hasheader(req, "connection", "close") || hasheader(resp, "connection", "close"))
9d87a119 347 break;
5fc1bf9f
FT
348 } else {
349 break;
9d87a119 350 }
5fc1bf9f
FT
351
352 fclose(out);
353 out = NULL;
9d87a119 354 freehthead(req);
9d87a119 355 freehthead(resp);
5fc1bf9f 356 req = resp = NULL;
66987955
FT
357 }
358
5fc1bf9f
FT
359 if(out != NULL)
360 fclose(out);
9d87a119
FT
361 if(req != NULL)
362 freehthead(req);
363 if(resp != NULL)
364 freehthead(resp);
5fc1bf9f 365 fclose(in);
66987955
FT
366}
367
f4cdf919
FT
368static void listenloop(struct muth *muth, va_list args)
369{
370 vavar(int, ss);
371 int ns;
372 struct sockaddr_storage name;
373 socklen_t namelen;
374
375 while(1) {
376 namelen = sizeof(name);
9d87a119 377 block(ss, EV_READ, 0);
f4cdf919 378 ns = accept(ss, (struct sockaddr *)&name, &namelen);
66987955
FT
379 if(ns < 0) {
380 flog(LOG_ERR, "accept: %s", strerror(errno));
381 goto out;
382 }
9d87a119 383 mustart(serve, ns, name);
f4cdf919 384 }
66987955
FT
385
386out:
387 close(ss);
f4cdf919
FT
388}
389
32e24c19
FT
390static void plexwatch(struct muth *muth, va_list args)
391{
392 vavar(int, fd);
393 char *buf;
394 int ret;
395
396 while(1) {
397 block(fd, EV_READ, 0);
398 buf = smalloc(65536);
399 ret = recv(fd, buf, 65536, 0);
400 if(ret < 0) {
401 flog(LOG_WARNING, "received error on rootplex read channel: %s", strerror(errno));
402 exit(1);
403 } else if(ret == 0) {
404 exit(0);
405 }
406 /* Maybe I'd like to implement some protocol in this direction
407 * some day... */
408 free(buf);
409 }
410}
411
f0bbedf7
FT
412int main(int argc, char **argv)
413{
f4cdf919
FT
414 int fd;
415
9d87a119
FT
416 if(argc < 2) {
417 fprintf(stderr, "usage: htparser ROOT [ARGS...]\n");
418 exit(1);
419 }
420 if((plex = stdmkchild(argv + 1)) < 0) {
421 flog(LOG_ERR, "could not spawn root multiplexer: %s", strerror(errno));
422 return(1);
423 }
f4cdf919
FT
424 if((fd = listensock6(8080)) < 0) {
425 flog(LOG_ERR, "could not listen on IPv6: %s", strerror(errno));
426 return(1);
427 }
428 mustart(listenloop, fd);
429 if((fd = listensock4(8080)) < 0) {
430 if(errno != EADDRINUSE) {
431 flog(LOG_ERR, "could not listen on IPv4: %s", strerror(errno));
432 return(1);
433 }
434 } else {
435 mustart(listenloop, fd);
436 }
32e24c19 437 mustart(plexwatch, plex);
f4cdf919
FT
438 ioloop();
439 return(0);
f0bbedf7 440}