2 * Dolda Connect - Modular multiuser Direct Connect-style client
3 * Copyright (C) 2007 Fredrik Tolf <fredrik@dolda2000.com>
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.
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.
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
27 #include <sys/socket.h>
37 #define HTDEBUG(fmt...) fprintf(stderr, "httplib: " fmt)
39 #define HTDEBUG(fmt...)
45 #define STATE_RXBODY 3
46 #define STATE_RXCHLEN 4
47 #define STATE_RXCHUNK 5
50 void freeurl(struct hturlinfo *ui)
59 struct hturlinfo *parseurl(char *url)
64 if(strncmp(url, "http://", 7))
66 ui = memset(smalloc(sizeof(*ui)), 0, sizeof(*ui));
68 if((p2 = strchr(p, '/')) != NULL)
70 if((p3 = strrchr(p, ':')) != NULL) {
76 ui->host = sstrdup(p);
78 ui->path = sstrdup("/");
81 if((p2 = strchr(p, '?')) != NULL)
84 ui->path = sstrdup(p - 1);
87 ui->query = sstrdup("");
90 if((p2 = strchr(p, '#')) != NULL)
92 ui->query = sstrdup(p);
95 ui->fragment = sstrdup("");
97 ui->fragment = sstrdup(p2);
102 static struct hturlinfo *dupurl(struct hturlinfo *ui)
104 struct hturlinfo *new;
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);
115 static struct addrinfo *resolvtcp(char *name, int port)
117 struct addrinfo hint, *ret;
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))
129 void freehtconn(struct htconn *cn)
131 if(cn->outbuf != NULL)
133 if(cn->inbuf != NULL)
135 if(cn->databuf != NULL)
137 if(cn->resstr != NULL)
140 freeaddrinfo(cn->ailist);
146 struct htconn *htconnect(struct hturlinfo *ui)
150 cn = memset(smalloc(sizeof(*cn)), 0, sizeof(*cn));
153 cn->url = dupurl(ui);
154 cn->ailist = resolvtcp(ui->host, ui->port);
155 if(htprocess(cn, 0) < 0) {
162 int htpollflags(struct htconn *cn)
169 if((cn->state == STATE_SYN) || (cn->outbufdata > 0))
174 static 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,
186 static void consreq(struct htconn *cn)
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);
195 bprintf(cn->outbuf, "%%%02X", *p);
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);
203 bprintf(cn->outbuf, "%%%02X", *p);
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);
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);
215 static void trimcr(char *buf)
222 static int parseheaders(struct htconn *cn)
224 char *p, *p2, *p3, *p4;
226 while((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) {
230 memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
233 if((p2 = strchr(cn->inbuf, ':')) == NULL)
236 for(p3 = cn->inbuf; isspace(*p3); p3++);
239 for(p4 = p2 - 2; isspace(*p4); p4--) {
242 for(; isspace(*p2); p2++);
243 for(p4 = p3; *p4; p4++)
245 newstrpair(p3, p2, &cn->headers);
246 if(!strcmp(p3, "content-length"))
249 memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
254 int htprocess(struct htconn *cn, int pollflags)
261 if(cn->state == STATE_SYN) {
263 optlen = sizeof(ret);
264 getsockopt(cn->fd, SOL_SOCKET, SO_ERROR, &ret, &optlen);
269 cn->state = STATE_TXREQ;
273 if(cn->curai == NULL)
274 cn->curai = cn->ailist;
276 cn->curai = cn->curai->ai_next;
277 if(cn->curai == NULL) {
278 /* Bleh! Linux and BSD don't share any good
283 if((cn->fd = socket(cn->curai->ai_family, cn->curai->ai_socktype, cn->curai->ai_protocol)) < 0)
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)
291 cn->state = STATE_TXREQ;
295 if(cn->state == STATE_TXREQ) {
296 HTDEBUG("connected, sending request\n");
297 if(pollflags & POLLIN) {
302 if(pollflags & POLLOUT) {
303 if((ret = send(cn->fd, cn->outbuf, cn->outbufdata, MSG_DONTWAIT)) < 0) {
304 if(errno != EAGAIN) {
310 memmove(cn->outbuf, cn->outbuf + ret, cn->outbufdata -= ret);
311 if(cn->outbufdata == 0)
312 cn->state = STATE_RXRES;
317 * All further states will do receiving
319 if(pollflags & POLLIN) {
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) {
331 } else if(ret == 0) {
332 HTDEBUG("EOF received\n");
336 bufcat(cn->inbuf, rxbuf, ret);
337 HTDEBUG("received %i bytes of raw data, %i bytes in buffer\n", ret, cn->inbufdata);
341 /* We need to loop until all processable data has been processed,
342 * or we won't get called again */
345 if(cn->state == STATE_RXRES) {
347 if(cn->rescode == 0) {
348 HTDEBUG("received EOF before response, flaggin EPROTO\n");
352 HTDEBUG("EOF after headers, no body\n");
353 cn->state = STATE_DONE;
355 /* Headers shouldn't be this long! */
356 if(cn->inbufdata >= 65536) {
357 HTDEBUG("got suspiciously long headers, flagging ENOMEM\n");
363 HTDEBUG("received some header data\n");
365 if(cn->rescode == 0) {
366 if((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) {
367 HTDEBUG("received response line\n");
371 if((p3 = strchr(p2, ' ')) == NULL) {
378 if(strncmp(p2, "HTTP/", 5)) {
385 if((p3 = strchr(p2, ' ')) == NULL) {
392 cn->rescode = atoi(p2);
393 if((cn->rescode < 100) || (cn->rescode >= 1000)) {
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);
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");
411 cn->state = STATE_RXCHLEN;
413 HTDEBUG("receiving normally\n");
414 cn->state = STATE_RXBODY;
419 if(cn->state == STATE_RXBODY) {
421 HTDEBUG("EOF in body, flagging as done\n");
422 cn->state = STATE_DONE;
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;
428 if((cn->tlen != -1) && (cn->rxd >= cn->tlen)) {
429 HTDEBUG("received Content-Length, flagging as done\n");
430 cn->state = STATE_DONE;
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)) {
439 HTDEBUG("trimmed chunk line: %s\n", cn->inbuf);
442 cn->chl = strtol(cn->inbuf, NULL, 16);
443 HTDEBUG("parsed chunk length: %i\n", cn->chl);
445 memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
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;
454 HTDEBUG("will read chunk\n");
455 cn->state = STATE_RXCHUNK;
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);
465 cn->state = STATE_RXCHLEN;
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);
476 return((cn->state == STATE_DONE)?1:0);