Removed obsolete code in fnet-dc.c.
[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 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;
152 cn->tlen = -1;
153 cn->url = dupurl(ui);
154 cn->ailist = resolvtcp(ui->host, ui->port);
155 if(htprocess(cn, 0) < 0) {
156 freehtconn(cn);
157 return(NULL);
158 }
159 return(cn);
160}
161
162int htpollflags(struct htconn *cn)
163{
164 int ret;
165
166 if(cn->fd == -1)
167 return(0);
168 ret = POLLIN;
169 if((cn->state == STATE_SYN) || (cn->outbufdata > 0))
170 ret |= POLLOUT;
171 return(ret);
172}
173
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)
187{
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;
257 socklen_t optlen;
258 char rxbuf[1024];
259 char *p, *p2, *p3;
260
261 if(cn->state == STATE_SYN) {
262 if(cn->fd != -1) {
263 optlen = sizeof(ret);
264 getsockopt(cn->fd, SOL_SOCKET, SO_ERROR, &ret, &optlen);
265 if(ret) {
266 cn->fd = -1;
267 } else {
268 consreq(cn);
269 cn->state = STATE_TXREQ;
270 }
271 }
272 if(cn->fd == -1) {
273 if(cn->curai == NULL)
274 cn->curai = cn->ailist;
275 else
276 cn->curai = cn->curai->ai_next;
277 if(cn->curai == NULL) {
278 /* Bleh! Linux and BSD don't share any good
279 * errno for this. */
280 errno = ENOENT;
281 return(-1);
282 }
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 }
293 }
294 }
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);
477}