Made htparser listening much more flexible.
[ashd.git] / src / htparser.c
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
40 static int plex;
41
42 static 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     
91 fail:
92     if(req != NULL) {
93         freehthead(req);
94         req = NULL;
95     }
96 out:
97     buffree(method);
98     buffree(url);
99     buffree(ver);
100     return(req);
101 }
102
103 static 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     
152 fail:
153     if(req != NULL) {
154         freehthead(req);
155         req = NULL;
156     }
157 out:
158     buffree(msg);
159     buffree(ver);
160     return(req);
161 }
162
163 static 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
184 static 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
201 static 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
210 void 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
313 static 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
335 static 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
342 static 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
383 int 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 }