Fixed HTTP-client query-string handling bug.
[doldaconnect.git] / common / http.c
1 /*
2  *  Dolda Connect - Modular multiuser Direct Connect-style client
3  *  Copyright (C) 2007 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 2 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, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <sys/socket.h>
28 #include <sys/poll.h>
29
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 #include <utils.h>
34 #include <http.h>
35
36 #if 0
37 #define HTDEBUG(fmt...) fprintf(stderr, "httplib: " fmt)
38 #else
39 #define HTDEBUG(fmt...)
40 #endif
41
42 #define STATE_SYN 0
43 #define STATE_TXREQ 1
44 #define STATE_RXRES 2
45 #define STATE_RXBODY 3
46 #define STATE_RXCHLEN 4
47 #define STATE_RXCHUNK 5
48 #define STATE_DONE 6
49
50 void freeurl(struct hturlinfo *ui)
51 {
52     free(ui->host);
53     free(ui->path);
54     free(ui->query);
55     free(ui->fragment);
56     free(ui);
57 }
58
59 struct hturlinfo *parseurl(char *url)
60 {
61     char *p, *p2, *p3;
62     struct hturlinfo *ui;
63     
64     if(strncmp(url, "http://", 7))
65         return(NULL);
66     ui = memset(smalloc(sizeof(*ui)), 0, sizeof(*ui));
67     p = url + 7;
68     if((p2 = strchr(p, '/')) != NULL)
69         *(p2++) = 0;
70     if((p3 = strrchr(p, ':')) != NULL) {
71         *(p3++) = 0;
72         ui->port = atoi(p3);
73     } else {
74         ui->port = 80;
75     }
76     ui->host = sstrdup(p);
77     if(p2 == NULL) {
78         ui->path = sstrdup("/");
79     } else {
80         p = p2;
81         if((p2 = strchr(p, '?')) != NULL)
82             *(p2++) = 0;
83         p[-1] = '/';
84         ui->path = sstrdup(p - 1);
85     }
86     if(p2 == NULL) {
87         ui->query = sstrdup("");
88     } else {
89         p = p2;
90         if((p2 = strchr(p, '#')) != NULL)
91             *(p2++) = 0;
92         ui->query = sstrdup(p);
93     }
94     if(p2 == NULL) {
95         ui->fragment = sstrdup("");
96     } else {
97         ui->fragment = sstrdup(p2);
98     }
99     return(ui);
100 }
101
102 static struct hturlinfo *dupurl(struct hturlinfo *ui)
103 {
104     struct hturlinfo *new;
105     
106     new = memset(smalloc(sizeof(*new)), 0, sizeof(*new));
107     new->host = sstrdup(ui->host);
108     new->port = ui->port;
109     new->path = sstrdup(ui->path);
110     new->query = sstrdup(ui->query);
111     new->fragment = sstrdup(ui->fragment);
112     return(new);
113 }
114
115 static struct addrinfo *resolvtcp(char *name, int port)
116 {
117     struct addrinfo hint, *ret;
118     char tmp[32];
119     
120     memset(&hint, 0, sizeof(hint));
121     hint.ai_socktype = SOCK_STREAM;
122     hint.ai_flags = AI_NUMERICSERV | AI_CANONNAME;
123     snprintf(tmp, sizeof(tmp), "%i", port);
124     if(!getaddrinfo(name, tmp, &hint, &ret))
125         return(ret);
126     return(NULL);
127 }
128
129 void freehtconn(struct htconn *cn)
130 {
131     if(cn->outbuf != NULL)
132         free(cn->outbuf);
133     if(cn->inbuf != NULL)
134         free(cn->inbuf);
135     if(cn->databuf != NULL)
136         free(cn->databuf);
137     if(cn->resstr != NULL)
138         free(cn->resstr);
139     while(cn->headers)
140         freestrpair(cn->headers, &cn->headers);
141     freeurl(cn->url);
142     freeaddrinfo(cn->ailist);
143     if(cn->fd != -1)
144         close(cn->fd);
145     free(cn);
146 }
147
148 static int resethtconn(struct htconn *cn, struct hturlinfo *ui)
149 {
150     if(cn->fd != -1)
151         close(cn->fd);
152     if(cn->resstr != NULL)
153         free(cn->resstr);
154     freeurl(cn->url);
155     freeaddrinfo(cn->ailist);
156     while(cn->headers)
157         freestrpair(cn->headers, &cn->headers);
158     
159     cn->state = STATE_SYN;
160     cn->fd = -1;
161     cn->outbufdata = cn->inbufdata = cn->databufdata = 0;
162     cn->rescode = 0;
163     cn->resstr = NULL;
164     cn->tlen = -1;
165     cn->rxd = 0;
166     cn->chl = 0;
167     cn->url = dupurl(ui);
168     cn->ailist = resolvtcp(ui->host, ui->port);
169     cn->curai = NULL;
170     
171     return(htprocess(cn, 0));
172 }
173
174 struct htconn *htconnect(struct hturlinfo *ui)
175 {
176     struct htconn *cn;
177     
178     cn = memset(smalloc(sizeof(*cn)), 0, sizeof(*cn));
179     cn->fd = -1;
180     cn->tlen = -1;
181     cn->url = dupurl(ui);
182     cn->ailist = resolvtcp(ui->host, ui->port);
183     if(htprocess(cn, 0) < 0) {
184         freehtconn(cn);
185         return(NULL);
186     }
187     return(cn);
188 }
189
190 int htpollflags(struct htconn *cn)
191 {
192     int ret;
193     
194     if(cn->fd == -1)
195         return(0);
196     ret = POLLIN;
197     if((cn->state == STATE_SYN) || (cn->outbufdata > 0))
198         ret |= POLLOUT;
199     return(ret);
200 }
201
202 static char safechars[128] = {
203  /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */
204     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
205     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
206     0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1,
207     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
208     0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
209     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
210     0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
211     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
212 };
213
214 static void consreq(struct htconn *cn)
215 {
216     char *p;
217     
218     bufcat(cn->outbuf, "GET ", 4);
219     for(p = cn->url->path; *p; p++) {
220         if(!(*p & 0x80) && safechars[(int)*p])
221             addtobuf(cn->outbuf, *p);
222         else
223             bprintf(cn->outbuf, "%%%02X", *p);
224     }
225     if(*(cn->url->query)) {
226         addtobuf(cn->outbuf, '?');
227         for(p = cn->url->query; *p; p++) {
228             if(!(*p & 0x80) && (safechars[(int)*p] || (*p == '&') || (*p == '=')))
229                 addtobuf(cn->outbuf, *p);
230             else
231                 bprintf(cn->outbuf, "%%%02X", *p);
232         }
233     }
234     bufcat(cn->outbuf, " HTTP/1.1\r\n", 11);
235     if(cn->url->port != 80)
236         bprintf(cn->outbuf, "Host: %s:%i\r\n", cn->url->host, cn->url->port);
237     else
238         bprintf(cn->outbuf, "Host: %s\r\n", cn->url->host);
239     bprintf(cn->outbuf, "User-Agent: DoldaConnect/%s\r\n", VERSION);
240     bufcat(cn->outbuf, "\r\n", 2);
241 }
242
243 static void trimcr(char *buf)
244 {
245     for(; *buf; buf++);
246     if(*(--buf) == '\r')
247         *buf = 0;
248 }
249
250 static int parseheaders(struct htconn *cn)
251 {
252     char *p, *p2, *p3, *p4;
253     
254     while((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) {
255         *(p++) = 0;
256         trimcr(cn->inbuf);
257         if(!*(cn->inbuf)) {
258             memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
259             return(1);
260         }
261         if((p2 = strchr(cn->inbuf, ':')) == NULL)
262             goto skip;
263         *(p2++) = 0;
264         for(p3 = cn->inbuf; isspace(*p3); p3++);
265         if(!*p3)
266             goto skip;
267         for(p4 = p2 - 2; isspace(*p4); p4--) {
268         }
269         *(++p4) = 0;
270         for(; isspace(*p2); p2++);
271         for(p4 = p3; *p4; p4++)
272             *p4 = tolower(*p4);
273         newstrpair(p3, p2, &cn->headers);
274         if(!strcmp(p3, "content-length"))
275             cn->tlen = atoi(p2);
276     skip:
277         memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
278     }
279     return(0);
280 }
281
282 int htprocess(struct htconn *cn, int pollflags)
283 {
284     int ret, done;
285     socklen_t optlen;
286     char rxbuf[1024];
287     char *p, *p2, *p3;
288     struct hturlinfo *ui;
289     
290     if(cn->state == STATE_SYN) {
291         if(cn->fd != -1) {
292             optlen = sizeof(ret);
293             getsockopt(cn->fd, SOL_SOCKET, SO_ERROR, &ret, &optlen);
294             if(ret) {
295                 cn->fd = -1;
296             } else {
297                 consreq(cn);
298                 cn->state = STATE_TXREQ;
299             }
300         }
301         if(cn->fd == -1) {
302             if(cn->curai == NULL)
303                 cn->curai = cn->ailist;
304             else
305                 cn->curai = cn->curai->ai_next;
306             if(cn->curai == NULL) {
307                 /* Bleh! Linux and BSD don't share any good
308                  * errno for this. */
309                 errno = ENOENT;
310                 return(-1);
311             }
312             if((cn->fd = socket(cn->curai->ai_family, cn->curai->ai_socktype, cn->curai->ai_protocol)) < 0)
313                 return(-1);
314             fcntl(cn->fd, F_SETFL, fcntl(cn->fd, F_GETFL) | O_NONBLOCK);
315             if(connect(cn->fd, cn->curai->ai_addr, cn->curai->ai_addrlen) < 0) {
316                 if(errno != EINPROGRESS)
317                     return(-1);
318             } else {
319                 consreq(cn);
320                 cn->state = STATE_TXREQ;
321             }
322         }
323     }
324     if(cn->state == STATE_TXREQ) {
325         HTDEBUG("connected, sending request\n");
326         if(pollflags & POLLIN) {
327             close(cn->fd);
328             cn->fd = -1;
329             return(-1);
330         }
331         if(pollflags & POLLOUT) {
332             if((ret = send(cn->fd, cn->outbuf, cn->outbufdata, MSG_DONTWAIT)) < 0) {
333                 if(errno != EAGAIN) {
334                     close(cn->fd);
335                     cn->fd = -1;
336                     return(-1);
337                 }
338             } else {
339                 memmove(cn->outbuf, cn->outbuf + ret, cn->outbufdata -= ret);
340                 if(cn->outbufdata == 0)
341                     cn->state = STATE_RXRES;
342             }
343         }
344     }
345     /*
346      * All further states will do receiving
347      */
348     if(pollflags & POLLIN) {
349         if(cn->fd == -1) {
350             ret = 0;
351         } else {
352             if((ret = recv(cn->fd, rxbuf, sizeof(rxbuf), MSG_DONTWAIT)) < 0) {
353                 HTDEBUG("error in recv: %s\n", strerror(errno));
354                 if(errno != EAGAIN) {
355                     close(cn->fd);
356                     cn->fd = -1;
357                     return(-1);
358                 }
359                 return(0);
360             } else if(ret == 0) {
361                 HTDEBUG("EOF received\n");
362                 close(cn->fd);
363                 cn->fd = -1;
364             } else {
365                 bufcat(cn->inbuf, rxbuf, ret);
366                 HTDEBUG("received %i bytes of raw data, %i bytes in buffer\n", ret, cn->inbufdata);
367             }
368         }
369     }
370     /* We need to loop until all processable data has been processed,
371      * or we won't get called again */
372     do {
373         done = 1;
374         if(cn->state == STATE_RXRES) {
375             if(ret == 0) {
376                 if(cn->rescode == 0) {
377                     HTDEBUG("received EOF before response, flaggin EPROTO\n");
378                     errno = EPROTO;
379                     return(-1);
380                 }
381                 HTDEBUG("EOF after headers, no body\n");
382                 cn->state = STATE_DONE;
383             } else {
384                 /* Headers shouldn't be this long! */
385                 if(cn->inbufdata >= 65536) {
386                     HTDEBUG("got suspiciously long headers, flagging ENOMEM\n");
387                     close(cn->fd);
388                     cn->fd = -1;
389                     errno = ENOMEM;
390                     return(-1);
391                 }
392                 HTDEBUG("received some header data\n");
393             }
394             if(cn->rescode == 0) {
395                 if((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) {
396                     HTDEBUG("received response line\n");
397                     *(p++) = 0;
398                     trimcr(cn->inbuf);
399                     p2 = cn->inbuf;
400                     if((p3 = strchr(p2, ' ')) == NULL) {
401                         close(cn->fd);
402                         cn->fd = -1;
403                         errno = EPROTO;
404                         return(-1);
405                     }
406                     *(p3++) = 0;
407                     if(strncmp(p2, "HTTP/", 5)) {
408                         close(cn->fd);
409                         cn->fd = -1;
410                         errno = EPROTO;
411                         return(-1);
412                     }
413                     p2 = p3;
414                     if((p3 = strchr(p2, ' ')) == NULL) {
415                         close(cn->fd);
416                         cn->fd = -1;
417                         errno = EPROTO;
418                         return(-1);
419                     }
420                     *(p3++) = 0;
421                     cn->rescode = atoi(p2);
422                     if((cn->rescode < 100) || (cn->rescode >= 1000)) {
423                         close(cn->fd);
424                         cn->fd = -1;
425                         errno = EPROTO;
426                         return(-1);
427                     }
428                     cn->resstr = sstrdup(p3);
429                     memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
430                     HTDEBUG("parsed response line (%i, %s)\n", cn->rescode, cn->resstr);
431                 }
432             }
433             if(cn->rescode != 0) {
434                 HTDEBUG("parsing some headers\n");
435                 if(parseheaders(cn)) {
436                     HTDEBUG("all headers received\n");
437                     if(((p = spfind(cn->headers, "transfer-encoding")) != NULL) && !strcasecmp(p, "chunked")) {
438                         HTDEBUG("doing chunky decoding\n");
439                         cn->chl = -1;
440                         cn->state = STATE_RXCHLEN;
441                     } else {
442                         HTDEBUG("receiving normally\n");
443                         cn->state = STATE_RXBODY;
444                     }
445                 }
446             }
447         }
448         if(cn->state == STATE_RXBODY) {
449             if(ret == 0) {
450                 if(cn->tlen == -1) {
451                     HTDEBUG("EOF in body without Content-Length, flagging as done\n");
452                     cn->state = STATE_DONE;
453                 } else {
454                     HTDEBUG("got premature if in body with Content-Length, flagging EPROTO\n");
455                     errno = EPROTO;
456                     return(-1);
457                 }
458             } else {
459                 bufcat(cn->databuf, cn->inbuf, cn->inbufdata);
460                 HTDEBUG("transferred %i bytes from inbuf to databuf, %i bytes now in databuf\n", cn->inbufdata, cn->databufdata);
461                 cn->rxd += cn->inbufdata;
462                 cn->inbufdata = 0;
463                 if((cn->tlen != -1) && (cn->rxd >= cn->tlen)) {
464                     HTDEBUG("received Content-Length, flagging as done\n");
465                     cn->state = STATE_DONE;
466                 }
467             }
468         }
469         if(cn->state == STATE_RXCHLEN) {
470             HTDEBUG("trying to parse chunk length\n");
471             while(((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) && (cn->chl == -1)) {
472                 *(p++) = 0;
473                 trimcr(cn->inbuf);
474                 HTDEBUG("trimmed chunk line: %s\n", cn->inbuf);
475                 if(!*cn->inbuf)
476                     goto skip;
477                 cn->chl = strtol(cn->inbuf, NULL, 16);
478                 HTDEBUG("parsed chunk length: %i\n", cn->chl);
479             skip:
480                 memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
481             }
482             if(cn->chl == 0) {
483                 HTDEBUG("zero chunk length, looking for CRLF\n");
484                 if((cn->inbuf[0] == '\r') && (cn->inbuf[1] == '\n')) {
485                     HTDEBUG("ending CRLF gotten, flagging as done\n");
486                     cn->state = STATE_DONE;
487                 }
488             } else {
489                 HTDEBUG("will read chunk\n");
490                 cn->state = STATE_RXCHUNK;
491             }
492         }
493         if(cn->state == STATE_RXCHUNK) {
494             if(cn->inbufdata >= cn->chl) {
495                 bufcat(cn->databuf, cn->inbuf, cn->chl);
496                 memmove(cn->inbuf, cn->inbuf + cn->chl, cn->inbufdata -= cn->chl);
497                 HTDEBUG("received final %i bytes of chunk, inbuf %i bytes, databuf %i bytes\n", cn->chl, cn->inbufdata, cn->databufdata);
498                 cn->rxd += cn->chl;
499                 cn->chl = 0;
500                 cn->state = STATE_RXCHLEN;
501                 done = 0;
502             } else {
503                 bufcat(cn->databuf, cn->inbuf, cn->inbufdata);
504                 cn->chl -= cn->inbufdata;
505                 cn->rxd += cn->inbufdata;
506                 HTDEBUG("received %i bytes of chunk, %i bytes remaining, %i bytes in databuf\n", cn->inbufdata, cn->chl, cn->databufdata);
507                 cn->inbufdata = 0;
508             }
509         }
510     } while(!done);
511     if((cn->state == STATE_DONE) && cn->autoredir) {
512         if((cn->rescode == 301) || (cn->rescode == 302) || (cn->rescode == 303) || (cn->rescode == 307)) {
513             if((p = spfind(cn->headers, "location")) == NULL) {
514                 HTDEBUG("got redirect without Location, flagging EPROTO\n");
515                 errno = EPROTO;
516                 return(-1);
517             }
518             if((ui = parseurl(p)) == NULL) {
519                 HTDEBUG("unparsable URL in redirection (%s), flagging EPROTO\n", p);
520                 errno = EPROTO;
521                 return(-1);
522             }
523             HTDEBUG("autohandling redirect (%i, ->%s)\n", cn->rescode, p);
524             ret = resethtconn(cn, ui);
525             freeurl(ui);
526             return(ret);
527         }
528     }
529     return((cn->state == STATE_DONE)?1:0);
530 }