2 ashd - A Sane HTTP Daemon
3 Copyright (C) 2008 Fredrik Tolf <fredrik@dolda2000.com>
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.
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.
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/>.
23 #include <sys/socket.h>
24 #include <netinet/in.h>
25 #include <arpa/inet.h>
40 static int listensock4(int port)
42 struct sockaddr_in name;
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)
52 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &valbuf, sizeof(valbuf));
53 if(bind(fd, (struct sockaddr *)&name, sizeof(name))) {
57 if(listen(fd, 16) < 0) {
64 static int listensock6(int port)
66 struct sockaddr_in6 name;
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)
76 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &valbuf, sizeof(valbuf));
77 if(bind(fd, (struct sockaddr *)&name, sizeof(name))) {
81 if(listen(fd, 16) < 0) {
88 static struct hthead *parsereq(FILE *in)
91 struct charbuf method, url, ver;
102 } else if((c == EOF) || (c < 32) || (c >= 128)) {
112 } else if((c == EOF) || (c < 32)) {
123 } else if((c == EOF) || (c < 32) || (c >= 128)) {
132 req = mkreq(method.b, url.b, ver.b);
133 if(parseheaders(req, in))
149 static struct hthead *parseresp(FILE *in)
153 struct charbuf ver, msg;
164 } else if((c == EOF) || (c < 32) || (c >= 128)) {
174 } else if((c == EOF) || (c < '0') || (c > '9')) {
177 code = (code * 10) + (c - '0');
185 } else if((c == EOF) || (c < 32)) {
193 req = mkresp(code, msg.b, ver.b);
194 if(parseheaders(req, in))
209 static off_t passdata(FILE *in, FILE *out, off_t max)
216 while(!feof(in) && (total < max)) {
219 read = min(max - total, read);
220 read = fread(buf, 1, read, in);
223 if(fwrite(buf, 1, read, out) != read)
230 static int passchunks(FILE *in, FILE *out)
236 read = fread(buf, 1, sizeof(buf), in);
239 fprintf(out, "%x\r\n", read);
240 if(fwrite(buf, 1, read, out) != read)
242 fprintf(out, "\r\n");
247 static int hasheader(struct hthead *head, char *name, char *val)
251 if((hd = getheader(head, name)) == NULL)
253 return(!strcasecmp(hd, val));
256 static void serve(struct muth *muth, va_list args)
259 vavar(struct sockaddr_storage, name);
262 struct hthead *req, *resp;
267 in = mtstdopen(fd, 1, 60, "r+");
271 if((req = parsereq(in)) == NULL)
273 replrest(req, req->url);
274 if(req->rest[0] == '/')
275 replrest(req, req->rest + 1);
276 if((p = strchr(req->rest, '?')) != NULL)
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)));
287 if(block(plex, EV_WRITE, 60) <= 0)
289 if(socketpair(PF_UNIX, SOCK_STREAM, 0, pfds))
291 if(sendreq(plex, req, pfds[0]))
294 out = mtstdopen(pfds[1], 1, 600, "r+");
296 if((hd = getheader(req, "content-length")) != NULL) {
299 if(passdata(in, out, dlen) != dlen)
305 /* Make sure to send EOF */
306 shutdown(pfds[1], SHUT_WR);
308 resp = parseresp(out);
309 replstr(&resp->ver, req->ver);
311 if(!strcmp(req->ver, "HTTP/1.0")) {
314 if((hd = getheader(resp, "content-length")) != NULL) {
315 dlen = passdata(out, in, -1);
318 if(!hasheader(req, "connection", "keep-alive"))
321 passdata(out, in, -1);
324 if(hasheader(req, "connection", "close") || hasheader(resp, "connection", "close"))
326 } else if(!strcmp(req->ver, "HTTP/1.1")) {
327 if((hd = getheader(resp, "content-length")) != NULL) {
330 dlen = passdata(out, in, -1);
333 } else if(!getheader(resp, "transfer-encoding")) {
334 headappheader(resp, "Transfer-Encoding", "chunked");
337 if(passchunks(out, in))
342 passdata(out, in, -1);
345 if(hasheader(req, "connection", "close") || hasheader(resp, "connection", "close"))
367 static void listenloop(struct muth *muth, va_list args)
371 struct sockaddr_storage name;
375 namelen = sizeof(name);
376 block(ss, EV_READ, 0);
377 ns = accept(ss, (struct sockaddr *)&name, &namelen);
379 flog(LOG_ERR, "accept: %s", strerror(errno));
382 mustart(serve, ns, name);
389 static void plexwatch(struct muth *muth, va_list args)
396 block(fd, EV_READ, 0);
397 buf = smalloc(65536);
398 ret = recv(fd, buf, 65536, 0);
400 flog(LOG_WARNING, "received error on rootplex read channel: %s", strerror(errno));
402 } else if(ret == 0) {
405 /* Maybe I'd like to implement some protocol in this direction
411 int main(int argc, char **argv)
416 fprintf(stderr, "usage: htparser ROOT [ARGS...]\n");
419 if((plex = stdmkchild(argv + 1)) < 0) {
420 flog(LOG_ERR, "could not spawn root multiplexer: %s", strerror(errno));
423 if((fd = listensock6(8080)) < 0) {
424 flog(LOG_ERR, "could not listen on IPv6: %s", strerror(errno));
427 mustart(listenloop, fd);
428 if((fd = listensock4(8080)) < 0) {
429 if(errno != EADDRINUSE) {
430 flog(LOG_ERR, "could not listen on IPv4: %s", strerror(errno));
434 mustart(listenloop, fd);
436 mustart(plexwatch, plex);