2
Copyright (c) 2009-2010
3
Lars-Dominik Braun <lars@6xq.net>
5
Permission is hereby granted, free of charge, to any person obtaining a copy
6
of this software and associated documentation files (the "Software"), to deal
7
in the Software without restriction, including without limitation the rights
8
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
copies of the Software, and to permit persons to whom the Software is
10
furnished to do so, subject to the following conditions:
12
The above copyright notice and this permission notice shall be included in
13
all copies or substantial portions of the Software.
15
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
#define _POSIX_C_SOURCE 1 /* required by getaddrinfo() */
25
#define _BSD_SOURCE /* snprintf() */
27
#include <sys/types.h>
28
#include <sys/socket.h>
45
} WaitressFetchBufCbBuffer_t;
47
inline void WaitressInit (WaitressHandle_t *waith) {
48
memset (waith, 0, sizeof (*waith));
49
waith->socktimeout = 30000;
52
inline void WaitressFree (WaitressHandle_t *waith) {
53
memset (waith, 0, sizeof (*waith));
57
* @param Waitress handle
60
static inline char WaitressProxyEnabled (const WaitressHandle_t *waith) {
61
return *waith->proxyHost != '\0' && *waith->proxyPort != '\0';
65
* @param waitress handle
69
inline void WaitressSetProxy (WaitressHandle_t *waith, const char *host,
71
strncpy (waith->proxyHost, host, sizeof (waith->proxyHost)-1);
72
strncpy (waith->proxyPort, port, sizeof (waith->proxyPort)-1);
75
/* urlencode post-data
77
* @return malloc'ed encoded string, don't forget to free it
79
char *WaitressUrlEncode (const char *in) {
80
size_t inLen = strlen (in);
81
/* worst case: encode all characters */
82
char *out = calloc (inLen * 3 + 1, sizeof (*in));
83
const char *inPos = in;
86
while (inPos - in < inLen) {
87
if (!isalnum (*inPos) && *inPos != '_' && *inPos != '-' && *inPos != '.') {
89
snprintf (outPos, 3, "%02x", *inPos & 0xff);
101
/* Split http url into host, port and path
103
* @param return buffer: host
104
* @param host buffer size
105
* @param return buffer: port, defaults to 80
106
* @param port buffer size
107
* @param return buffer: path
108
* @param path buffer size
109
* @param 1 = ok, 0 = not a http url; if your buffers are too small horrible
110
* things will happen... But 1 is returned anyway.
112
char WaitressSplitUrl (const char *url, char *retHost, size_t retHostSize,
113
char *retPort, size_t retPortSize, char *retPath, size_t retPathSize) {
114
size_t urlSize = strlen (url);
115
const char *urlPos = url, *lastPos;
117
if (urlSize > sizeof ("http://")-1 &&
118
memcmp (url, "http://", sizeof ("http://")-1) == 0) {
119
memset (retHost, 0, retHostSize);
120
memset (retPort, 0, retPortSize);
121
strncpy (retPort, "80", retPortSize-1);
122
memset (retPath, 0, retPathSize);
124
urlPos += sizeof ("http://")-1;
128
while (*urlPos != '\0' && *urlPos != ':' && *urlPos != '/' &&
129
urlPos - lastPos < retHostSize-1) {
130
*retHost++ = *urlPos++;
134
/* port, if available */
135
if (*urlPos == ':') {
139
while (*urlPos != '\0' && *urlPos != '/' &&
140
urlPos - lastPos < retPortSize-1) {
141
*retPort++ = *urlPos++;
147
while (*urlPos != '\0' && *urlPos != '#' &&
148
urlPos - lastPos < retPathSize-1) {
149
*retPath++ = *urlPos++;
157
/* Parse url and set host, port, path
158
* @param Waitress handle
159
* @param url: protocol://host:port/path
161
inline char WaitressSetUrl (WaitressHandle_t *waith, const char *url) {
162
return WaitressSplitUrl (url, waith->host, sizeof (waith->host),
163
waith->port, sizeof (waith->port), waith->path, sizeof (waith->path));
166
/* Set host, port, path
167
* @param Waitress handle
169
* @param port (getaddrinfo () needs a string...)
170
* @param path, including leading /
172
inline void WaitressSetHPP (WaitressHandle_t *waith, const char *host,
173
const char *port, const char *path) {
174
strncpy (waith->host, host, sizeof (waith->host)-1);
175
strncpy (waith->port, port, sizeof (waith->port)-1);
176
strncpy (waith->path, path, sizeof (waith->path)-1);
179
/* Callback for WaitressFetchBuf, appends received data to NULL-terminated buffer
180
* @param received data
182
* @param buffer structure
184
static WaitressCbReturn_t WaitressFetchBufCb (void *recvData, size_t recvDataSize,
186
char *recvBytes = recvData;
187
WaitressFetchBufCbBuffer_t *buffer = extraData;
189
if (buffer->buf == NULL) {
190
if ((buffer->buf = malloc (sizeof (*buffer->buf) *
191
(recvDataSize + 1))) == NULL) {
192
return WAITRESS_CB_RET_ERR;
196
if ((newbuf = realloc (buffer->buf,
197
sizeof (*buffer->buf) *
198
(buffer->pos + recvDataSize + 1))) == NULL) {
200
return WAITRESS_CB_RET_ERR;
202
buffer->buf = newbuf;
204
memcpy (buffer->buf + buffer->pos, recvBytes, recvDataSize);
205
buffer->pos += recvDataSize;
206
*(buffer->buf+buffer->pos) = '\0';
208
return WAITRESS_CB_RET_OK;
211
/* Fetch string. Beware! This overwrites your waith->data pointer
212
* @param waitress handle
213
* @param result buffer, malloced (don't forget to free it yourself)
215
WaitressReturn_t WaitressFetchBuf (WaitressHandle_t *waith, char **buf) {
216
WaitressFetchBufCbBuffer_t buffer;
217
WaitressReturn_t wRet;
219
memset (&buffer, 0, sizeof (buffer));
221
waith->data = &buffer;
222
waith->callback = WaitressFetchBufCb;
224
wRet = WaitressFetchCall (waith);
229
/* poll wrapper that retries after signal interrupts, required for socksify
232
static int WaitressPollLoop (struct pollfd *fds, nfds_t nfds, int timeout) {
237
pollres = poll (fds, nfds, timeout);
240
} while (pollerr == EINTR || pollerr == EINPROGRESS || pollerr == EAGAIN);
245
/* write () wrapper with poll () timeout
247
* @param write buffer
248
* @param write count bytes
249
* @param reuse existing pollfd structure
250
* @param timeout (microseconds)
251
* @return WAITRESS_RET_OK, WAITRESS_RET_TIMEOUT or WAITRESS_RET_ERR
253
static WaitressReturn_t WaitressPollWrite (int sockfd, const char *buf, size_t count,
254
struct pollfd *sockpoll, int timeout) {
257
sockpoll->events = POLLOUT;
258
pollres = WaitressPollLoop (sockpoll, 1, timeout);
260
return WAITRESS_RET_TIMEOUT;
261
} else if (pollres == -1) {
262
return WAITRESS_RET_ERR;
264
if (write (sockfd, buf, count) == -1) {
265
return WAITRESS_RET_ERR;
267
return WAITRESS_RET_OK;
270
/* read () wrapper with poll () timeout
272
* @param write to this buf, not NULL terminated
274
* @param reuse existing pollfd struct
275
* @param timeout (in microseconds)
276
* @param read () return value/written bytes
277
* @return WAITRESS_RET_OK, WAITRESS_RET_TIMEOUT, WAITRESS_RET_ERR
279
static WaitressReturn_t WaitressPollRead (int sockfd, char *buf, size_t count,
280
struct pollfd *sockpoll, int timeout, ssize_t *retSize) {
283
sockpoll->events = POLLIN;
284
pollres = WaitressPollLoop (sockpoll, 1, timeout);
286
return WAITRESS_RET_TIMEOUT;
287
} else if (pollres == -1) {
288
return WAITRESS_RET_ERR;
290
if ((*retSize = read (sockfd, buf, count)) == -1) {
291
return WAITRESS_RET_READ_ERR;
293
return WAITRESS_RET_OK;
296
/* FIXME: compiler macros are ugly... */
297
#define CLOSE_RET(ret) close (sockfd); return ret;
298
#define WRITE_RET(buf, count) \
299
if ((wRet = WaitressPollWrite (sockfd, buf, count, \
300
&sockpoll, waith->socktimeout)) != WAITRESS_RET_OK) { \
303
#define READ_RET(buf, count, size) \
304
if ((wRet = WaitressPollRead (sockfd, buf, count, \
305
&sockpoll, waith->socktimeout, size)) != WAITRESS_RET_OK) { \
309
/* Receive data from host and call *callback ()
310
* @param waitress handle
311
* @return WaitressReturn_t
313
WaitressReturn_t WaitressFetchCall (WaitressHandle_t *waith) {
314
struct addrinfo hints, *res;
316
char recvBuf[WAITRESS_RECV_BUFFER];
317
char writeBuf[2*1024];
318
ssize_t recvSize = 0;
319
WaitressReturn_t wRet = WAITRESS_RET_OK;
320
struct pollfd sockpoll;
322
/* header parser vars */
323
char *nextLine = NULL, *thisLine = NULL;
324
enum {HDRM_HEAD, HDRM_LINES, HDRM_FINISHED} hdrParseMode = HDRM_HEAD;
325
char statusCode[3], val[256];
326
unsigned int bufFilled = 0;
329
waith->contentLength = 0;
330
waith->contentReceived = 0;
331
memset (&hints, 0, sizeof hints);
333
hints.ai_family = AF_UNSPEC;
334
hints.ai_socktype = SOCK_STREAM;
337
if (WaitressProxyEnabled (waith)) {
338
if (getaddrinfo (waith->proxyHost, waith->proxyPort, &hints, &res) != 0) {
339
return WAITRESS_RET_GETADDR_ERR;
342
if (getaddrinfo (waith->host, waith->port, &hints, &res) != 0) {
343
return WAITRESS_RET_GETADDR_ERR;
347
if ((sockfd = socket (res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) {
349
return WAITRESS_RET_SOCK_ERR;
351
sockpoll.fd = sockfd;
353
/* we need shorter timeouts for connect() */
354
fcntl (sockfd, F_SETFL, O_NONBLOCK);
356
/* increase socket receive buffer */
357
const int sockopt = 256*1024;
358
setsockopt (sockfd, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof (sockopt));
360
/* non-blocking connect will return immediately */
361
connect (sockfd, res->ai_addr, res->ai_addrlen);
363
sockpoll.events = POLLOUT;
364
pollres = WaitressPollLoop (&sockpoll, 1, waith->socktimeout);
367
return WAITRESS_RET_TIMEOUT;
368
} else if (pollres == -1) {
369
return WAITRESS_RET_ERR;
371
/* check connect () return value */
372
socklen_t pollresSize = sizeof (pollres);
373
getsockopt (sockfd, SOL_SOCKET, SO_ERROR, &pollres, &pollresSize);
375
return WAITRESS_RET_CONNECT_REFUSED;
379
if (WaitressProxyEnabled (waith)) {
380
snprintf (writeBuf, sizeof (writeBuf),
381
"%s http://%s:%s%s HTTP/1.0\r\n",
382
(waith->method == WAITRESS_METHOD_GET ? "GET" : "POST"),
383
waith->host, waith->port, waith->path);
385
snprintf (writeBuf, sizeof (writeBuf),
386
"%s %s HTTP/1.0\r\n",
387
(waith->method == WAITRESS_METHOD_GET ? "GET" : "POST"),
390
WRITE_RET (writeBuf, strlen (writeBuf));
392
snprintf (writeBuf, sizeof (writeBuf),
393
"Host: %s\r\nUser-Agent: " PACKAGE "\r\n", waith->host);
394
WRITE_RET (writeBuf, strlen (writeBuf));
396
if (waith->method == WAITRESS_METHOD_POST && waith->postData != NULL) {
397
snprintf (writeBuf, sizeof (writeBuf), "Content-Length: %zu\r\n",
398
strlen (waith->postData));
399
WRITE_RET (writeBuf, strlen (writeBuf));
402
if (waith->extraHeaders != NULL) {
403
WRITE_RET (waith->extraHeaders, strlen (waith->extraHeaders));
406
WRITE_RET ("\r\n", 2);
408
if (waith->method == WAITRESS_METHOD_POST && waith->postData != NULL) {
409
WRITE_RET (waith->postData, strlen (waith->postData));
414
while (hdrParseMode != HDRM_FINISHED) {
415
READ_RET (recvBuf+bufFilled, sizeof (recvBuf)-1 - bufFilled, &recvSize);
417
/* connection closed too early */
418
CLOSE_RET (WAITRESS_RET_CONNECTION_CLOSED);
420
bufFilled += recvSize;
421
memset (recvBuf+bufFilled, 0, sizeof (recvBuf) - bufFilled);
425
while ((nextLine = strchr (thisLine, '\n')) != NULL &&
426
hdrParseMode != HDRM_FINISHED) {
427
/* make lines parseable by string routines */
429
if (*(nextLine-1) == '\r') {
430
*(nextLine-1) = '\0';
435
switch (hdrParseMode) {
438
if (sscanf (thisLine, "HTTP/1.%*1[0-9] %3[0-9] ",
440
if (memcmp (statusCode, "200", 3) == 0 ||
441
memcmp (statusCode, "206", 3) == 0) {
442
/* everything's fine... */
443
} else if (memcmp (statusCode, "403", 3) == 0) {
444
CLOSE_RET (WAITRESS_RET_FORBIDDEN);
445
} else if (memcmp (statusCode, "404", 3) == 0) {
446
CLOSE_RET (WAITRESS_RET_NOTFOUND);
448
CLOSE_RET (WAITRESS_RET_STATUS_UNKNOWN);
450
hdrParseMode = HDRM_LINES;
454
/* Everything else, except status code */
456
/* empty line => content starts here */
457
if (*thisLine == '\0') {
458
hdrParseMode = HDRM_FINISHED;
460
memset (val, 0, sizeof (val));
461
if (sscanf (thisLine, "Content-Length: %255c", val) == 1) {
462
waith->contentLength = atol (val);
471
} /* end while strchr */
472
memmove (recvBuf, thisLine, thisLine-recvBuf);
473
bufFilled -= (thisLine-recvBuf);
474
} /* end while hdrParseMode */
476
/* push remaining bytes */
478
waith->contentReceived += bufFilled;
479
if (waith->callback (thisLine, bufFilled, waith->data) ==
480
WAITRESS_CB_RET_ERR) {
481
CLOSE_RET (WAITRESS_RET_CB_ABORT);
485
/* receive content */
487
READ_RET (recvBuf, sizeof (recvBuf), &recvSize);
489
waith->contentReceived += recvSize;
490
if (waith->callback (recvBuf, recvSize, waith->data) ==
491
WAITRESS_CB_RET_ERR) {
492
wRet = WAITRESS_RET_CB_ABORT;
496
} while (recvSize > 0);
500
if (wRet == WAITRESS_RET_OK && waith->contentReceived < waith->contentLength) {
501
return WAITRESS_RET_PARTIAL_FILE;
510
const char *WaitressErrorToStr (WaitressReturn_t wRet) {
512
case WAITRESS_RET_OK:
513
return "Everything's fine :)";
516
case WAITRESS_RET_ERR:
520
case WAITRESS_RET_STATUS_UNKNOWN:
521
return "Unknown HTTP status code.";
524
case WAITRESS_RET_NOTFOUND:
525
return "File not found.";
528
case WAITRESS_RET_FORBIDDEN:
532
case WAITRESS_RET_CONNECT_REFUSED:
533
return "Connection refused.";
536
case WAITRESS_RET_SOCK_ERR:
537
return "Socket error.";
540
case WAITRESS_RET_GETADDR_ERR:
541
return "getaddr failed.";
544
case WAITRESS_RET_CB_ABORT:
545
return "Callback aborted request.";
548
case WAITRESS_RET_HDR_OVERFLOW:
549
return "HTTP header overflow.";
552
case WAITRESS_RET_PARTIAL_FILE:
553
return "Partial file.";
556
case WAITRESS_RET_TIMEOUT:
560
case WAITRESS_RET_READ_ERR:
561
return "Read error.";
564
case WAITRESS_RET_CONNECTION_CLOSED:
565
return "Connection closed by remote host.";
569
return "No error message available.";