Initial commit of newLISP.
[newlisp.git] / nl-web.c
blobb1c2a6f85a79a072e376d2b3b2725856105c6bb9
1 /* nl-web.c --- HTTP network protocol routines for newLISP
3 Copyright (C) 2008 Lutz Mueller
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 3 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, see <http://www.gnu.org/licenses/>.
20 #include "newlisp.h"
21 #include <errno.h>
22 #include "protos.h"
24 #ifdef WIN_32
25 #include <winsock2.h>
26 #else
27 #include <sys/types.h>
28 #include <sys/time.h>
29 #include <sys/socket.h>
30 #include <sys/ioctl.h>
31 #include <sys/wait.h>
32 #include <netdb.h>
33 #include <netinet/in.h>
34 #include <arpa/inet.h>
35 #endif
37 #define BUFFSIZE 10240
39 #ifndef WIN_32
40 #define SOCKET_ERROR -1
41 #else
42 #define fgets win32_fgets
43 #define fgetc win32_fgetc
44 #define close closesocket
45 #endif
47 #define ERROR_BAD_URL "ERR: bad formed URL"
50 #define MAX_PROTOCOL 8
51 #define NO_FLAGS_SET 0
53 /* with MinGW gcc 3.4.5 not needed
54 #ifdef WIN_32
55 struct timezone {
56 int tz_minuteswest;
57 int tz_dsttime;
60 int gettimeofday( struct timeval *tp, struct timezone *tzp );
61 #endif
64 int parseUrl(char *url, char * protocol, char * host, int * port, char * path, size_t maxlen);
65 void parsePath(char * url, char * path, size_t maxlen);
66 size_t parseValue(char * str);
67 void trimTrailing(char * ptr);
68 CELL * base64(CELL * params, int type);
70 size_t Curl_base64_encode(const char *inp, size_t insize, char **outptr);
71 size_t Curl_base64_decode(const char *src, char *dest);
74 jmp_buf socketTimeoutJump;
75 long socketTimeout;
76 struct timeval socketStart;
78 /* socket send and receive routines with timeout */
79 int recvc_tm(int sock)
81 struct timeval tm;
82 char chr;
83 ssize_t bytes;
85 while(wait_ready(sock, 1000, 0) <= 0)
87 if(socketTimeout)
89 gettimeofday(&tm, NULL);
90 if(timediff(tm, socketStart) > socketTimeout)
91 longjmp(socketTimeoutJump, 1);
95 bytes = recv(sock, &chr, 1, NO_FLAGS_SET);
96 if(bytes <= 0) return(-1);
98 return(chr);
102 char * recvs_tm(char * buffer, size_t size, int sock)
104 ssize_t bytesReceived = 0;
105 int chr;
107 while(bytesReceived < size)
109 if((chr = recvc_tm(sock)) < 0)
111 if(bytesReceived == 0)
112 return(NULL);
113 else break;
116 *(buffer + bytesReceived++) = chr;
117 if(chr == '\n') break;
120 *(buffer + bytesReceived) = 0;
121 return(buffer);
124 size_t recvsize_tm(char * buffer, size_t size, int sock)
126 ssize_t sizeRead = 0;
127 size_t resultSize = 0;
128 struct timeval tm;
130 wait_ready(sock, 1000, 0);
131 memset(buffer, 0, size);
132 while( (sizeRead = recv(sock, buffer + resultSize, size, NO_FLAGS_SET)) < size)
134 if(socketTimeout)
136 gettimeofday(&tm, NULL);
137 if(timediff(tm, socketStart) > socketTimeout)
138 longjmp(socketTimeoutJump, 1);
141 if(size == 0) break;
142 if(sizeRead <= 0)
144 sizeRead = 0;
145 break;
147 resultSize += sizeRead;
148 size -= sizeRead;
149 wait_ready(sock, 1000, 0);
152 return(resultSize + sizeRead);
155 ssize_t sendf(int sock, int debug, char * format, ...)
157 char * buffer;
158 va_list argptr;
159 int result;
161 va_start(argptr,format);
162 /* new in 7201 , defined in nl-filesys.c if not in libc */
163 vasprintf(&buffer, format, argptr);
165 result = send(sock, buffer, strlen(buffer), NO_FLAGS_SET);
166 if(debug) varPrintf(OUT_CONSOLE, buffer);
168 freeMemory(buffer);
169 va_end(argptr);
171 return(result);
176 CELL * p_getUrl(CELL * params)
178 return(getPutPostDeleteUrl(NULL, params, HTTP_GET_URL, 0));
182 CELL * p_putUrl(CELL * params)
184 return(getPutPostDeleteUrl(NULL, params, HTTP_PUT_URL, 0));
188 CELL * p_postUrl(CELL * params)
190 return(getPutPostDeleteUrl(NULL, params, HTTP_POST_URL, 0));
193 CELL * p_deleteUrl(CELL * params)
195 return(getPutPostDeleteUrl(NULL, params, HTTP_DELETE_URL, 0));
198 #define BASE64_ENC 0
199 #define BASE64_DEC 1
201 CELL * p_base64Enc(CELL * params) { return(base64(params, BASE64_ENC)); }
202 CELL * p_base64Dec(CELL * params) { return(base64(params, BASE64_DEC)); }
204 CELL * base64(CELL * params, int type)
206 char * inPtr;
207 char * outPtr;
208 size_t sizein, sizeout;
209 CELL * strCell;
211 getStringSize(params, &inPtr, &sizein, TRUE);
214 if(type == BASE64_ENC)
216 if(sizein == 0)
217 return(stuffString("===="));
218 if((sizeout = Curl_base64_encode(inPtr, sizein, &outPtr)) == 0)
219 return(stuffString(""));
221 else /* BASE64_DEC */
223 outPtr = allocMemory((sizein * 3) / 4 + 9);
224 sizeout = Curl_base64_decode(inPtr, outPtr);
225 *(outPtr + sizeout) = 0;
228 strCell = getCell(CELL_STRING);
229 strCell->contents = (UINT)outPtr;
230 strCell->aux = sizeout + 1;
232 return(strCell);
236 CELL * getPutPostDeleteUrl(char * url, CELL * params, int type, int timeout)
238 char * proxyUrl, * putPostStr = NULL, *contentType;
239 char * protocol;
240 char * host;
241 char * pHost;
242 char * path;
243 char * customHeader = NULL;
244 size_t maxlen;
245 int port, pPort, sock = 0;
246 char * option, * method = NULL;
247 char buff[BUFFSIZE];
248 char errorTxt[128];
249 char * buffPtr;
250 char * resultPtr = NULL;
251 int haveContentLength = FALSE, headRequest = FALSE, listFlag = FALSE, debugFlag = FALSE;
252 int chunked = FALSE;
253 ssize_t sizeRead = 0;
254 size_t resultSize = 0, fSize = 0, size = 0;
255 CELL * result, * cell;
256 CELL * headerCell = NULL;
257 int ch;
258 int responseLoop;
259 int statusCode;
261 /* get parameters */
263 if(url == NULL)
264 params = getString(params, &url);
266 if(type == HTTP_PUT_URL || type == HTTP_PUT_APPEND_URL || type == HTTP_POST_URL)
267 params = getStringSize(params, &putPostStr, &size, TRUE);
269 if(type == HTTP_POST_URL)
271 if(params->type != CELL_NIL)
272 params = getString(params, &contentType);
273 else
274 contentType = "application/x-www-form-urlencoded";
277 result = evaluateExpression(params);
278 params = params->next;
279 socketTimeout = timeout;
280 if(isNumber(result->type))
281 getIntegerExt(result, (UINT*)&socketTimeout, FALSE);
282 else if(isString(result->type))
284 option = (char *)result->contents;
285 if(my_strnicmp(option, "header", 6) == 0)
286 headRequest = TRUE;
287 if(my_strnicmp(option, "list", 5) == 0)
288 listFlag = TRUE;
289 /* "debug" or "header debug" or "list-debug" options
290 print all outgoing informatiopn on the console */
291 if(my_strnicmp(option, "debug", 5) == 0)
292 debugFlag = TRUE;
293 if(my_strnicmp(option + 7, "debug", 5) == 0)
294 debugFlag = TRUE;
295 if(my_strnicmp(option + 6, "debug", 5) == 0)
296 debugFlag = TRUE;
297 if(params != nilCell)
298 params = getInteger(params, (UINT*)&socketTimeout);
300 else if(result != nilCell)
301 return(errorProcExt(ERR_NUMBER_OR_STRING_EXPECTED, result));
303 /* if timeout is specified, custom-header can be specified too */
304 if(socketTimeout && params != nilCell)
305 getString(params, &customHeader);
307 maxlen = strlen(url);
308 if(maxlen < MAX_URL_LEN) maxlen = MAX_URL_LEN;
310 protocol = alloca(8);
311 host = alloca(maxlen + 1);
312 pHost = alloca(maxlen + 1);
313 path = alloca(maxlen + 1);
315 /* parse URL for parameters */
316 if(parseUrl(url, protocol, host, &port, path, maxlen) == FALSE)
317 return(stuffString(ERROR_BAD_URL));
319 proxyUrl = getenv("HTTP_PROXY");
320 if(proxyUrl == NULL)
322 strncpy(pHost, host, maxlen);
323 pPort = port;
325 else
327 if(parseUrl(proxyUrl, protocol, pHost, &pPort, NULL, maxlen) == FALSE)
328 return(stuffString(ERROR_BAD_URL));
331 /* start timer */
332 gettimeofday(&socketStart, NULL);
333 /* connect to host */
334 CONNECT_TO_HOST:
335 if(sock)
336 close(sock);
338 if((sock = netConnect(pHost, pPort, SOCK_STREAM, NULL, 3)) == SOCKET_ERROR)
339 return(stuffString("ERR: could not connect"));
341 if(type == HTTP_GET_URL)
342 method = (headRequest == TRUE) ? "HEAD" : "GET";
343 else if(type == HTTP_PUT_URL || type == HTTP_PUT_APPEND_URL)
344 method = "PUT";
345 else if(type == HTTP_POST_URL)
346 method = "POST";
347 else if(type == HTTP_DELETE_URL)
348 method = "DELETE";
350 /* send header */
351 if(proxyUrl != NULL)
352 sendf(sock, debugFlag, "%s %s://%s:%d/%s HTTP/1.1\r\n", method, protocol, host, port, path);
353 else
354 sendf(sock, debugFlag, "%s /%s HTTP/1.1\r\n", method, path);
356 /* obligatory host spec */
357 sendf(sock, debugFlag, "Host: %s\r\n", host);
359 /* send optional custom header entries */
360 if (customHeader != NULL)
361 sendf(sock, debugFlag, customHeader);
362 else
364 sendf(sock, debugFlag, "User-Agent: newLISP v%d\r\n", version);
367 sendf(sock, debugFlag, "Connection: close\r\n");
369 /* expanded header for PUT, POST and body */
370 if(type == HTTP_GET_URL || type == HTTP_DELETE_URL)
372 sendf(sock, debugFlag, "\r\n");
374 else if(type == HTTP_PUT_URL || type == HTTP_PUT_APPEND_URL)
376 if(type == HTTP_PUT_APPEND_URL) sendf(sock, debugFlag, "Pragma: append\r\n");
377 if(customHeader == NULL)
378 sendf(sock, debugFlag, "Content-type: text/html\r\nContent-length: %d\r\n\r\n", size);
379 else
380 sendf(sock, debugFlag, "Content-length: %d\r\n\r\n", size);
382 send(sock, putPostStr, size, NO_FLAGS_SET);
383 if(debugFlag) varPrintf(OUT_CONSOLE, putPostStr);
385 else if(type == HTTP_POST_URL)
387 sendf(sock, debugFlag, "Content-type: %s\r\nContent-length: %d\r\n\r\n", contentType, size);
388 send(sock, putPostStr, size, NO_FLAGS_SET);
389 if(debugFlag) varPrintf(OUT_CONSOLE, putPostStr);
392 if(setjmp(socketTimeoutJump) != 0)
394 if(sock) close(sock);
395 if(resultPtr != NULL) free(resultPtr);
396 return(stuffString("ERR: timeout"));
399 /* Retrieve HTTP response and check for status code. */
400 responseLoop = 0;
401 READ_RESPONSE:
402 if(++responseLoop == 4)
403 return(stuffString("ERR: invalid response from server"));
405 if (recvs_tm(buff, BUFFSIZE, sock) == NULL)
406 return(stuffString("ERR: no response from server"));
408 /* go past first token */
409 for (buffPtr = buff; *buffPtr != '\0' && !isspace((int)*buffPtr); ++buffPtr) {;}
411 /* trim leading spaces */
412 while(isspace((int)*buffPtr)) ++buffPtr;
414 /* get status code */
415 statusCode = atoi(buffPtr);
416 /* printf("statusCode:%d\n", statusCode); */
417 switch (statusCode)
419 case 0:
420 case 100:
421 /* drain and continue */
422 while (ch = recvc_tm(sock), ch != EOF && ch != '\n') {;}
423 goto READ_RESPONSE;
424 case 200:
425 case 201:
426 case 202:
427 case 203:
428 case 204:
429 case 205:
430 case 206:
431 case 300:
432 case 301:
433 case 302:
434 case 303:
435 case 307:
436 break;
437 default:
438 if(strlen(buff) > 127) buff[127] = 0;
439 snprintf(errorTxt, 127, "ERR: server code %d: %s", atoi(buffPtr), buff);
440 break;
443 /* Retrieve HTTP headers. */
444 memset(buff, 0, BUFFSIZE);
445 if(listFlag || headRequest)
446 headerCell = stuffString("");
448 /* printf("retrieving headers\n"); */
450 /* Retrieve header */
451 while(strcmp(buff, "\r\n") != 0 && strcmp(buff, "\n") != 0)
453 if(recvs_tm(buff, BUFFSIZE, sock) == NULL)
454 return(stuffString("ERR: problem in header"));
456 /* printf("==>%s<==\n", buff); */
458 if(listFlag || headRequest) appendCellString(headerCell, buff, strlen(buff));
460 if(my_strnicmp(buff, "content-length:", 15) == 0)
462 fSize = parseValue(buff + 15);
463 haveContentLength = TRUE;
465 if(my_strnicmp(buff, "location:", 9) == 0 && (statusCode == 301 || statusCode == 303))
467 buffPtr = buff + 9;
468 while(isspace((int)*buffPtr)) ++buffPtr;
469 if(*buffPtr == '/')
470 strncpy(path, buffPtr + 1, maxlen);
471 else /* its a url or path */
473 if(parseUrl(buffPtr, protocol, host, &port, path, maxlen) == FALSE)
474 /* path only */
475 parsePath(buffPtr, path, buffPtr - buff);
477 if(proxyUrl == NULL)
479 strncpy(pHost, host, maxlen);
480 pPort = port;
484 if(headerCell) deleteList(headerCell);
485 goto CONNECT_TO_HOST;
488 if(my_strnicmp(buff, "Transfer-Encoding:", 18) == 0
489 && searchBuffer(buff, strlen(buff), "chunked", 7, 0) != 0xFFFFFFFF)
490 chunked = TRUE;
493 if(headRequest)
494 return(headerCell);
496 if((haveContentLength == TRUE && fSize == 0) || statusCode == 204)
498 resultPtr = NULL;
500 /* Retrieve HTTP body. */
501 else if(chunked == TRUE)
503 resultPtr = NULL;
504 if(recvs_tm(buff, BUFFSIZE, sock) == NULL)
505 return(stuffString("ERR: document empty"));
506 while((size = strtoul(buff, NULL, 16)) > 0)
508 if(resultSize == 0)
509 resultPtr = allocMemory(size + 1);
510 else
511 resultPtr = reallocMemory(resultPtr, resultSize + size + 1);
513 if(recvsize_tm(resultPtr + resultSize, size, sock) != size)
515 free(resultPtr);
516 return(stuffString("ERR: problem in chunked format"));
518 resultSize += size;
519 recvs_tm(buff, BUFFSIZE, sock); /* empty line */
520 recvs_tm(buff, BUFFSIZE, sock); /* chunck size */
524 else if(haveContentLength == TRUE)
526 resultPtr = allocMemory(fSize + 1);
527 if((resultSize = recvsize_tm(resultPtr, fSize, sock)) == 0)
529 free(resultPtr);
530 return(stuffString("ERR: document empty"));
534 else /* no content length given, relies on host closing the connection */
536 resultPtr = allocMemory(BUFFSIZE + 1);
537 resultSize = 0;
538 size = BUFFSIZE;
539 memset(buff, 0, BUFFSIZE);
540 while ((sizeRead = recvsize_tm(buff, BUFFSIZE - 1, sock)) > 0)
542 if((resultSize + sizeRead) > size)
544 size = resultSize + BUFFSIZE;
545 resultPtr = reallocMemory(resultPtr, size + 1);
547 memcpy(resultPtr + resultSize, buff, sizeRead);
548 resultSize += sizeRead;
549 memset(buff, 0, BUFFSIZE);
554 if(resultPtr == NULL)
556 if(statusCode < 400)
557 result = stuffString("");
558 else
559 result = stuffString(errorTxt);
561 else
563 *(resultPtr + resultSize) = 0;
564 result = getCell(CELL_STRING);
565 if(statusCode >= 400)
567 maxlen = strlen(errorTxt);
568 buffPtr = allocMemory(maxlen + resultSize + 1);
569 memcpy(buffPtr, errorTxt, maxlen);
570 memcpy(buffPtr + maxlen, resultPtr, resultSize);
571 free(resultPtr);
572 resultPtr = buffPtr;
573 resultSize += maxlen;
575 result->contents = (UINT)resultPtr;
576 result->aux = resultSize + 1;
579 close(sock);
581 if(listFlag)
583 cell = getCell(CELL_EXPRESSION);
584 cell->contents = (UINT)headerCell;
585 headerCell->next = result;
586 return(cell);
589 return(result);
593 int parseUrl(char * url, char * protocol, char * host, int * port, char * path, size_t maxlen)
595 char * colonPtr;
596 char * slashPtr;
597 int len;
599 /* trim trailing whitespace like '/r/n' from url */
600 len = strlen(url);
601 while(*(url + len) <= ' ' && len > 0)
603 *(url + len) = 0;
604 len--;
607 *port = 80;
609 if(my_strnicmp(url, "http://", 7) == 0)
611 strncpy(protocol,"http", MAX_PROTOCOL );
612 strncpy(host, url+7, maxlen);
614 else if( my_strnicmp(url, "https://", 8) == 0)
616 strncpy(protocol, "https", MAX_PROTOCOL);
617 strncpy(host, url+8, maxlen);
619 else
620 return(FALSE);
622 colonPtr = strchr(host, ':');
623 slashPtr = strchr(host, '/');
625 if (colonPtr != NULL && (slashPtr == NULL || colonPtr < slashPtr))
627 *colonPtr++ = '\0';
628 *port = atoi(colonPtr);
631 if(path == NULL) return(TRUE);
633 if (slashPtr != NULL)
635 *slashPtr++ = '\0';
636 strncpy(path, slashPtr, maxlen);
638 else
639 strncpy(path, "", maxlen);
642 return(TRUE);
645 void parsePath(char * url, char * path, size_t maxlen)
647 int len;
649 /* trim trailing whitespace like '/r/n' from url */
650 len = strlen(url);
651 while(*(url + len) <= ' ' && len > 0)
653 *(url + len) = 0;
654 len--;
657 /* trim leading whitespace */
658 while(*url <= ' ') url++;
659 strncpy(path, url, maxlen);
662 size_t parseValue(char * str)
664 char * number;
665 while(!isDigit((unsigned char)*str) && *str != 0) ++str;
666 number = str;
667 while(isDigit((unsigned char)*str)) ++str;
668 return atol(number);
672 /***************************************************************************
673 * _ _ ____ _
674 * Project ___| | | | _ \| |
675 * / __| | | | |_) | |
676 * | (__| |_| | _ <| |___
677 * \___|\___/|_| \_\_____|
679 * Copyright (C) 1998 - 2004, Daniel Stenberg, <daniel@haxx.se>, et al.
681 * This software is licensed as described in the file COPYING, which
682 * you should have received as part of this distribution. The terms
683 * are also available at http://curl.haxx.se/docs/copyright.html.
685 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
686 * copies of the Software, and permit persons to whom the Software is
687 * furnished to do so, under the terms of the COPYING file.
689 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
690 * KIND, either express or implied.
692 * $Id: base64.c,v 1.32 2004/12/15 01:38:25 danf Exp $
693 ***************************************************************************/
695 /* Base64 encoding/decoding
697 this file from the cURL project is included in nl-web.c for the
698 newLISP functions 'base64-enc' and 'base64-dec'
700 all #include statements have and the test harness rootines have
701 been stripped. 2005-1-6 Lutz Mueller
705 static void decodeQuantum(unsigned char *dest, const char *src)
707 unsigned int x = 0;
708 int i;
709 for(i = 0; i < 4; i++) {
710 if(src[i] >= 'A' && src[i] <= 'Z')
711 x = (x << 6) + (unsigned int)(src[i] - 'A' + 0);
712 else if(src[i] >= 'a' && src[i] <= 'z')
713 x = (x << 6) + (unsigned int)(src[i] - 'a' + 26);
714 else if(src[i] >= '0' && src[i] <= '9')
715 x = (x << 6) + (unsigned int)(src[i] - '0' + 52);
716 else if(src[i] == '+')
717 x = (x << 6) + 62;
718 else if(src[i] == '/')
719 x = (x << 6) + 63;
720 else if(src[i] == '=')
721 x = (x << 6);
724 dest[2] = (unsigned char)(x & 255);
725 x >>= 8;
726 dest[1] = (unsigned char)(x & 255);
727 x >>= 8;
728 dest[0] = (unsigned char)(x & 255);
732 * Curl_base64_decode()
734 * Given a base64 string at src, decode it into the memory pointed to by
735 * dest. Returns the length of the decoded data.
737 size_t Curl_base64_decode(const char *src, char *dest)
739 int length = 0;
740 int equalsTerm = 0;
741 int i;
742 int numQuantums;
743 unsigned char lastQuantum[3];
744 size_t rawlen=0;
746 while((src[length] != '=') && src[length])
747 length++;
748 while(src[length+equalsTerm] == '=')
749 equalsTerm++;
751 if(equalsTerm > 3) equalsTerm = 3; /* LM added 2006-09-08 */
753 numQuantums = (length + equalsTerm) / 4;
755 if(numQuantums == 0) return(0);
757 rawlen = (numQuantums * 3) - equalsTerm;
759 for(i = 0; i < numQuantums - 1; i++) {
760 decodeQuantum((unsigned char *)dest, src);
761 dest += 3; src += 4;
764 decodeQuantum(lastQuantum, src);
765 for(i = 0; i < 3 - equalsTerm; i++)
766 dest[i] = lastQuantum[i];
768 return rawlen;
771 /* ---- Base64 Encoding --- */
772 static const char table64[]=
773 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
776 * Curl_base64_encode()
778 * Returns the length of the newly created base64 string. The third argument
779 * is a pointer to an allocated area holding the base64 data. If something
780 * went wrong, -1 is returned.
783 size_t Curl_base64_encode(const char *inp, size_t insize, char **outptr)
785 unsigned char ibuf[3];
786 unsigned char obuf[4];
787 int i;
788 int inputparts;
789 char *output;
790 char *base64data;
792 char *indata = (char *)inp;
794 *outptr = NULL; /* set to NULL in case of failure before we reach the end */
796 if(0 == insize)
797 insize = strlen(indata);
799 base64data = output = (char*)malloc(insize*4/3+4);
800 if(NULL == output)
801 return 0;
803 while(insize > 0) {
804 for (i = inputparts = 0; i < 3; i++) {
805 if(insize > 0) {
806 inputparts++;
807 ibuf[i] = *indata;
808 indata++;
809 insize--;
811 else
812 ibuf[i] = 0;
815 obuf [0] = (ibuf [0] & 0xFC) >> 2;
816 obuf [1] = ((ibuf [0] & 0x03) << 4) | ((ibuf [1] & 0xF0) >> 4);
817 obuf [2] = ((ibuf [1] & 0x0F) << 2) | ((ibuf [2] & 0xC0) >> 6);
818 obuf [3] = ibuf [2] & 0x3F;
820 switch(inputparts) {
821 case 1: /* only one byte read */
822 snprintf(output, 5, "%c%c==",
823 table64[obuf[0]],
824 table64[obuf[1]]);
825 break;
826 case 2: /* two bytes read */
827 snprintf(output, 5, "%c%c%c=",
828 table64[obuf[0]],
829 table64[obuf[1]],
830 table64[obuf[2]]);
831 break;
832 default:
833 snprintf(output, 5, "%c%c%c%c",
834 table64[obuf[0]],
835 table64[obuf[1]],
836 table64[obuf[2]],
837 table64[obuf[3]] );
838 break;
840 output += 4;
842 *output=0;
843 *outptr = base64data; /* make it return the actual data memory */
845 return strlen(base64data); /* return the length of the new data */
847 /* ---- End of Base64 Encoding ---- */
850 /* --------------------------- HTTP server mode -----------------------------
851 handles GET, POST, PUT and DELETE requests
852 handles queries in GET requests and sets environment variable QUERY_STRING
853 sets HTTP_HOST and HTTP_USER_AGENTT when present in client request header
854 no special encodings are supported
855 subset HTTP/1.0 compliant
858 int sendHTTPmessage(char * fmt, char * str);
859 void handleHTTPcgi(char * command, char * query);
860 size_t readHeader(char * buff, int * pragmaFlag);
861 ssize_t readPayLoad(ssize_t size, char * content, int outFile, char * request);
862 int endsWith(char * str, char * ext);
864 void sendHTTPpage(char * content, size_t size, char * media, int closeFlag)
866 varPrintf(OUT_CONSOLE, "HTTP/1.0 200 OK\r\nServer: newLISP v.%d (%s)\r\n", version, ostype);
868 if(media != NULL) /* else Content-type: is provided by CGI page */
869 varPrintf(OUT_CONSOLE, "Content-length: %d\r\nContent-type: %s\r\n\r\n", size, media);
870 #ifndef WIN_32
871 /* size = fwrite(content, 1, size, IOchannel); */ /* does not work with xinetd on OSX */
872 size = write(fileno(IOchannel), content, size);
873 fflush(IOchannel);
874 if(closeFlag)
876 fclose(IOchannel);
877 IOchannel = NULL;
879 #else
880 if(IOchannel != NULL && isSocketStream(IOchannel))
882 sendall(getSocket(IOchannel), content, size);
883 close(getSocket(IOchannel));
885 else
886 varPrintf(OUT_CONSOLE, content);
887 return;
888 #endif
891 #define ERR_411 "ERR:411 length required for: %s\r\n"
892 #define MAX_BUFF 1024
893 #define DEFAULT_PAGE_1 "index.html"
894 #define DEFAULT_PAGE_2 "index.cgi"
895 #define CGI_EXTENSION ".cgi"
896 #define MEDIA_TXT "text/html"
898 #define ERROR_404 "ERR:404 File not found: %s\r\n"
900 int executeHTTPrequest(char * request, int type)
902 char * sptr;
903 char * query;
904 int len;
905 char * content = NULL;
906 ssize_t transferred, size;
907 char buff[MAX_BUFF];
908 int outFile;
909 int pragmaFlag;
910 char * fileMode = "w";
911 char * mediaType;
912 char * command;
913 CELL * result = NULL;
915 chdir(startupDir);
916 query = sptr = request;
918 setenv("DOCUMENT_ROOT", startupDir, 1);
920 #ifdef DEBUGHTTP
921 printf("HTTP request:%s:%d:\n", request, type);
922 #endif
924 /* stuff after request */
925 while(*sptr > ' ') ++sptr;
926 *sptr = 0;
927 while(*query != 0 && *query != '?') ++query;
928 if(*query == '?')
930 *query = 0;
931 query++;
934 setenv("QUERY_STRING", query, 1);
936 /* if httpd-conf is defined call it with the request
937 the httpd-conf procedure returns a string transformation
938 of request, for security stuff, remappping etc. */
940 if(lookupSymbol("httpd-conf", mainContext) != NULL)
942 len = strlen(request) + strlen(query) + 32;
943 command = alloca(len);
944 snprintf(command, len - 1, "(httpd-conf \"%s\" \"%s\")", request, query);
945 result = sysEvalString(command, nilCell, mainContext);
946 if(result->type == CELL_STRING)
947 request = (char *)result->contents;
948 else if (isNil(result))
950 if(IOchannel != NULL)
952 deleteList(result);
953 fclose(IOchannel);
954 IOchannel = NULL;
956 return(TRUE);
960 /* change to base dir of request file */
961 sptr = request + strlen(request);
962 while(*sptr != '/' && sptr != request) --sptr;
963 if(*sptr == '/')
965 *sptr = 0;
966 sptr++;
967 chdir(request);
968 request = sptr;
971 if((len = strlen(request)) == 0)
973 if(isFile(DEFAULT_PAGE_2) == 0)
974 request = DEFAULT_PAGE_2;
975 else
976 request = DEFAULT_PAGE_1;
977 len = strlen(request);
980 size = readHeader(buff, &pragmaFlag);
981 switch(type)
983 case HTTP_GET_HEAD:
984 case HTTP_GET_URL:
985 if(endsWith(request, CGI_EXTENSION))
986 handleHTTPcgi(request, query);
987 else
989 if(endsWith(request, ".jpg")) mediaType = "image/jpeg";
990 else if(endsWith(request, ".png")) mediaType = "image/png";
991 else if(endsWith(request, ".gif")) mediaType = "image/gif";
992 else if(endsWith(request, ".pdf")) mediaType = "application/pdf";
993 else if(endsWith(request, ".mp3")) mediaType = "audio/mpeg";
994 else if(endsWith(request, ".mov")) mediaType = "video/quicktime";
995 else if(endsWith(request, ".mpg")) mediaType = "video/mpeg";
996 else mediaType = MEDIA_TXT;
998 if(type == HTTP_GET_HEAD)
1000 snprintf(buff, MAX_BUFF - 1,
1001 #ifndef WIN_32
1002 "Content-length: %lld\r\nContent-type: %s\r\n\r\n",
1003 (long long int)fileSize(request),
1004 #else
1005 "Content-length: %ld\r\nContent-type: %s\r\n\r\n",
1006 (long)fileSize(request),
1007 #endif
1008 mediaType);
1009 sendHTTPpage(buff, strlen(buff), NULL, TRUE);
1011 else
1013 if((size = readFile(request, &content)) == -1)
1014 sendHTTPmessage(ERROR_404, request);
1015 else
1016 sendHTTPpage(content, size, mediaType, TRUE);
1018 if(content) free(content);
1021 break;
1023 case HTTP_DELETE_URL:
1024 if(unlink(request) != 0)
1025 sendHTTPmessage("ERR:500 Could not delete: %s\r\n", request);
1026 else
1027 sendHTTPmessage("File deleted: %s\r\n", request);
1028 break;
1030 case HTTP_POST_URL:
1031 if(!size)
1033 sendHTTPmessage(ERR_411, request);
1034 break;
1037 query = callocMemory(size + 1);
1039 if(readPayLoad(size, query, 0, request) == -1)
1041 free(query);
1042 sendHTTPmessage("ERR:500 cannot read header: %s\r\n", request);
1043 break;
1046 handleHTTPcgi(request, query);
1047 free(query);
1048 break;
1050 case HTTP_PUT_URL:
1051 if(pragmaFlag) fileMode = "a";
1053 if(!size)
1055 sendHTTPmessage(ERR_411, request);
1056 break;
1059 if( (outFile = openFile(request, fileMode, NULL)) == (int)-1)
1061 sendHTTPmessage("ERR:500 cannot create file: %s\r\n", request);
1062 break;
1065 transferred = readPayLoad(size, buff, outFile, request);
1066 close(outFile);
1068 if(transferred != -1)
1070 snprintf(buff, 255, "%d bytes transferred for %s\r\n", (int)transferred, request);
1071 sendHTTPpage(buff, strlen(buff), MEDIA_TXT, TRUE);
1073 break;
1075 default:
1076 break;
1079 chdir(startupDir);
1080 if(result != NULL) deleteList(result);
1081 return(TRUE);
1085 int sendHTTPmessage(char * fmt, char * str)
1087 char msg[256];
1089 snprintf(msg, 255, fmt, str);
1090 sendHTTPpage(msg, strlen(msg), MEDIA_TXT, TRUE);
1091 return(0);
1095 /* remove leading white space */
1096 char * trim(char * buff)
1098 char * ptr = buff;
1099 while(*ptr <= ' ') ptr++;
1100 return(ptr);
1103 /* retrieve rest of header */
1104 size_t readHeader(char * buff, int * pragmaFlag)
1106 size_t size = 0;
1107 int offset;
1109 *pragmaFlag = 0;
1111 memset(buff, 0, MAX_LINE);
1113 setenv("HTTP_HOST", "", 1);
1114 setenv("HTTP_USER_AGENT", "", 1);
1115 setenv("HTTP_COOKIE", "", 1);
1117 while(fgets(buff, MAX_LINE - 1, IOchannel) != NULL)
1119 if(strcmp(buff, "\r\n") == 0 || strcmp(buff, "\n") == 0) break;
1121 /* trim trailing white space */
1122 offset = strlen(buff) - 1;
1123 while(offset > 0 && *(buff + offset) <= ' ')
1124 *(buff + offset--) = 0;
1126 if(my_strnicmp(buff, "content-length:", 15) == 0)
1127 size = parseValue(buff + 15);
1128 if(my_strnicmp(buff, "pragma: append", 14) == 0)
1129 *pragmaFlag = TRUE;
1131 /* trim leading white space */
1132 if(my_strnicmp(buff, "Host:", 5) == 0)
1133 setenv("HTTP_HOST", trim(buff + 5), 1);
1134 if(my_strnicmp(buff, "User-Agent:", 11) == 0)
1135 setenv("HTTP_USER_AGENT", trim(buff + 11), 1);
1136 if(my_strnicmp(buff, "Cookie:", 7) == 0)
1137 setenv("HTTP_COOKIE", trim(buff + 7), 1);
1141 return(size);
1145 ssize_t readPayLoad(ssize_t size, char * buff, int outFile, char * request)
1147 ssize_t bytes;
1148 size_t offset = 0, transferred = 0;
1150 #ifdef DEBUGHTTP
1151 printf("payload size:%ld:\n", size);
1152 #endif
1154 while(size > 0)
1156 #ifndef WIN_32
1157 bytes = read(fileno(IOchannel), buff + offset, MAX_BUFF);
1158 #else
1159 if(IOchannel != NULL && isSocketStream(IOchannel))
1160 bytes = recv(getSocket(IOchannel), buff + offset, MAX_BUFF, NO_FLAGS_SET);
1161 else
1162 bytes = read(fileno(IOchannel), buff + offset, MAX_BUFF);
1163 #endif
1165 #ifdef DEBUGHTTP
1166 printf("payload bytes:%ld:%s:\n", bytes, buff + offset);
1167 #endif
1169 if(bytes <= 0)
1171 sendHTTPmessage("ERR:500 error reading data: %s\r\n", request);
1172 return(-1);
1175 if(outFile)
1177 if(write(outFile, buff + offset, bytes) != bytes)
1179 sendHTTPmessage("ERR:500 cannot create file: %s\r\n", request);
1180 return(-1);
1183 else
1184 offset += bytes;
1186 transferred += bytes;
1187 size -= bytes;
1189 #ifndef WIN_32
1190 fflush(NULL);
1191 #endif
1192 return(transferred);
1197 void handleHTTPcgi(char * request, char * query)
1199 FILE * handle;
1200 char * command;
1201 char * content = NULL;
1202 size_t size;
1203 char tempfile[32];
1204 #ifdef WIN_32_BEFORE_SETTING_BINARYMODE
1205 char * ptr;
1206 char * pos;
1207 int bytes = 0;
1208 #endif
1210 srandom(milliSecTime());
1212 #ifdef DEBUGHTTP
1213 printf("CGI request:%s:%s:\n", request, query);
1214 #endif
1216 if(isFile(request) != 0)
1218 sendHTTPmessage(ERROR_404, request);
1219 return;
1222 if(isFile("/tmp") != 0)
1224 sendHTTPmessage("ERR:500 need /tmp directory configured: %s\n", request);
1225 return;
1228 size = strlen(request) + 64;
1229 command = alloca(size);
1230 snprintf(tempfile, 30, "/tmp/nl%02x%08x%08x", (unsigned int)size, (unsigned int)random(), (unsigned int)random());
1232 #if defined (WIN_32) || (OS2)
1233 snprintf(command, size - 1, "newlisp %s > %s", request, tempfile);
1234 #else
1235 snprintf(command, size - 1, "./%s > %s", request, tempfile);
1236 #endif
1238 if((handle = popen(command, "w")) == NULL)
1240 sendHTTPmessage("ERR:500 failed creating pipe: %s\n", request);
1241 return;
1244 fwrite(query, 1, strlen(query), handle);
1245 fflush(handle);
1246 pclose(handle);
1248 size = readFile(tempfile, &content);
1249 if(size == -1)
1250 sendHTTPmessage("ERR:500 cannot read output of: %s", request);
1251 else
1253 #ifdef WIN_32_BEFORE_SETTING_BINARYMODE
1254 /* replace all \r\r\n with \r\n (artefact of Win32 out-piping) */
1255 ptr = content;
1256 bytes = 0;
1257 while((pos = strstr(ptr, "\r\r\n")))
1259 memcpy(content + bytes, ptr, pos - ptr);
1260 bytes += pos - ptr;
1261 memcpy(content + bytes, "\r\n", 2);
1262 bytes += 2;
1263 ptr = pos + 3 ;
1264 --size;
1266 memcpy(content + bytes, ptr, size - bytes);
1267 *(content + size) = 0;
1268 #endif
1269 sendHTTPpage(content, size, NULL, TRUE);
1272 unlink(tempfile);
1273 if(content) free(content);
1277 int endsWith(char * str, char * ext)
1279 size_t size, len;
1281 size = strlen(str);
1282 len = strlen(ext);
1284 return(strncmp(str + size - len, ext, len) == 0);
1287 /* eof */