Fixed HTTP-client query-string handling bug.
[doldaconnect.git] / common / http.c
... / ...
CommitLineData
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
50void 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
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);
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
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);
111 new->fragment = sstrdup(ui->fragment);
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);
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
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
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;
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
190int 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
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)
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
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;
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}