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 freestrpair(cn->headers, &cn->headers);
142 freeaddrinfo(cn->ailist);
148 static int resethtconn(struct htconn *cn, struct hturlinfo *ui)
152 if(cn->resstr != NULL)
155 freeaddrinfo(cn->ailist);
157 freestrpair(cn->headers, &cn->headers);
159 cn->state = STATE_SYN;
161 cn->outbufdata = cn->inbufdata = cn->databufdata = 0;
167 cn->url = dupurl(ui);
168 cn->ailist = resolvtcp(ui->host, ui->port);
171 return(htprocess(cn, 0));
174 struct htconn *htconnect(struct hturlinfo *ui)
178 cn = memset(smalloc(sizeof(*cn)), 0, sizeof(*cn));
181 cn->url = dupurl(ui);
182 cn->ailist = resolvtcp(ui->host, ui->port);
183 if(htprocess(cn, 0) < 0) {
190 int htpollflags(struct htconn *cn)
197 if((cn->state == STATE_SYN) || (cn->outbufdata > 0))
202 static 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,
214 static void consreq(struct htconn *cn)
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);
223 bprintf(cn->outbuf, "%%%02X", *p);
225 if(*(cn->url->query)) {
226 addtobuf(cn->outbuf, '?');
227 for(p = cn->url->path; *p; p++) {
228 if(!(*p & 0x80) && (safechars[(int)*p] || (*p == '&')))
229 addtobuf(cn->outbuf, *p);
231 bprintf(cn->outbuf, "%%%02X", *p);
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);
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);
243 static void trimcr(char *buf)
250 static int parseheaders(struct htconn *cn)
252 char *p, *p2, *p3, *p4;
254 while((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) {
258 memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
261 if((p2 = strchr(cn->inbuf, ':')) == NULL)
264 for(p3 = cn->inbuf; isspace(*p3); p3++);
267 for(p4 = p2 - 2; isspace(*p4); p4--) {
270 for(; isspace(*p2); p2++);
271 for(p4 = p3; *p4; p4++)
273 newstrpair(p3, p2, &cn->headers);
274 if(!strcmp(p3, "content-length"))
277 memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
282 int htprocess(struct htconn *cn, int pollflags)
288 struct hturlinfo *ui;
290 if(cn->state == STATE_SYN) {
292 optlen = sizeof(ret);
293 getsockopt(cn->fd, SOL_SOCKET, SO_ERROR, &ret, &optlen);
298 cn->state = STATE_TXREQ;
302 if(cn->curai == NULL)
303 cn->curai = cn->ailist;
305 cn->curai = cn->curai->ai_next;
306 if(cn->curai == NULL) {
307 /* Bleh! Linux and BSD don't share any good
312 if((cn->fd = socket(cn->curai->ai_family, cn->curai->ai_socktype, cn->curai->ai_protocol)) < 0)
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)
320 cn->state = STATE_TXREQ;
324 if(cn->state == STATE_TXREQ) {
325 HTDEBUG("connected, sending request\n");
326 if(pollflags & POLLIN) {
331 if(pollflags & POLLOUT) {
332 if((ret = send(cn->fd, cn->outbuf, cn->outbufdata, MSG_DONTWAIT)) < 0) {
333 if(errno != EAGAIN) {
339 memmove(cn->outbuf, cn->outbuf + ret, cn->outbufdata -= ret);
340 if(cn->outbufdata == 0)
341 cn->state = STATE_RXRES;
346 * All further states will do receiving
348 if(pollflags & POLLIN) {
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) {
360 } else if(ret == 0) {
361 HTDEBUG("EOF received\n");
365 bufcat(cn->inbuf, rxbuf, ret);
366 HTDEBUG("received %i bytes of raw data, %i bytes in buffer\n", ret, cn->inbufdata);
370 /* We need to loop until all processable data has been processed,
371 * or we won't get called again */
374 if(cn->state == STATE_RXRES) {
376 if(cn->rescode == 0) {
377 HTDEBUG("received EOF before response, flaggin EPROTO\n");
381 HTDEBUG("EOF after headers, no body\n");
382 cn->state = STATE_DONE;
384 /* Headers shouldn't be this long! */
385 if(cn->inbufdata >= 65536) {
386 HTDEBUG("got suspiciously long headers, flagging ENOMEM\n");
392 HTDEBUG("received some header data\n");
394 if(cn->rescode == 0) {
395 if((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) {
396 HTDEBUG("received response line\n");
400 if((p3 = strchr(p2, ' ')) == NULL) {
407 if(strncmp(p2, "HTTP/", 5)) {
414 if((p3 = strchr(p2, ' ')) == NULL) {
421 cn->rescode = atoi(p2);
422 if((cn->rescode < 100) || (cn->rescode >= 1000)) {
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);
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");
440 cn->state = STATE_RXCHLEN;
442 HTDEBUG("receiving normally\n");
443 cn->state = STATE_RXBODY;
448 if(cn->state == STATE_RXBODY) {
451 HTDEBUG("EOF in body without Content-Length, flagging as done\n");
452 cn->state = STATE_DONE;
454 HTDEBUG("got premature if in body with Content-Length, flagging EPROTO\n");
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;
463 if((cn->tlen != -1) && (cn->rxd >= cn->tlen)) {
464 HTDEBUG("received Content-Length, flagging as done\n");
465 cn->state = STATE_DONE;
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)) {
474 HTDEBUG("trimmed chunk line: %s\n", cn->inbuf);
477 cn->chl = strtol(cn->inbuf, NULL, 16);
478 HTDEBUG("parsed chunk length: %i\n", cn->chl);
480 memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
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;
489 HTDEBUG("will read chunk\n");
490 cn->state = STATE_RXCHUNK;
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);
500 cn->state = STATE_RXCHLEN;
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);
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");
518 if((ui = parseurl(p)) == NULL) {
519 HTDEBUG("unparsable URL in redirection (%s), flagging EPROTO\n", p);
523 HTDEBUG("autohandling redirect (%i, ->%s)\n", cn->rescode, p);
524 ret = resethtconn(cn, ui);
529 return((cn->state == STATE_DONE)?1:0);