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)
167 if((cn->state == STATE_SYN) || (cn->outbufdata > 0))
172 static 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,
184 static void consreq(struct htconn *cn)
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);
193 bprintf(cn->outbuf, "%%%02X", *p);
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);
201 bprintf(cn->outbuf, "%%%02X", *p);
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);
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);
213 static void trimcr(char *buf)
220 static int parseheaders(struct htconn *cn)
222 char *p, *p2, *p3, *p4;
224 while((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) {
228 memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
231 if((p2 = strchr(cn->inbuf, ':')) == NULL)
234 for(p3 = cn->inbuf; isspace(*p3); p3++);
237 for(p4 = p2 - 2; isspace(*p4); p4--) {
240 for(; isspace(*p2); p2++);
241 for(p4 = p3; *p4; p4++)
243 newstrpair(p3, p2, &cn->headers);
244 if(!strcmp(p3, "content-length"))
247 memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
252 int htprocess(struct htconn *cn, int pollflags)
259 if(cn->state == STATE_SYN) {
261 optlen = sizeof(ret);
262 getsockopt(cn->fd, SOL_SOCKET, SO_ERROR, &ret, &optlen);
267 cn->state = STATE_TXREQ;
271 if(cn->curai == NULL)
272 cn->curai = cn->ailist;
274 cn->curai = cn->curai->ai_next;
275 if(cn->curai == NULL) {
276 /* Bleh! Linux and BSD don't share any good
281 if((cn->fd = socket(cn->curai->ai_family, cn->curai->ai_socktype, cn->curai->ai_protocol)) < 0)
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)
289 cn->state = STATE_TXREQ;
293 if(cn->state == STATE_TXREQ) {
294 HTDEBUG("connected, sending request\n");
295 if(pollflags & POLLIN) {
300 if(pollflags & POLLOUT) {
301 if((ret = send(cn->fd, cn->outbuf, cn->outbufdata, MSG_DONTWAIT)) < 0) {
302 if(errno != EAGAIN) {
308 memmove(cn->outbuf, cn->outbuf + ret, cn->outbufdata -= ret);
309 if(cn->outbufdata == 0)
310 cn->state = STATE_RXRES;
315 * All further states will do receiving
317 if(pollflags & POLLIN) {
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) {
329 } else if(ret == 0) {
330 HTDEBUG("EOF received\n");
334 bufcat(cn->inbuf, rxbuf, ret);
335 HTDEBUG("received %i bytes of raw data, %i bytes in buffer\n", ret, cn->inbufdata);
339 /* We need to loop until all processable data has been processed,
340 * or we won't get called again */
343 if(cn->state == STATE_RXRES) {
345 if(cn->rescode == 0) {
346 HTDEBUG("received EOF before response, flaggin EPROTO\n");
350 HTDEBUG("EOF after headers, no body\n");
351 cn->state = STATE_DONE;
353 /* Headers shouldn't be this long! */
354 if(cn->inbufdata >= 65536) {
355 HTDEBUG("got suspiciously long headers, flagging ENOMEM\n");
361 HTDEBUG("received some header data\n");
363 if(cn->rescode == 0) {
364 if((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) {
365 HTDEBUG("received response line\n");
369 if((p3 = strchr(p2, ' ')) == NULL) {
376 if(strncmp(p2, "HTTP/", 5)) {
383 if((p3 = strchr(p2, ' ')) == NULL) {
390 cn->rescode = atoi(p2);
391 if((cn->rescode < 100) || (cn->rescode >= 1000)) {
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);
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");
409 cn->state = STATE_RXCHLEN;
411 HTDEBUG("receiving normally\n");
412 cn->state = STATE_RXBODY;
417 if(cn->state == STATE_RXBODY) {
419 HTDEBUG("EOF in body, flagging as done\n");
420 cn->state = STATE_DONE;
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;
426 if((cn->tlen != -1) && (cn->rxd >= cn->tlen)) {
427 HTDEBUG("received Content-Length, flagging as done\n");
428 cn->state = STATE_DONE;
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)) {
437 HTDEBUG("trimmed chunk line: %s\n", cn->inbuf);
440 cn->chl = strtol(cn->inbuf, NULL, 16);
441 HTDEBUG("parsed chunk length: %i\n", cn->chl);
443 memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
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;
452 HTDEBUG("will read chunk\n");
453 cn->state = STATE_RXCHUNK;
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);
463 cn->state = STATE_RXCHLEN;
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);
474 return((cn->state == STATE_DONE)?1:0);