Fixed HTTP-client query-string handling bug.
[doldaconnect.git] / common / http.c
CommitLineData
672dbb8f
FT
1/*
2 * Dolda Connect - Modular multiuser Direct Connect-style client
302a2600 3 * Copyright (C) 2007 Fredrik Tolf <fredrik@dolda2000.com>
672dbb8f
FT
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);
78ba3ee1
FT
139 while(cn->headers)
140 freestrpair(cn->headers, &cn->headers);
f4663439
FT
141 freeurl(cn->url);
142 freeaddrinfo(cn->ailist);
143 if(cn->fd != -1)
144 close(cn->fd);
145 free(cn);
146}
147
78ba3ee1
FT
148static 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
f4663439
FT
174struct htconn *htconnect(struct hturlinfo *ui)
175{
176 struct htconn *cn;
177
178 cn = memset(smalloc(sizeof(*cn)), 0, sizeof(*cn));
179 cn->fd = -1;
2b6703ac 180 cn->tlen = -1;
f4663439
FT
181 cn->url = dupurl(ui);
182 cn->ailist = resolvtcp(ui->host, ui->port);
2b6703ac
FT
183 if(htprocess(cn, 0) < 0) {
184 freehtconn(cn);
185 return(NULL);
186 }
f4663439
FT
187 return(cn);
188}
189
2b6703ac 190int htpollflags(struct htconn *cn)
f4663439
FT
191{
192 int ret;
193
c340c8ab
FT
194 if(cn->fd == -1)
195 return(0);
f4663439 196 ret = POLLIN;
2b6703ac 197 if((cn->state == STATE_SYN) || (cn->outbufdata > 0))
f4663439
FT
198 ret |= POLLOUT;
199 return(ret);
200}
201
2b6703ac
FT
202static 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
214static void consreq(struct htconn *cn)
f4663439 215{
2b6703ac
FT
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, '?');
6fa04fbd
FT
227 for(p = cn->url->query; *p; p++) {
228 if(!(*p & 0x80) && (safechars[(int)*p] || (*p == '&') || (*p == '=')))
2b6703ac
FT
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
243static void trimcr(char *buf)
244{
245 for(; *buf; buf++);
246 if(*(--buf) == '\r')
247 *buf = 0;
248}
249
250static 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
282int htprocess(struct htconn *cn, int pollflags)
283{
284 int ret, done;
f4663439 285 socklen_t optlen;
2b6703ac
FT
286 char rxbuf[1024];
287 char *p, *p2, *p3;
78ba3ee1 288 struct hturlinfo *ui;
f4663439 289
2b6703ac
FT
290 if(cn->state == STATE_SYN) {
291 if(cn->fd != -1) {
f4663439 292 optlen = sizeof(ret);
2b6703ac 293 getsockopt(cn->fd, SOL_SOCKET, SO_ERROR, &ret, &optlen);
f4663439 294 if(ret) {
2b6703ac 295 cn->fd = -1;
f4663439 296 } else {
2b6703ac
FT
297 consreq(cn);
298 cn->state = STATE_TXREQ;
f4663439
FT
299 }
300 }
2b6703ac
FT
301 if(cn->fd == -1) {
302 if(cn->curai == NULL)
303 cn->curai = cn->ailist;
f4663439 304 else
2b6703ac
FT
305 cn->curai = cn->curai->ai_next;
306 if(cn->curai == NULL) {
f4663439
FT
307 /* Bleh! Linux and BSD don't share any good
308 * errno for this. */
309 errno = ENOENT;
310 return(-1);
311 }
2b6703ac
FT
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 }
f4663439
FT
322 }
323 }
2b6703ac
FT
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) {
78ba3ee1
FT
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 }
2b6703ac
FT
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);
78ba3ee1
FT
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 }
2b6703ac 529 return((cn->state == STATE_DONE)?1:0);
f4663439 530}