Added test program for the HTTP library
[doldaconnect.git] / common / http.c
CommitLineData
672dbb8f
FT
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>
95c14b73 23#include <string.h>
2b6703ac
FT
24#include <ctype.h>
25#include <errno.h>
26#include <fcntl.h>
672dbb8f 27#include <sys/socket.h>
f4663439 28#include <sys/poll.h>
672dbb8f
FT
29
30#ifdef HAVE_CONFIG_H
31#include <config.h>
32#endif
95c14b73
FT
33#include <utils.h>
34#include <http.h>
672dbb8f 35
2b6703ac
FT
36#if 0
37#define HTDEBUG(fmt...) fprintf(stderr, "httplib: " fmt)
38#else
39#define HTDEBUG(fmt...)
40#endif
41
f4663439
FT
42#define STATE_SYN 0
43#define STATE_TXREQ 1
2b6703ac
FT
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
f4663439 49
061d5c59
FT
50void freeurl(struct hturlinfo *ui)
51{
52 free(ui->host);
53 free(ui->path);
54 free(ui->query);
47359765 55 free(ui->fragment);
061d5c59
FT
56 free(ui);
57}
58
95c14b73
FT
59struct 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);
f4663439
FT
73 } else {
74 ui->port = 80;
95c14b73
FT
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;
2b6703ac
FT
83 p[-1] = '/';
84 ui->path = sstrdup(p - 1);
95c14b73
FT
85 }
86 if(p2 == NULL) {
87 ui->query = sstrdup("");
88 } else {
47359765
FT
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);
95c14b73
FT
98 }
99 return(ui);
100}
f4663439
FT
101
102static 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);
47359765 111 new->fragment = sstrdup(ui->fragment);
f4663439
FT
112 return(new);
113}
114
115static 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
129void freehtconn(struct htconn *cn)
130{
131 if(cn->outbuf != NULL)
132 free(cn->outbuf);
133 if(cn->inbuf != NULL)
134 free(cn->inbuf);
2b6703ac
FT
135 if(cn->databuf != NULL)
136 free(cn->databuf);
137 if(cn->resstr != NULL)
138 free(cn->resstr);
f4663439
FT
139 freeurl(cn->url);
140 freeaddrinfo(cn->ailist);
141 if(cn->fd != -1)
142 close(cn->fd);
143 free(cn);
144}
145
146struct htconn *htconnect(struct hturlinfo *ui)
147{
148 struct htconn *cn;
149
150 cn = memset(smalloc(sizeof(*cn)), 0, sizeof(*cn));
151 cn->fd = -1;
2b6703ac 152 cn->tlen = -1;
f4663439
FT
153 cn->url = dupurl(ui);
154 cn->ailist = resolvtcp(ui->host, ui->port);
2b6703ac
FT
155 if(htprocess(cn, 0) < 0) {
156 freehtconn(cn);
157 return(NULL);
158 }
f4663439
FT
159 return(cn);
160}
161
2b6703ac 162int htpollflags(struct htconn *cn)
f4663439
FT
163{
164 int ret;
165
166 ret = POLLIN;
2b6703ac 167 if((cn->state == STATE_SYN) || (cn->outbufdata > 0))
f4663439
FT
168 ret |= POLLOUT;
169 return(ret);
170}
171
2b6703ac
FT
172static 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
184static void consreq(struct htconn *cn)
f4663439 185{
2b6703ac
FT
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
213static void trimcr(char *buf)
214{
215 for(; *buf; buf++);
216 if(*(--buf) == '\r')
217 *buf = 0;
218}
219
220static 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
252int htprocess(struct htconn *cn, int pollflags)
253{
254 int ret, done;
f4663439 255 socklen_t optlen;
2b6703ac
FT
256 char rxbuf[1024];
257 char *p, *p2, *p3;
f4663439 258
2b6703ac
FT
259 if(cn->state == STATE_SYN) {
260 if(cn->fd != -1) {
f4663439 261 optlen = sizeof(ret);
2b6703ac 262 getsockopt(cn->fd, SOL_SOCKET, SO_ERROR, &ret, &optlen);
f4663439 263 if(ret) {
2b6703ac 264 cn->fd = -1;
f4663439 265 } else {
2b6703ac
FT
266 consreq(cn);
267 cn->state = STATE_TXREQ;
f4663439
FT
268 }
269 }
2b6703ac
FT
270 if(cn->fd == -1) {
271 if(cn->curai == NULL)
272 cn->curai = cn->ailist;
f4663439 273 else
2b6703ac
FT
274 cn->curai = cn->curai->ai_next;
275 if(cn->curai == NULL) {
f4663439
FT
276 /* Bleh! Linux and BSD don't share any good
277 * errno for this. */
278 errno = ENOENT;
279 return(-1);
280 }
2b6703ac
FT
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 }
f4663439
FT
291 }
292 }
2b6703ac
FT
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);
f4663439 475}