Made htparser listening much more flexible.
[ashd.git] / src / htparser.c
... / ...
CommitLineData
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>
22#include <string.h>
23#include <sys/socket.h>
24#include <netinet/in.h>
25#include <arpa/inet.h>
26#include <errno.h>
27
28#ifdef HAVE_CONFIG_H
29#include <config.h>
30#endif
31#include <utils.h>
32#include <mt.h>
33#include <mtio.h>
34#include <log.h>
35#include <req.h>
36#include <proc.h>
37
38#include "htparser.h"
39
40static int plex;
41
42static struct hthead *parsereq(FILE *in)
43{
44 struct hthead *req;
45 struct charbuf method, url, ver;
46 int c;
47
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);
60 }
61 }
62 while(1) {
63 c = getc(in);
64 if(c == ' ') {
65 break;
66 } else if((c == EOF) || (c < 32)) {
67 goto fail;
68 } else {
69 bufadd(url, c);
70 }
71 }
72 while(1) {
73 c = getc(in);
74 if(c == 10) {
75 break;
76 } else if(c == 13) {
77 } else if((c == EOF) || (c < 32) || (c >= 128)) {
78 goto fail;
79 } else {
80 bufadd(ver, c);
81 }
82 }
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;
90
91fail:
92 if(req != NULL) {
93 freehthead(req);
94 req = NULL;
95 }
96out:
97 buffree(method);
98 buffree(url);
99 buffree(ver);
100 return(req);
101}
102
103static struct hthead *parseresp(FILE *in)
104{
105 struct hthead *req;
106 int code;
107 struct charbuf ver, msg;
108 int c;
109
110 req = NULL;
111 bufinit(ver);
112 bufinit(msg);
113 code = 0;
114 while(1) {
115 c = getc(in);
116 if(c == ' ') {
117 break;
118 } else if((c == EOF) || (c < 32) || (c >= 128)) {
119 goto fail;
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')) {
129 goto fail;
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)) {
140 goto fail;
141 } else {
142 bufadd(msg, c);
143 }
144 }
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;
151
152fail:
153 if(req != NULL) {
154 freehthead(req);
155 req = NULL;
156 }
157out:
158 buffree(msg);
159 buffree(ver);
160 return(req);
161}
162
163static off_t passdata(FILE *in, FILE *out, off_t max)
164{
165 size_t read;
166 off_t total;
167 char buf[8192];
168
169 total = 0;
170 while(!feof(in) && ((max < 0) || (total < max))) {
171 read = sizeof(buf);
172 if(max >= 0)
173 read = min(max - total, read);
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;
180 }
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);
193 fprintf(out, "%zx\r\n", read);
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));
208}
209
210void serve(FILE *in, struct conn *conn)
211{
212 int pfds[2];
213 FILE *out;
214 struct hthead *req, *resp;
215 char *hd, *p;
216 off_t dlen;
217
218 out = NULL;
219 req = resp = NULL;
220 while(1) {
221 if((req = parsereq(in)) == NULL)
222 break;
223 replrest(req, req->url);
224 if(req->rest[0] == '/')
225 replrest(req, req->rest + 1);
226 if((p = strchr(req->rest, '?')) != NULL)
227 *p = 0;
228
229 if((conn->initreq != NULL) && conn->initreq(conn, req))
230 break;
231
232 if(block(plex, EV_WRITE, 60) <= 0)
233 break;
234 if(socketpair(PF_UNIX, SOCK_STREAM, 0, pfds))
235 break;
236 if(sendreq(plex, req, pfds[0]))
237 break;
238 close(pfds[0]);
239 out = mtstdopen(pfds[1], 1, 600, "r+");
240
241 if((hd = getheader(req, "content-length")) != NULL) {
242 dlen = atoo(hd);
243 if(dlen > 0) {
244 if(passdata(in, out, dlen) != dlen)
245 break;
246 }
247 }
248 if(fflush(out))
249 break;
250 /* Make sure to send EOF */
251 shutdown(pfds[1], SHUT_WR);
252
253 if((resp = parseresp(out)) == NULL)
254 break;
255 replstr(&resp->ver, req->ver);
256
257 if(!strcmp(req->ver, "HTTP/1.0")) {
258 writeresp(in, resp);
259 fprintf(in, "\r\n");
260 if((hd = getheader(resp, "content-length")) != NULL) {
261 dlen = passdata(out, in, -1);
262 if(dlen != atoo(hd))
263 break;
264 if(!hasheader(req, "connection", "keep-alive"))
265 break;
266 } else {
267 passdata(out, in, -1);
268 break;
269 }
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);
289 break;
290 }
291 if(hasheader(req, "connection", "close") || hasheader(resp, "connection", "close"))
292 break;
293 } else {
294 break;
295 }
296
297 fclose(out);
298 out = NULL;
299 freehthead(req);
300 freehthead(resp);
301 req = resp = NULL;
302 }
303
304 if(out != NULL)
305 fclose(out);
306 if(req != NULL)
307 freehthead(req);
308 if(resp != NULL)
309 freehthead(resp);
310 fclose(in);
311}
312
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
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
383int main(int argc, char **argv)
384{
385 int c;
386 int i, s1;
387
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);
400 exit(1);
401 }
402 s1 = 0;
403 for(i = optind; i < argc; i++) {
404 if(!strcmp(argv[i], "--"))
405 break;
406 s1 = 1;
407 addport(argv[i]);
408 }
409 if(!s1 || (i == argc)) {
410 usage(stderr);
411 exit(1);
412 }
413 if((plex = stdmkchild(argv + ++i)) < 0) {
414 flog(LOG_ERR, "could not spawn root multiplexer: %s", strerror(errno));
415 return(1);
416 }
417 mustart(plexwatch, plex);
418 ioloop();
419 return(0);
420}