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