8190f2d9730403d3f826265aa9190cf52cfa4792
[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     freeurl(cn->url);
140     freeaddrinfo(cn->ailist);
141     if(cn->fd != -1)
142         close(cn->fd);
143     free(cn);
144 }
145
146 struct htconn *htconnect(struct hturlinfo *ui)
147 {
148     struct htconn *cn;
149     
150     cn = memset(smalloc(sizeof(*cn)), 0, sizeof(*cn));
151     cn->fd = -1;
152     cn->tlen = -1;
153     cn->url = dupurl(ui);
154     cn->ailist = resolvtcp(ui->host, ui->port);
155     if(htprocess(cn, 0) < 0) {
156         freehtconn(cn);
157         return(NULL);
158     }
159     return(cn);
160 }
161
162 int htpollflags(struct htconn *cn)
163 {
164     int ret;
165     
166     if(cn->fd == -1)
167         return(0);
168     ret = POLLIN;
169     if((cn->state == STATE_SYN) || (cn->outbufdata > 0))
170         ret |= POLLOUT;
171     return(ret);
172 }
173
174 static char safechars[128] = {
175  /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */
176     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
177     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
178     0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1,
179     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
180     0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
181     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
182     0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
183     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
184 };
185
186 static void consreq(struct htconn *cn)
187 {
188     char *p;
189     
190     bufcat(cn->outbuf, "GET ", 4);
191     for(p = cn->url->path; *p; p++) {
192         if(!(*p & 0x80) && safechars[(int)*p])
193             addtobuf(cn->outbuf, *p);
194         else
195             bprintf(cn->outbuf, "%%%02X", *p);
196     }
197     if(*(cn->url->query)) {
198         addtobuf(cn->outbuf, '?');
199         for(p = cn->url->path; *p; p++) {
200             if(!(*p & 0x80) && (safechars[(int)*p] || (*p == '&')))
201                 addtobuf(cn->outbuf, *p);
202             else
203                 bprintf(cn->outbuf, "%%%02X", *p);
204         }
205     }
206     bufcat(cn->outbuf, " HTTP/1.1\r\n", 11);
207     if(cn->url->port != 80)
208         bprintf(cn->outbuf, "Host: %s:%i\r\n", cn->url->host, cn->url->port);
209     else
210         bprintf(cn->outbuf, "Host: %s\r\n", cn->url->host);
211     bprintf(cn->outbuf, "User-Agent: DoldaConnect/%s\r\n", VERSION);
212     bufcat(cn->outbuf, "\r\n", 2);
213 }
214
215 static void trimcr(char *buf)
216 {
217     for(; *buf; buf++);
218     if(*(--buf) == '\r')
219         *buf = 0;
220 }
221
222 static int parseheaders(struct htconn *cn)
223 {
224     char *p, *p2, *p3, *p4;
225     
226     while((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) {
227         *(p++) = 0;
228         trimcr(cn->inbuf);
229         if(!*(cn->inbuf)) {
230             memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
231             return(1);
232         }
233         if((p2 = strchr(cn->inbuf, ':')) == NULL)
234             goto skip;
235         *(p2++) = 0;
236         for(p3 = cn->inbuf; isspace(*p3); p3++);
237         if(!*p3)
238             goto skip;
239         for(p4 = p2 - 2; isspace(*p4); p4--) {
240         }
241         *(++p4) = 0;
242         for(; isspace(*p2); p2++);
243         for(p4 = p3; *p4; p4++)
244             *p4 = tolower(*p4);
245         newstrpair(p3, p2, &cn->headers);
246         if(!strcmp(p3, "content-length"))
247             cn->tlen = atoi(p2);
248     skip:
249         memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
250     }
251     return(0);
252 }
253
254 int htprocess(struct htconn *cn, int pollflags)
255 {
256     int ret, done;
257     socklen_t optlen;
258     char rxbuf[1024];
259     char *p, *p2, *p3;
260     
261     if(cn->state == STATE_SYN) {
262         if(cn->fd != -1) {
263             optlen = sizeof(ret);
264             getsockopt(cn->fd, SOL_SOCKET, SO_ERROR, &ret, &optlen);
265             if(ret) {
266                 cn->fd = -1;
267             } else {
268                 consreq(cn);
269                 cn->state = STATE_TXREQ;
270             }
271         }
272         if(cn->fd == -1) {
273             if(cn->curai == NULL)
274                 cn->curai = cn->ailist;
275             else
276                 cn->curai = cn->curai->ai_next;
277             if(cn->curai == NULL) {
278                 /* Bleh! Linux and BSD don't share any good
279                  * errno for this. */
280                 errno = ENOENT;
281                 return(-1);
282             }
283             if((cn->fd = socket(cn->curai->ai_family, cn->curai->ai_socktype, cn->curai->ai_protocol)) < 0)
284                 return(-1);
285             fcntl(cn->fd, F_SETFL, fcntl(cn->fd, F_GETFL) | O_NONBLOCK);
286             if(connect(cn->fd, cn->curai->ai_addr, cn->curai->ai_addrlen) < 0) {
287                 if(errno != EINPROGRESS)
288                     return(-1);
289             } else {
290                 consreq(cn);
291                 cn->state = STATE_TXREQ;
292             }
293         }
294     }
295     if(cn->state == STATE_TXREQ) {
296         HTDEBUG("connected, sending request\n");
297         if(pollflags & POLLIN) {
298             close(cn->fd);
299             cn->fd = -1;
300             return(-1);
301         }
302         if(pollflags & POLLOUT) {
303             if((ret = send(cn->fd, cn->outbuf, cn->outbufdata, MSG_DONTWAIT)) < 0) {
304                 if(errno != EAGAIN) {
305                     close(cn->fd);
306                     cn->fd = -1;
307                     return(-1);
308                 }
309             } else {
310                 memmove(cn->outbuf, cn->outbuf + ret, cn->outbufdata -= ret);
311                 if(cn->outbufdata == 0)
312                     cn->state = STATE_RXRES;
313             }
314         }
315     }
316     /*
317      * All further states will do receiving
318      */
319     if(pollflags & POLLIN) {
320         if(cn->fd == -1) {
321             ret = 0;
322         } else {
323             if((ret = recv(cn->fd, rxbuf, sizeof(rxbuf), MSG_DONTWAIT)) < 0) {
324                 HTDEBUG("error in recv: %s\n", strerror(errno));
325                 if(errno != EAGAIN) {
326                     close(cn->fd);
327                     cn->fd = -1;
328                     return(-1);
329                 }
330                 return(0);
331             } else if(ret == 0) {
332                 HTDEBUG("EOF received\n");
333                 close(cn->fd);
334                 cn->fd = -1;
335             } else {
336                 bufcat(cn->inbuf, rxbuf, ret);
337                 HTDEBUG("received %i bytes of raw data, %i bytes in buffer\n", ret, cn->inbufdata);
338             }
339         }
340     }
341     /* We need to loop until all processable data has been processed,
342      * or we won't get called again */
343     do {
344         done = 1;
345         if(cn->state == STATE_RXRES) {
346             if(ret == 0) {
347                 if(cn->rescode == 0) {
348                     HTDEBUG("received EOF before response, flaggin EPROTO\n");
349                     errno = EPROTO;
350                     return(-1);
351                 }
352                 HTDEBUG("EOF after headers, no body\n");
353                 cn->state = STATE_DONE;
354             } else {
355                 /* Headers shouldn't be this long! */
356                 if(cn->inbufdata >= 65536) {
357                     HTDEBUG("got suspiciously long headers, flagging ENOMEM\n");
358                     close(cn->fd);
359                     cn->fd = -1;
360                     errno = ENOMEM;
361                     return(-1);
362                 }
363                 HTDEBUG("received some header data\n");
364             }
365             if(cn->rescode == 0) {
366                 if((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) {
367                     HTDEBUG("received response line\n");
368                     *(p++) = 0;
369                     trimcr(cn->inbuf);
370                     p2 = cn->inbuf;
371                     if((p3 = strchr(p2, ' ')) == NULL) {
372                         close(cn->fd);
373                         cn->fd = -1;
374                         errno = EPROTO;
375                         return(-1);
376                     }
377                     *(p3++) = 0;
378                     if(strncmp(p2, "HTTP/", 5)) {
379                         close(cn->fd);
380                         cn->fd = -1;
381                         errno = EPROTO;
382                         return(-1);
383                     }
384                     p2 = p3;
385                     if((p3 = strchr(p2, ' ')) == NULL) {
386                         close(cn->fd);
387                         cn->fd = -1;
388                         errno = EPROTO;
389                         return(-1);
390                     }
391                     *(p3++) = 0;
392                     cn->rescode = atoi(p2);
393                     if((cn->rescode < 100) || (cn->rescode >= 1000)) {
394                         close(cn->fd);
395                         cn->fd = -1;
396                         errno = EPROTO;
397                         return(-1);
398                     }
399                     cn->resstr = sstrdup(p3);
400                     memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
401                     HTDEBUG("parsed response line (%i, %s)\n", cn->rescode, cn->resstr);
402                 }
403             }
404             if(cn->rescode != 0) {
405                 HTDEBUG("parsing some headers\n");
406                 if(parseheaders(cn)) {
407                     HTDEBUG("all headers received\n");
408                     if(((p = spfind(cn->headers, "transfer-encoding")) != NULL) && !strcasecmp(p, "chunked")) {
409                         HTDEBUG("doing chunky decoding\n");
410                         cn->chl = -1;
411                         cn->state = STATE_RXCHLEN;
412                     } else {
413                         HTDEBUG("receiving normally\n");
414                         cn->state = STATE_RXBODY;
415                     }
416                 }
417             }
418         }
419         if(cn->state == STATE_RXBODY) {
420             if(ret == 0) {
421                 HTDEBUG("EOF in body, flagging as done\n");
422                 cn->state = STATE_DONE;
423             } else {
424                 bufcat(cn->databuf, cn->inbuf, cn->inbufdata);
425                 HTDEBUG("transferred %i bytes from inbuf to databuf, %i bytes now in databuf\n", cn->inbufdata, cn->databufdata);
426                 cn->rxd += cn->inbufdata;
427                 cn->inbufdata = 0;
428                 if((cn->tlen != -1) && (cn->rxd >= cn->tlen)) {
429                     HTDEBUG("received Content-Length, flagging as done\n");
430                     cn->state = STATE_DONE;
431                 }
432             }
433         }
434         if(cn->state == STATE_RXCHLEN) {
435             HTDEBUG("trying to parse chunk length\n");
436             while(((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) && (cn->chl == -1)) {
437                 *(p++) = 0;
438                 trimcr(cn->inbuf);
439                 HTDEBUG("trimmed chunk line: %s\n", cn->inbuf);
440                 if(!*cn->inbuf)
441                     goto skip;
442                 cn->chl = strtol(cn->inbuf, NULL, 16);
443                 HTDEBUG("parsed chunk length: %i\n", cn->chl);
444             skip:
445                 memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
446             }
447             if(cn->chl == 0) {
448                 HTDEBUG("zero chunk length, looking for CRLF\n");
449                 if((cn->inbuf[0] == '\r') && (cn->inbuf[1] == '\n')) {
450                     HTDEBUG("ending CRLF gotten, flagging as done\n");
451                     cn->state = STATE_DONE;
452                 }
453             } else {
454                 HTDEBUG("will read chunk\n");
455                 cn->state = STATE_RXCHUNK;
456             }
457         }
458         if(cn->state == STATE_RXCHUNK) {
459             if(cn->inbufdata >= cn->chl) {
460                 bufcat(cn->databuf, cn->inbuf, cn->chl);
461                 memmove(cn->inbuf, cn->inbuf + cn->chl, cn->inbufdata -= cn->chl);
462                 HTDEBUG("received final %i bytes of chunk, inbuf %i bytes, databuf %i bytes\n", cn->chl, cn->inbufdata, cn->databufdata);
463                 cn->rxd += cn->chl;
464                 cn->chl = 0;
465                 cn->state = STATE_RXCHLEN;
466                 done = 0;
467             } else {
468                 bufcat(cn->databuf, cn->inbuf, cn->inbufdata);
469                 cn->chl -= cn->inbufdata;
470                 cn->rxd += cn->inbufdata;
471                 HTDEBUG("received %i bytes of chunk, %i bytes remaining, %i bytes in databuf\n", cn->inbufdata, cn->chl, cn->databufdata);
472                 cn->inbufdata = 0;
473             }
474         }
475     } while(!done);
476     return((cn->state == STATE_DONE)?1:0);
477 }