Made htparser listening much more flexible.
[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
8774c31b 38#include "htparser.h"
f4cdf919 39
8774c31b 40static int plex;
f4cdf919 41
5fc1bf9f 42static struct hthead *parsereq(FILE *in)
66987955 43{
5fc1bf9f
FT
44 struct hthead *req;
45 struct charbuf method, url, ver;
46 int c;
66987955 47
5fc1bf9f
FT
48 req = NULL;
49 bufinit(method);
50 bufinit(url);
51 bufinit(ver);
52 while(1) {
53 c = getc(in);
54 if(c == ' ') {
55 break;
56 } else if((c == EOF) || (c < 32) || (c >= 128)) {
57 goto fail;
58 } else {
59 bufadd(method, c);
66987955 60 }
c9955b14 61 }
c9955b14 62 while(1) {
5fc1bf9f
FT
63 c = getc(in);
64 if(c == ' ') {
c9955b14 65 break;
5fc1bf9f
FT
66 } else if((c == EOF) || (c < 32)) {
67 goto fail;
68 } else {
69 bufadd(url, c);
66987955
FT
70 }
71 }
66987955 72 while(1) {
5fc1bf9f
FT
73 c = getc(in);
74 if(c == 10) {
66987955 75 break;
5fc1bf9f
FT
76 } else if(c == 13) {
77 } else if((c == EOF) || (c < 32) || (c >= 128)) {
c9955b14 78 goto fail;
5fc1bf9f
FT
79 } else {
80 bufadd(ver, c);
81 }
66987955 82 }
5fc1bf9f
FT
83 bufadd(method, 0);
84 bufadd(url, 0);
85 bufadd(ver, 0);
86 req = mkreq(method.b, url.b, ver.b);
87 if(parseheaders(req, in))
88 goto fail;
89 goto out;
c9955b14
FT
90
91fail:
5fc1bf9f
FT
92 if(req != NULL) {
93 freehthead(req);
94 req = NULL;
95 }
96out:
97 buffree(method);
98 buffree(url);
99 buffree(ver);
100 return(req);
66987955
FT
101}
102
5fc1bf9f 103static struct hthead *parseresp(FILE *in)
9d87a119 104{
5fc1bf9f 105 struct hthead *req;
9d87a119 106 int code;
5fc1bf9f
FT
107 struct charbuf ver, msg;
108 int c;
9d87a119 109
5fc1bf9f
FT
110 req = NULL;
111 bufinit(ver);
112 bufinit(msg);
113 code = 0;
9d87a119 114 while(1) {
5fc1bf9f
FT
115 c = getc(in);
116 if(c == ' ') {
9d87a119 117 break;
5fc1bf9f 118 } else if((c == EOF) || (c < 32) || (c >= 128)) {
9d87a119 119 goto fail;
5fc1bf9f
FT
120 } else {
121 bufadd(ver, c);
122 }
123 }
124 while(1) {
125 c = getc(in);
126 if(c == ' ') {
127 break;
128 } else if((c == EOF) || (c < '0') || (c > '9')) {
9d87a119 129 goto fail;
5fc1bf9f
FT
130 } else {
131 code = (code * 10) + (c - '0');
132 }
133 }
134 while(1) {
135 c = getc(in);
136 if(c == 10) {
137 break;
138 } else if(c == 13) {
139 } else if((c == EOF) || (c < 32)) {
9d87a119 140 goto fail;
5fc1bf9f
FT
141 } else {
142 bufadd(msg, c);
143 }
9d87a119 144 }
5fc1bf9f
FT
145 bufadd(msg, 0);
146 bufadd(ver, 0);
147 req = mkresp(code, msg.b, ver.b);
148 if(parseheaders(req, in))
149 goto fail;
150 goto out;
9d87a119
FT
151
152fail:
5fc1bf9f
FT
153 if(req != NULL) {
154 freehthead(req);
155 req = NULL;
156 }
157out:
158 buffree(msg);
159 buffree(ver);
160 return(req);
9d87a119
FT
161}
162
5fc1bf9f 163static off_t passdata(FILE *in, FILE *out, off_t max)
9d87a119 164{
5fc1bf9f
FT
165 size_t read;
166 off_t total;
167 char buf[8192];
168
169 total = 0;
f9255ddd 170 while(!feof(in) && ((max < 0) || (total < max))) {
5fc1bf9f
FT
171 read = sizeof(buf);
172 if(max >= 0)
a701d7b7 173 read = min(max - total, read);
5fc1bf9f
FT
174 read = fread(buf, 1, read, in);
175 if(ferror(in))
176 return(-1);
177 if(fwrite(buf, 1, read, out) != read)
178 return(-1);
179 total += read;
9d87a119 180 }
5fc1bf9f
FT
181 return(total);
182}
183
184static int passchunks(FILE *in, FILE *out)
185{
186 char buf[8192];
187 size_t read;
188
189 do {
190 read = fread(buf, 1, sizeof(buf), in);
191 if(ferror(in))
192 return(-1);
f9255ddd 193 fprintf(out, "%zx\r\n", read);
5fc1bf9f
FT
194 if(fwrite(buf, 1, read, out) != read)
195 return(-1);
196 fprintf(out, "\r\n");
197 } while(read > 0);
198 return(0);
199}
200
201static int hasheader(struct hthead *head, char *name, char *val)
202{
203 char *hd;
204
205 if((hd = getheader(head, name)) == NULL)
206 return(0);
207 return(!strcasecmp(hd, val));
9d87a119
FT
208}
209
8774c31b 210void serve(FILE *in, struct conn *conn)
66987955 211{
af34331c 212 int pfds[2];
8774c31b 213 FILE *out;
9d87a119 214 struct hthead *req, *resp;
5fc1bf9f
FT
215 char *hd, *p;
216 off_t dlen;
66987955 217
5fc1bf9f 218 out = NULL;
3c296bd4 219 req = resp = NULL;
66987955 220 while(1) {
5fc1bf9f
FT
221 if((req = parsereq(in)) == NULL)
222 break;
223 replrest(req, req->url);
9e9eca79
FT
224 if(req->rest[0] == '/')
225 replrest(req, req->rest + 1);
edad3c6a
FT
226 if((p = strchr(req->rest, '?')) != NULL)
227 *p = 0;
9d87a119 228
8774c31b
FT
229 if((conn->initreq != NULL) && conn->initreq(conn, req))
230 break;
231
46c3d430 232 if(block(plex, EV_WRITE, 60) <= 0)
5fc1bf9f 233 break;
af34331c 234 if(socketpair(PF_UNIX, SOCK_STREAM, 0, pfds))
5fc1bf9f 235 break;
af34331c 236 if(sendreq(plex, req, pfds[0]))
5fc1bf9f 237 break;
af34331c 238 close(pfds[0]);
5fc1bf9f 239 out = mtstdopen(pfds[1], 1, 600, "r+");
a0327573 240
a0327573
FT
241 if((hd = getheader(req, "content-length")) != NULL) {
242 dlen = atoo(hd);
a06a2fbd 243 if(dlen > 0) {
5fc1bf9f
FT
244 if(passdata(in, out, dlen) != dlen)
245 break;
a06a2fbd 246 }
a0327573 247 }
5fc1bf9f
FT
248 if(fflush(out))
249 break;
d93d9a05 250 /* Make sure to send EOF */
5fc1bf9f 251 shutdown(pfds[1], SHUT_WR);
9d87a119 252
f9255ddd
FT
253 if((resp = parseresp(out)) == NULL)
254 break;
5fc1bf9f
FT
255 replstr(&resp->ver, req->ver);
256
257 if(!strcmp(req->ver, "HTTP/1.0")) {
258 writeresp(in, resp);
259 fprintf(in, "\r\n");
9d87a119 260 if((hd = getheader(resp, "content-length")) != NULL) {
5fc1bf9f
FT
261 dlen = passdata(out, in, -1);
262 if(dlen != atoo(hd))
263 break;
264 if(!hasheader(req, "connection", "keep-alive"))
9d87a119 265 break;
5fc1bf9f
FT
266 } else {
267 passdata(out, in, -1);
268 break;
9d87a119 269 }
5fc1bf9f
FT
270 if(hasheader(req, "connection", "close") || hasheader(resp, "connection", "close"))
271 break;
272 } else if(!strcmp(req->ver, "HTTP/1.1")) {
273 if((hd = getheader(resp, "content-length")) != NULL) {
274 writeresp(in, resp);
275 fprintf(in, "\r\n");
276 dlen = passdata(out, in, -1);
277 if(dlen != atoo(hd))
278 break;
279 } else if(!getheader(resp, "transfer-encoding")) {
280 headappheader(resp, "Transfer-Encoding", "chunked");
281 writeresp(in, resp);
282 fprintf(in, "\r\n");
283 if(passchunks(out, in))
284 break;
285 } else {
286 writeresp(in, resp);
287 fprintf(in, "\r\n");
288 passdata(out, in, -1);
9d87a119 289 break;
5fc1bf9f
FT
290 }
291 if(hasheader(req, "connection", "close") || hasheader(resp, "connection", "close"))
9d87a119 292 break;
5fc1bf9f
FT
293 } else {
294 break;
9d87a119 295 }
5fc1bf9f
FT
296
297 fclose(out);
298 out = NULL;
9d87a119 299 freehthead(req);
9d87a119 300 freehthead(resp);
5fc1bf9f 301 req = resp = NULL;
66987955
FT
302 }
303
5fc1bf9f
FT
304 if(out != NULL)
305 fclose(out);
9d87a119
FT
306 if(req != NULL)
307 freehthead(req);
308 if(resp != NULL)
309 freehthead(resp);
5fc1bf9f 310 fclose(in);
66987955
FT
311}
312
32e24c19
FT
313static void plexwatch(struct muth *muth, va_list args)
314{
315 vavar(int, fd);
316 char *buf;
317 int ret;
318
319 while(1) {
320 block(fd, EV_READ, 0);
321 buf = smalloc(65536);
322 ret = recv(fd, buf, 65536, 0);
323 if(ret < 0) {
324 flog(LOG_WARNING, "received error on rootplex read channel: %s", strerror(errno));
325 exit(1);
326 } else if(ret == 0) {
327 exit(0);
328 }
329 /* Maybe I'd like to implement some protocol in this direction
330 * some day... */
331 free(buf);
332 }
333}
334
8774c31b
FT
335static void usage(FILE *out)
336{
337 fprintf(out, "usage: htparser [-h] PORTSPEC... -- ROOT [ARGS...]\n");
338 fprintf(out, "\twhere PORTSPEC is HANDLER[:PAR[=VAL][(,PAR[=VAL])...]] (try HANDLER:help)\n");
339 fprintf(out, "\tavailable handlers are `plain'.\n");
340}
341
342static void addport(char *spec)
343{
344 char *nm, *p, *p2, *n;
345 struct charvbuf pars, vals;
346
347 bufinit(pars);
348 bufinit(vals);
349 if((p = strchr(spec, ':')) == NULL) {
350 nm = spec;
351 } else {
352 nm = spec;
353 *(p++) = 0;
354 do {
355 if((n = strchr(p, ',')) != NULL)
356 *(n++) = 0;
357 if((p2 = strchr(p, '=')) != NULL)
358 *(p2++) = 0;
359 if(!*p) {
360 usage(stderr);
361 exit(1);
362 }
363 bufadd(pars, p);
364 if(p2)
365 bufadd(vals, p2);
366 else
367 bufadd(vals, "");
368 } while((p = n) != NULL);
369 }
370
371 /* XXX: It would be nice to decentralize this, but, meh... */
372 if(!strcmp(nm, "plain")) {
373 handleplain(pars.d, pars.b, vals.b);
374 } else {
375 flog(LOG_ERR, "htparser: unknown port handler `%s'", nm);
376 exit(1);
377 }
378
379 buffree(pars);
380 buffree(vals);
381}
382
f0bbedf7
FT
383int main(int argc, char **argv)
384{
8774c31b
FT
385 int c;
386 int i, s1;
f4cdf919 387
8774c31b
FT
388 while((c = getopt(argc, argv, "+h")) >= 0) {
389 switch(c) {
390 case 'h':
391 usage(stdout);
392 exit(0);
393 default:
394 usage(stderr);
395 exit(1);
396 }
397 }
398 if((argc - optind) < 3) {
399 usage(stderr);
9d87a119
FT
400 exit(1);
401 }
8774c31b
FT
402 s1 = 0;
403 for(i = optind; i < argc; i++) {
404 if(!strcmp(argv[i], "--"))
405 break;
406 s1 = 1;
407 addport(argv[i]);
9d87a119 408 }
8774c31b
FT
409 if(!s1 || (i == argc)) {
410 usage(stderr);
411 exit(1);
f4cdf919 412 }
8774c31b
FT
413 if((plex = stdmkchild(argv + ++i)) < 0) {
414 flog(LOG_ERR, "could not spawn root multiplexer: %s", strerror(errno));
415 return(1);
f4cdf919 416 }
32e24c19 417 mustart(plexwatch, plex);
f4cdf919
FT
418 ioloop();
419 return(0);
f0bbedf7 420}