Created a release checklist.
[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);
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
c340c8ab
FT
166 if(cn->fd == -1)
167 return(0);
f4663439 168 ret = POLLIN;
2b6703ac 169 if((cn->state == STATE_SYN) || (cn->outbufdata > 0))
f4663439
FT
170 ret |= POLLOUT;
171 return(ret);
172}
173
2b6703ac
FT
174static 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
186static void consreq(struct htconn *cn)
f4663439 187{
2b6703ac
FT
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
215static void trimcr(char *buf)
216{
217 for(; *buf; buf++);
218 if(*(--buf) == '\r')
219 *buf = 0;
220}
221
222static 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
254int htprocess(struct htconn *cn, int pollflags)
255{
256 int ret, done;
f4663439 257 socklen_t optlen;
2b6703ac
FT
258 char rxbuf[1024];
259 char *p, *p2, *p3;
f4663439 260
2b6703ac
FT
261 if(cn->state == STATE_SYN) {
262 if(cn->fd != -1) {
f4663439 263 optlen = sizeof(ret);
2b6703ac 264 getsockopt(cn->fd, SOL_SOCKET, SO_ERROR, &ret, &optlen);
f4663439 265 if(ret) {
2b6703ac 266 cn->fd = -1;
f4663439 267 } else {
2b6703ac
FT
268 consreq(cn);
269 cn->state = STATE_TXREQ;
f4663439
FT
270 }
271 }
2b6703ac
FT
272 if(cn->fd == -1) {
273 if(cn->curai == NULL)
274 cn->curai = cn->ailist;
f4663439 275 else
2b6703ac
FT
276 cn->curai = cn->curai->ai_next;
277 if(cn->curai == NULL) {
f4663439
FT
278 /* Bleh! Linux and BSD don't share any good
279 * errno for this. */
280 errno = ENOENT;
281 return(-1);
282 }
2b6703ac
FT
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 }
f4663439
FT
293 }
294 }
2b6703ac
FT
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);
f4663439 477}