/* nl-web.c --- HTTP network protocol routines for newLISPD Copyright (C) 2016 Lutz Mueller This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "newlisp.h" #include #include "protos.h" #ifdef WINDOWS #include #else #include #include #include #include #include #include #include #include #endif #define BUFFSIZE 10240 #ifndef WINDOWS #define SOCKET_ERROR -1 #else #define fgets win_fgets #define close closesocket /* for file operations on Windows use _close */ #endif /* from nl-sock.c */ extern UINT netErrorIdx; extern char * netErrorMsg[]; #define OK_FILE_DELETED "file deleted" #define MAX_PROTOCOL 8 #define NO_FLAGS_SET 0 char * requestMethod[] = {"GET", "HEAD", "PUT", "PUT", "POST", "DELETE"}; /* with MinGW gcc 3.4.5 not needed #ifdef WINDOWS struct timezone { int tz_minuteswest; int tz_dsttime; }; int gettimeofday( struct timeval *tp, struct timezone *tzp ); #endif */ #ifdef WINDOWS extern int IOchannelIsSocketStream; #endif extern SYMBOL * transferEvent; ssize_t readFile(char * fileName, char * * buffer); int writeFile(char * fileName, char * buffer, size_t size, char * type); int parseUrl(char *url, char * protocol, char * host, int * port, char * path, size_t bufflen); void parsePath(char * url, char * path, size_t bufflen); size_t parseValue(char * str); void trimTrailing(char * ptr); CELL * webError(int no); CELL * base64(CELL * params, int type); #ifndef EMSCRIPTEN jmp_buf socketTimeoutJump; INT socketTimeout = 0; struct timeval socketStart; /* socket send and receive routines with timeout */ int recvc_tm(int sock) { struct timeval tm; unsigned char chr; ssize_t bytes; while(wait_ready(sock, 1000, 0) <= 0) { if(socketTimeout) { gettimeofday(&tm, NULL); if(timediff_ms(tm, socketStart) > socketTimeout) longjmp(socketTimeoutJump, 1); } } bytes = recv(sock, (void *)&chr, 1, NO_FLAGS_SET); if(bytes <= 0) return(-1); return(chr); } char * recvs_tm(char * buffer, size_t size, int sock) { ssize_t bytesReceived = 0; int chr; while(bytesReceived < size) { if((chr = recvc_tm(sock)) < 0) { if(bytesReceived == 0) return(NULL); else break; } *(buffer + bytesReceived++) = chr; if(chr == '\n') break; } *(buffer + bytesReceived) = 0; return(buffer); } void wait_until_read_ready(int sock) { struct timeval tm; while(wait_ready(sock, 1000, 0) <= 0) { if(socketTimeout) { gettimeofday(&tm, NULL); if(timediff_ms(tm, socketStart) > socketTimeout) longjmp(socketTimeoutJump, 1); } } } size_t recvsize_tm(char * buffer, size_t size, int sock, int flag) { ssize_t sizeRead = 0; size_t resultSize = 0; wait_until_read_ready(sock); memset(buffer, 0, size); while(size) { sizeRead = recv(sock, buffer + resultSize, size, NO_FLAGS_SET); if(sizeRead <= 0) { sizeRead = 0; break; } resultSize += sizeRead; size -= sizeRead; if(flag && transferEvent != nilSymbol) executeSymbol(transferEvent, stuffInteger(sizeRead), NULL); wait_until_read_ready(sock); } return(resultSize); } ssize_t sendf(int sock, int debug, char * format, ...) { char * buffer; va_list argptr; int result; va_start(argptr,format); /* new in 7201 , defined in nl-filesys.c if not in libc */ vasprintf(&buffer, format, argptr); result = send(sock, buffer, strlen(buffer), NO_FLAGS_SET); if(debug) varPrintf(OUT_CONSOLE, "%s", buffer); freeMemory(buffer); va_end(argptr); return(result); } CELL * p_getUrl(CELL * params) { return(getPutPostDeleteUrl(NULL, params, HTTP_GET, CONNECT_TIMEOUT)); } CELL * p_putUrl(CELL * params) { return(getPutPostDeleteUrl(NULL, params, HTTP_PUT, CONNECT_TIMEOUT)); } CELL * p_postUrl(CELL * params) { return(getPutPostDeleteUrl(NULL, params, HTTP_POST, CONNECT_TIMEOUT)); } CELL * p_deleteUrl(CELL * params) { return(getPutPostDeleteUrl(NULL, params, HTTP_DELETE, CONNECT_TIMEOUT)); } int transfer(int sock, char * buff, int len) { int bytesSend = 0, n; while(bytesSend < len) { if((n = sendall(sock, buff + bytesSend, (len - bytesSend) > BUFFSIZE ? BUFFSIZE : len - bytesSend) ) == SOCKET_ERROR) return(SOCKET_ERROR); bytesSend += n; if(transferEvent != nilSymbol) executeSymbol(transferEvent, stuffInteger(bytesSend), NULL); } return(bytesSend); } CELL * getPutPostDeleteUrl(char * url, CELL * params, int type, int timeout) { char * proxyUrl, * putPostStr = NULL, *contentType; char * protocol; char * host; char * pHost; char * path; char * customHeader = NULL; size_t bufflen; int port, pPort, sock = 0; char * option, * method = NULL; char * buff; char resultTxt[128]; char * buffPtr; char * resultPtr = NULL; int haveContentLength = FALSE, headRequest = FALSE, listFlag = FALSE, debugFlag = FALSE, rawFlag = FALSE; int chunked = FALSE; ssize_t sizeRead = 0; size_t resultSize = 0, fSize = 0, size = 0; CELL * result, * cell; CELL * headerCell = NULL; int ch, len; int responseLoop; int statusCode; buff = alloca(BUFFSIZE); /* reset net-error */ netErrorIdx = 0; /* get parameters */ if(url == NULL) params = getString(params, &url); if(type == HTTP_PUT || type == HTTP_PUT_APPEND || type == HTTP_POST) params = getStringSize(params, &putPostStr, &size, TRUE); if(my_strnicmp(url, "file://", 7) == 0) { if(type == HTTP_GET) { if((size = readFile(url, &buffPtr)) == -1) return(webError(ERROR_FILE_OP)); return(makeStringCell(buffPtr, size)); } if(type == HTTP_PUT) { if(writeFile(url, putPostStr, size, "w") == -1) return(webError(ERROR_FILE_OP)); snprintf(resultTxt, 64, "%u bytes written", (unsigned int)size); return(stuffString(resultTxt)); /* not an error */ } if(type == HTTP_DELETE) { url = getLocalPath(url); return(unlink(url) == 0 ? stuffString(OK_FILE_DELETED) : webError(ERROR_FILE_OP)); } return(webError(ERROR_BAD_URL)); } if(type == HTTP_POST) { if(params->type != CELL_NIL) params = getString(params, &contentType); else contentType = "application/x-www-form-urlencoded"; } result = evaluateExpression(params); params = params->next; if(isNumber(result->type)) { getIntegerExt(result, (UINT*)&socketTimeout, FALSE); /* set connection timeout to total-timeout specified by user */ timeout = socketTimeout; } else if(result->type == CELL_STRING) { option = (char *)result->contents; len = result->aux - 1; if(searchBuffer(option, len, "header", 6, 0) != -1) headRequest = TRUE; if(searchBuffer(option, len, "list", 4, 0) != -1) listFlag = TRUE; if(searchBuffer(option, len, "debug", 5, 0) != -1) debugFlag = TRUE; if(searchBuffer(option, len, "raw", 3, 0) != -1) rawFlag = TRUE; if(params != nilCell) params = getInteger(params, (UINT*)&socketTimeout); } else if(result != nilCell) return(errorProcExt(ERR_NUMBER_OR_STRING_EXPECTED, result)); /* if total timeout is specified, custom-header can be specified too */ if(socketTimeout && params != nilCell) getString(params, &customHeader); bufflen = strlen(url) + 1; if(bufflen < MAX_URL_LEN + 1) bufflen = MAX_URL_LEN + 1; protocol = alloca(8); host = alloca(bufflen); pHost = alloca(bufflen); path = alloca(bufflen); /* parse URL for parameters */ if(parseUrl(url, protocol, host, &port, path, bufflen) == FALSE) return(webError(ERROR_BAD_URL)); /* printf("protocol: %s host:%s port %d path:%s\n", protocol, host, port, path); */ proxyUrl = getenv("HTTP_PROXY"); if(proxyUrl == NULL) { strncpy(pHost, host, bufflen); pPort = port; } else { if(parseUrl(proxyUrl, protocol, pHost, &pPort, NULL, bufflen) == FALSE) return(webError(ERROR_BAD_URL)); } /* start timer */ gettimeofday(&socketStart, NULL); /* connect to host */ CONNECT_TO_HOST: if(sock) close(sock); if((sock = netConnect(pHost, pPort, SOCK_STREAM, 0, timeout)) == SOCKET_ERROR) return(webError(netErrorIdx)); if(type == HTTP_GET) if(headRequest == TRUE) type = HTTP_HEAD; method = requestMethod[type]; /* send header */ if(proxyUrl != NULL) sendf(sock, debugFlag, "%s %s://%s:%d/%s HTTP/1.1\r\n", method, protocol, host, port, path); else sendf(sock, debugFlag, "%s /%s HTTP/1.1\r\n", method, path); /* obligatory host spec */ sendf(sock, debugFlag, "Host: %s\r\n", host); /* send optional custom header entries */ if (customHeader != NULL) sendf(sock, debugFlag, "%s", customHeader); else sendf(sock, debugFlag, "User-Agent: newLISP v%d\r\n", version); sendf(sock, debugFlag, "%s", "Connection: close\r\n"); /* expanded header for PUT, POST and body */ if(type == HTTP_PUT || type == HTTP_PUT_APPEND) { if(type == HTTP_PUT_APPEND) sendf(sock, debugFlag, "%s", "Pragma: append\r\n"); if(customHeader == NULL) sendf(sock, debugFlag, "Content-type: text/html\r\nContent-length: %d\r\n\r\n", size); else sendf(sock, debugFlag, "Content-length: %d\r\n\r\n", size); if(transfer(sock, putPostStr, size) == SOCKET_ERROR) return(webError(ERROR_TRANSFER)); if(debugFlag) varPrintf(OUT_CONSOLE, "%s", putPostStr); } else if(type == HTTP_POST) { sendf(sock, debugFlag, "Content-type: %s\r\nContent-length: %d\r\n\r\n", contentType, size); if(transfer(sock, putPostStr, size) == SOCKET_ERROR) return(webError(ERROR_TRANSFER)); if(debugFlag) varPrintf(OUT_CONSOLE, "%s", putPostStr); } else /* HTTP_GET, HTTP_DELETE */ sendf(sock, debugFlag, "%s", "\r\n"); if(setjmp(socketTimeoutJump) != 0) { if(sock) close(sock); if(resultPtr != NULL) free(resultPtr); return(webError(ERR_INET_TIMEOUT)); } /* Retrieve HTTP response and check for status code. */ responseLoop = 0; READ_RESPONSE: if(++responseLoop == 4) return(webError(ERROR_INVALID_RESPONSE)); if (recvs_tm(buff, BUFFSIZE, sock) == NULL) return(webError(ERROR_NO_RESPONSE)); if(debugFlag) varPrintf(OUT_CONSOLE, "\n%s\n", buff); /* go past first token */ for (buffPtr = buff; *buffPtr != '\0' && !isspace((int)*buffPtr); ++buffPtr) {;} /* trim leading spaces */ while(isspace((int)*buffPtr)) ++buffPtr; /* get status code */ statusCode = atoi(buffPtr); if(statusCode >= 400) snprintf(resultTxt, 128, "ERR: %s", buff); else snprintf(resultTxt, 128, "%s", buff); switch (statusCode) { case 0: case 100: /* drain and continue */ while (ch = recvc_tm(sock), ch != EOF && ch != '\n') {;} goto READ_RESPONSE; case 200: case 201: case 202: case 203: case 204: case 205: case 206: case 300: case 301: case 302: case 303: case 307: break; default: break; } /* Retrieve HTTP headers. */ memset(buff, 0, BUFFSIZE); if(listFlag || headRequest) headerCell = stuffString(""); /* Retrieve header */ while(strcmp(buff, "\r\n") != 0 && strcmp(buff, "\n") != 0) { if(recvs_tm(buff, BUFFSIZE, sock) == NULL) return(webError(ERROR_HEADER)); if(listFlag || headRequest) appendCellString(headerCell, buff, strlen(buff)); if(my_strnicmp(buff, "content-length:", 15) == 0) { fSize = parseValue(buff + 15); haveContentLength = TRUE; } /* 302 contradicts standard for redirection but is common practice */ if(my_strnicmp(buff, "location:", 9) == 0 && !rawFlag && (statusCode == 301 || statusCode == 302 || statusCode == 303)) { buffPtr = buff + 9; while(isspace((int)*buffPtr)) ++buffPtr; if(*buffPtr == '/') strncpy(path, buffPtr + 1, bufflen); else /* its a url or path */ { if(parseUrl(buffPtr, protocol, host, &port, path, bufflen) == FALSE) /* path only */ parsePath(buffPtr, path, buffPtr - buff); if(proxyUrl == NULL) { strncpy(pHost, host, bufflen); pPort = port; } } if(headerCell) deleteList(headerCell); goto CONNECT_TO_HOST; } if(my_strnicmp(buff, "Transfer-Encoding:", 18) == 0 && searchBuffer(buff, strlen(buff), "chunked", 7, 0) != -1) chunked = TRUE; } if(headRequest) return(headerCell); /* changed in 10.6.4 should allow 0-length contents and 204 handled earlier */ /* if((haveContentLength == TRUE && fSize == 0) || statusCode == 204) */ if(statusCode == 204) { return(webError(ERROR_NO_CONTENT)); resultPtr = NULL; } /* Retrieve HTTP body. */ else if(chunked == TRUE) { resultPtr = NULL; if(recvs_tm(buff, BUFFSIZE, sock) == NULL) return(webError(ERROR_NO_CONTENT)); while((size = strtoul(buff, NULL, 16)) > 0) { if(resultSize == 0) resultPtr = allocMemory(size + 1); else resultPtr = reallocMemory(resultPtr, resultSize + size + 1); if(recvsize_tm(resultPtr + resultSize, size, sock, TRUE) != size) { free(resultPtr); return(webError(ERROR_CHUNKED_FORMAT)); } resultSize += size; recvs_tm(buff, BUFFSIZE, sock); /* empty line */ recvs_tm(buff, BUFFSIZE, sock); /* chunck size */ } } else if(haveContentLength == TRUE && fSize) { resultPtr = allocMemory(fSize + 1); if((resultSize = recvsize_tm(resultPtr, fSize, sock, TRUE)) == 0) { free(resultPtr); return(webError(ERROR_NO_CONTENT)); } } else /* no content length given, relies on host closing the connection */ { resultPtr = allocMemory(BUFFSIZE + 1); resultSize = 0; size = BUFFSIZE; while ((sizeRead = recvsize_tm(buff, BUFFSIZE, sock, FALSE)) > 0) { if((resultSize + sizeRead) > size) { size = resultSize + BUFFSIZE; resultPtr = reallocMemory(resultPtr, size + 1); } memcpy(resultPtr + resultSize, buff, sizeRead); resultSize += sizeRead; if(transferEvent != nilSymbol) executeSymbol(transferEvent, stuffInteger(sizeRead), NULL); } } if(resultPtr == NULL) { if(statusCode < 400) result = stuffString(""); else result = stuffString(resultTxt); } else { result = getCell(CELL_STRING); if(statusCode >= 400 && listFlag == FALSE) { bufflen = strlen(resultTxt); buffPtr = allocMemory(bufflen + resultSize + 1); memcpy(buffPtr, resultTxt, bufflen); memcpy(buffPtr + bufflen, resultPtr, resultSize); free(resultPtr); resultPtr = buffPtr; resultSize += bufflen; } *(resultPtr + resultSize) = 0; result->contents = (UINT)resultPtr; result->aux = resultSize + 1; } close(sock); if(listFlag) { cell = getCell(CELL_EXPRESSION); addList(cell, headerCell); addList(cell, result); addList(cell, stuffString(statusCode >= 400 ? resultTxt + 5 : resultTxt)); addList(cell, stuffInteger(statusCode)); return(cell); } return(result); } int parseUrl(char * url, char * protocol, char * host, int * port, char * path, size_t bufflen) { char * bracketPtr = NULL; char * colonPtr = NULL; char * slashPtr; int len; /* trim trailing whitespace like '/r/n' from url */ len = strlen(url); while(*(url + len) <= ' ' && len > 0) { *(url + len) = 0; len--; } *port = 80; if(my_strnicmp(url, "http://", 7) == 0) { strncpy(protocol,"http", MAX_PROTOCOL ); if( (ADDR_FAMILY == AF_INET6) && (*(url + 7) == '[') ) strncpy(host, url+8, bufflen); else strncpy(host, url+7, bufflen); } else if( my_strnicmp(url, "https://", 8) == 0) { strncpy(protocol, "https", MAX_PROTOCOL); if( (ADDR_FAMILY == AF_INET6) && (*(url + 8) == '[') ) strncpy(host, url+9, bufflen); else strncpy(host, url+8, bufflen); } else return(FALSE); if(ADDR_FAMILY == AF_INET6) bracketPtr = strchr(host, ']'); else colonPtr = strchr(host, ':'); slashPtr = strchr(host, '/'); if(ADDR_FAMILY == AF_INET6) { if (bracketPtr != NULL && (slashPtr == NULL || bracketPtr < slashPtr)) { *bracketPtr = '\0'; if(*(bracketPtr + 1) == ':') *port = atoi(bracketPtr + 2); } else { colonPtr = strchr(host, ':'); if (colonPtr != NULL && (slashPtr == NULL || colonPtr < slashPtr)) { *colonPtr++ = '\0'; *port = atoi(colonPtr); } } } else { if (colonPtr != NULL && (slashPtr == NULL || colonPtr < slashPtr)) { *colonPtr++ = '\0'; *port = atoi(colonPtr); } } if(path == NULL) return(TRUE); if (slashPtr != NULL) { *slashPtr++ = '\0'; strncpy(path, slashPtr, bufflen); } else strncpy(path, "", bufflen); /* printf("protocol:%s host:%s port:%d path:%s\n", protocol, host, *port, path); */ return(TRUE); } void parsePath(char * url, char * path, size_t bufflen) { int len; /* trim trailing whitespace like '/r/n' from url */ len = strlen(url); while(*(url + len) <= ' ' && len > 0) { *(url + len) = 0; len--; } /* trim leading whitespace */ while(*url <= ' ') url++; strncpy(path, url, bufflen); } size_t parseValue(char * str) { while(!isDigit((unsigned char)*str) && *str != 0) ++str; return atol(str); } CELL * webError(int errorNo) { char msg[64]; netErrorIdx = errorNo; snprintf(msg, 64, "ERR: %s", netErrorMsg[errorNo]); return(stuffString(msg)); } #endif /* ifndef EMSCRIPTEN */ /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://curl.haxx.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * * $Id: base64.c,v 1.32 2004/12/15 01:38:25 danf Exp $ ***************************************************************************/ /* Base64 encoding/decoding this file from the cURL project is included in nl-web.c for the newLISP functions 'base64-enc' and 'base64-dec' all #include statements have and the test harness rootines have been stripped. 2005-1-6 Lutz Mueller */ static void decodeQuantum(unsigned char *dest, const char *src) { unsigned int x = 0; int i; for(i = 0; i < 4; i++) { if(src[i] >= 'A' && src[i] <= 'Z') x = (x << 6) + (unsigned int)(src[i] - 'A' + 0); else if(src[i] >= 'a' && src[i] <= 'z') x = (x << 6) + (unsigned int)(src[i] - 'a' + 26); else if(src[i] >= '0' && src[i] <= '9') x = (x << 6) + (unsigned int)(src[i] - '0' + 52); else if(src[i] == '+') x = (x << 6) + 62; else if(src[i] == '/') x = (x << 6) + 63; else if(src[i] == '=') x = (x << 6); } dest[2] = (unsigned char)(x & 255); x >>= 8; dest[1] = (unsigned char)(x & 255); x >>= 8; dest[0] = (unsigned char)(x & 255); } /* * Curl_base64_decode() * * Given a base64 string at src, decode it into the memory pointed to by * dest. Returns the length of the decoded data. */ size_t Curl_base64_decode(const char *src, char *dest) { int length = 0; int equalsTerm = 0; int i; int numQuantums; unsigned char lastQuantum[3]; size_t rawlen=0; while((src[length] != '=') && src[length]) length++; while(src[length+equalsTerm] == '=') equalsTerm++; if(equalsTerm > 3) equalsTerm = 3; /* LM added 2006-09-08 */ numQuantums = (length + equalsTerm) / 4; if(numQuantums == 0) return(0); rawlen = (numQuantums * 3) - equalsTerm; for(i = 0; i < numQuantums - 1; i++) { decodeQuantum((unsigned char *)dest, src); dest += 3; src += 4; } decodeQuantum(lastQuantum, src); for(i = 0; i < 3 - equalsTerm; i++) dest[i] = lastQuantum[i]; return rawlen; } #define BASE64_ENC 0 #define BASE64_DEC 1 CELL * p_base64Enc(CELL * params) { return(base64(params, BASE64_ENC)); } CELL * p_base64Dec(CELL * params) { return(base64(params, BASE64_DEC)); } CELL * base64(CELL * params, int type) { char * inPtr; char * outPtr; size_t sizein, sizeout; int emptyFlag = 0; params = getStringSize(params, &inPtr, &sizein, TRUE); emptyFlag = getFlag(params); if(type == BASE64_ENC) { if(sizein == 0) return(emptyFlag ? stuffString("") : stuffString("====")); if((sizeout = Curl_base64_encode(inPtr, sizein, &outPtr)) == 0) return(stuffString("")); } else /* BASE64_DEC */ { outPtr = allocMemory((sizein * 3) / 4 + 9); sizeout = Curl_base64_decode(inPtr, outPtr); *(outPtr + sizeout) = 0; } /* strCell = getCell(CELL_STRING); strCell->contents = (UINT)outPtr; strCell->aux = sizeout + 1; return(strCell); */ return(makeStringCell(outPtr, sizeout)); } /* ---- Base64 Encoding --- */ static const char table64[]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /* * Curl_base64_encode() * * Returns the length of the newly created base64 string. The third argument * is a pointer to an allocated area holding the base64 data. If something * went wrong, -1 is returned. * */ size_t Curl_base64_encode(const char *inp, size_t insize, char **outptr) { unsigned char ibuf[3]; unsigned char obuf[4]; int i; int inputparts; char *output; char *base64data; char *indata = (char *)inp; *outptr = NULL; /* set to NULL in case of failure before we reach the end */ if(0 == insize) insize = strlen(indata); base64data = output = (char*)malloc(insize*4/3+4); if(NULL == output) return 0; while(insize > 0) { for (i = inputparts = 0; i < 3; i++) { if(insize > 0) { inputparts++; ibuf[i] = *indata; indata++; insize--; } else ibuf[i] = 0; } obuf [0] = (ibuf [0] & 0xFC) >> 2; obuf [1] = ((ibuf [0] & 0x03) << 4) | ((ibuf [1] & 0xF0) >> 4); obuf [2] = ((ibuf [1] & 0x0F) << 2) | ((ibuf [2] & 0xC0) >> 6); obuf [3] = ibuf [2] & 0x3F; switch(inputparts) { case 1: /* only one byte read */ snprintf(output, 5, "%c%c==", table64[obuf[0]], table64[obuf[1]]); break; case 2: /* two bytes read */ snprintf(output, 5, "%c%c%c=", table64[obuf[0]], table64[obuf[1]], table64[obuf[2]]); break; default: snprintf(output, 5, "%c%c%c%c", table64[obuf[0]], table64[obuf[1]], table64[obuf[2]], table64[obuf[3]] ); break; } output += 4; } *output=0; *outptr = base64data; /* make it return the actual data memory */ return strlen(base64data); /* return the length of the new data */ } /* ---- End of Base64 Encoding ---- */ #ifndef EMSCRIPTEN /* --------------------------- HTTP server mode ----------------------------- Handles GET, POST, PUT and DELETE requests handles queries in GET requests and sets environment variables DOCUMENT_ROOT, REQUEST_METHOD, SERVER_SOFTWARE and QUERY_STRING, and when present in client request header HTTP_HOST, HTTP_USER_AGENT and HTTP_COOKIE. REMOTE_ADDR is set when the client connects. Subset HTTP/1.0 compliant. */ #ifndef LIBRARY /* #define DEBUGHTTP */ #define SERVER_SOFTWARE "newLISP/10.7.0" int sendHTTPmessage(int status, char * description, char * request); void handleHTTPcgi(char * command, char * query, ssize_t querySize); size_t readHeader(char * buff, int * pragmaFlag); ssize_t readPayLoad(ssize_t size, char * content, int outFile, char * request); int endsWith(char * str, char * ext); char * getMediaType(char * request); void url_decode(char *dest, char *src); void sendHTTPpage(char * content, size_t size, char * media) { int pos = 0; char status[128]; memset(status, 0, 128); if(strncmp(content, "Status:", 7) == 0) { /* get content after */ while(*(content + pos) >= 32) pos++; memcpy(status, content + 7, pos - 7); content = content + pos; if(size) size -= pos; while(*content == '\r' || *content == '\n') { content++; size--; } } else strncpy(status, "200 OK", 6); varPrintf(OUT_CONSOLE, "HTTP/1.0 %s\r\n", status); varPrintf(OUT_CONSOLE, "Server: newLISP v.%d (%s)\r\n", version, OSTYPE); #ifdef DEBUGHTTP puts("# Header sent:"); printf("HTTP/1.0 %s\r\n", status); printf("Server: newLISP v.%d (%s)\r\n", version, OSTYPE); #endif if(media != NULL) { varPrintf(OUT_CONSOLE, "Content-length: %d\r\nContent-type: %s\r\n\r\n", size, media); #ifdef DEBUGHTTP printf("Content-length: %d\r\nContent-type: %s\r\n\r\n", (int)size, media); #endif } #ifndef WINDOWS size = write(fileno(IOchannel), content, size); fflush(IOchannel); fclose(IOchannel); IOchannel = NULL; #else /* it is WINDOWS */ if(IOchannel != NULL && IOchannelIsSocketStream) { sendall(getSocket(IOchannel), content, size); close(getSocket(IOchannel)); } else varPrintf(OUT_CONSOLE, "%s", content); return; #endif #ifdef DEBUGHTTP printf("# content:%s:\r\n", content); fflush(stdout); #endif } #define MAX_BUFF 1024 #define ERROR_404 "File or Directory not found" #define ERROR_411 "Length required for" #define ERROR_500 "Server error" #define DEFAULT_PAGE_1 "index.html" #define DEFAULT_PAGE_2 "index.cgi" #define CGI_EXTENSION ".cgi" #define MEDIA_TEXT "text/plain" int executeHTTPrequest(char * request, int type) { char * sptr; char * query; char * content = NULL; char * decoded; char buff[MAX_BUFF]; ssize_t transferred, size; char * mediaType; CELL * result = NULL; int outFile; int pragmaFlag; int len; char * fileMode = "w"; if(chdir(startupDir) < 0) fatalError(ERR_IO_ERROR, 0, 0); query = sptr = request; setenv("DOCUMENT_ROOT", startupDir, 1); setenv("SERVER_SOFTWARE", SERVER_SOFTWARE, 1); setenv("REQUEST_METHOD", requestMethod[type], 1); #ifdef DEBUGHTTP printf("# HTTP request:%s:%s:\r\n", request, requestMethod[type]); #endif /* stuff after request */ while(*sptr > ' ') ++sptr; *sptr = 0; while(*query != 0 && *query != '?') ++query; if(*query == '?') { *query = 0; query++; } setenv("QUERY_STRING", query, 1); /* do url_decode */ decoded = alloca(strlen(request) + 1); url_decode(decoded, request); request = decoded; /* change to base dir of request file */ sptr = request + strlen(request); while(*sptr != '/' && sptr != request) --sptr; if(*sptr == '/') { *sptr = 0; sptr++; if(chdir(request)) { sendHTTPmessage(404, ERROR_404, request); return(TRUE); } request = sptr; } if((len = strlen(request)) == 0) { if(isFile(DEFAULT_PAGE_2, 0) == 0) request = DEFAULT_PAGE_2; else request = DEFAULT_PAGE_1; len = strlen(request); } size = readHeader(buff, &pragmaFlag); switch(type) { case HTTP_GET: case HTTP_HEAD: if(endsWith(request, CGI_EXTENSION)) handleHTTPcgi(request, query, strlen(query)); else { mediaType = getMediaType(request); if(type == HTTP_HEAD) { snprintf(buff, MAX_BUFF - 1, "Content-length: %"PRId64"\r\nContent-type: %s\r\n\r\n", fileSize(request), mediaType); sendHTTPpage(buff, strlen(buff), NULL); } else { if((size = readFile(request, &content)) == -1) sendHTTPmessage(404, ERROR_404, request); else sendHTTPpage(content, size, mediaType); if(content) free(content); } } break; case HTTP_DELETE: if(unlink(request) != 0) sendHTTPmessage(500, "Could not delete", request); else sendHTTPpage("File deleted", 12, MEDIA_TEXT); break; case HTTP_POST: if(!size) { sendHTTPmessage(411, ERROR_411, request); break; } query = callocMemory(size + 1); if(readPayLoad(size, query, 0, request) == -1) { free(query); break; } handleHTTPcgi(request, query, size); free(query); break; case HTTP_PUT: if(pragmaFlag) fileMode = "a"; if(!size) { sendHTTPmessage(411, ERROR_411, request); break; } if( (outFile = openFile(request, fileMode, NULL)) == (int)-1) { sendHTTPmessage(500, "Cannot create file", request); break; } transferred = readPayLoad(size, buff, outFile, request); #ifdef WINDOWS _close(outFile); #else close(outFile); #endif if(transferred != -1) { snprintf(buff, 255, "%d bytes transferred for %s\r\n", (int)transferred, request); sendHTTPpage(buff, strlen(buff), MEDIA_TEXT); } break; default: break; } if(chdir(startupDir) < 0) fatalError(ERR_IO_ERROR, 0, 0); if(result != NULL) deleteList(result); return(TRUE); } int sendHTTPmessage(int status, char * desc, char * req) { char msg[256]; snprintf(msg, 256, "Status:%d %s\r\nERR:%d %s: %s\r\n", status, desc, status, desc, req); sendHTTPpage(msg, strlen(msg), MEDIA_TEXT); return(0); } /* remove leading white space */ char * trim(char * buff) { char * ptr = buff; while(*ptr <= ' ') ptr++; return(ptr); } /* retrieve rest of header */ size_t readHeader(char * buff, int * pragmaFlag) { size_t size = 0; int offset; char numStr[16]; *pragmaFlag = 0; memset(buff, 0, MAX_LINE); setenv("HTTP_HOST", "", 1); setenv("HTTP_USER_AGENT", "", 1); setenv("HTTP_COOKIE", "", 1); setenv("HTTP_AUTHORIZATION", "", 1); while(fgets(buff, MAX_LINE - 1, IOchannel) != NULL) { if(strcmp(buff, "\r\n") == 0 || strcmp(buff, "\n") == 0) break; /* trim trailing white space */ offset = strlen(buff) - 1; while(offset > 0 && *(buff + offset) <= ' ') *(buff + offset--) = 0; if(my_strnicmp(buff, "content-length:", 15) == 0) { size = parseValue(buff + 15); #if defined(WINDOWS) || defined(TRU64) snprintf(numStr, 16, "%lu", (long unsigned int)size); #else snprintf(numStr, 16, "%llu", (long long unsigned int)size); #endif setenv("CONTENT_LENGTH", numStr, 1); } if(my_strnicmp(buff, "pragma: append", 14) == 0) *pragmaFlag = TRUE; /* trim leading white space */ if(my_strnicmp(buff, "content-type:", 13) == 0) setenv("CONTENT_TYPE", trim(buff + 13), 1); if(my_strnicmp(buff, "Host:", 5) == 0) setenv("HTTP_HOST", trim(buff + 5), 1); if(my_strnicmp(buff, "User-Agent:", 11) == 0) setenv("HTTP_USER_AGENT", trim(buff + 11), 1); if(my_strnicmp(buff, "Cookie:", 7) == 0) setenv("HTTP_COOKIE", trim(buff + 7), 1); if(my_strnicmp(buff, "Authorization:", 14) == 0) setenv("HTTP_AUTHORIZATION", trim(buff + 14), 1); } return(size); } ssize_t readPayLoad(ssize_t size, char * buff, int outFile, char * request) { ssize_t bytes, readsize; size_t offset = 0, transferred = 0; #ifdef DEBUGHTTP printf("# Payload size:%ld\r\n", (long)size); #endif while(size > 0) { readsize = (size > MAX_BUFF) ? MAX_BUFF : size; #ifndef WINDOWS bytes = read(fileno(IOchannel), buff + offset, readsize); #else /* it is WINDOWS */ if(IOchannel != NULL && IOchannelIsSocketStream) bytes = recv(getSocket(IOchannel), buff + offset, readsize, NO_FLAGS_SET); else bytes = read(fileno(IOchannel), buff + offset, readsize); #endif #ifdef DEBUGHTTP printf("Payload bytes:%ld:%s:\r\n", (long)bytes, buff + offset); #endif if(bytes <= 0) { sendHTTPmessage(500, "Problem reading data", request); return(-1); } if(outFile) { if(write(outFile, buff + offset, bytes) != bytes) { sendHTTPmessage(500, "Cannot create file", request); return(-1); } } else offset += bytes; transferred += bytes; size -= bytes; } #ifndef WINDOWS fflush(NULL); #endif return(transferred); } void handleHTTPcgi(char * request, char * query, ssize_t querySize) { FILE * handle; char * command; char * content = NULL; ssize_t size; char tempfile[PATH_MAX]; #ifdef WINDOWS_BEFORE_SETTING_BINARYMODE char * ptr; char * pos; int bytes = 0; #endif srandom(milliSecTime()); #ifdef DEBUGHTTP printf("# CGI request:%s:%s:\r\n", request, query); #endif if(isFile(request, 0) != 0) { sendHTTPmessage(404, ERROR_404, request); return; } if(isFile(tempDir, 0) != 0) { sendHTTPmessage(500, "cannot find tmp directory", request); return; } size = strlen(request) + PATH_MAX; command = alloca(size); snprintf(tempfile, PATH_MAX, "%s/nl%04x-%08x-%08x", tempDir, (unsigned int)size, (unsigned int)random(), (unsigned int)random()); #if defined (WINDOWS) || (OS2) snprintf(command, size - 1, "newlisp \"%s\" > %s", request, tempfile); #else snprintf(command, size - 1, "./\"%s\" > %s", request, tempfile); #endif if((handle = popen(command, "w")) == NULL) { sendHTTPmessage(500, "failed creating pipe", request); return; } if((size = fwrite(query, 1, querySize, handle)) < 0) fatalError(ERR_IO_ERROR, 0, 0); fflush(handle); pclose(handle); size = readFile(tempfile, &content); if(size == -1) sendHTTPmessage(500, "cannot read output of", tempfile); else sendHTTPpage(content, size, NULL); #ifdef DEBUGHTTP printf("# Temporary file: %s\n", tempfile); #else unlink(tempfile); #endif if(content) free(content); } int endsWith(char * str, char * ext) { size_t size, len; size = strlen(str); len = strlen(ext); return(strncmp(str + size - len, ext, len) == 0); } typedef struct { char * extension; char * type; } T_MEDIA_TYPE; /* T_ prefix added in 10.4.8 to compile on later MinGW */ T_MEDIA_TYPE mediaType[] = { {".avi", "video/x-msvideo"}, {".css", "text/css"}, {".gif", "image/gif"}, {".htm", "text/html"}, {".html","text/html"}, {".jpg", "image/jpeg"}, {".js", "application/javascript"}, {".mov", "video/quicktime"}, {".mp3", "audio/mpeg"}, {".mpg", "video/mpeg"}, {".pdf", "application/pdf"}, {".png", "image/png"}, {".wav", "audio/x-wav"}, {".zip", "application/zip"}, { NULL, NULL}, }; char * getMediaType(char * request) { int i; for(i = 0; mediaType[i].extension != NULL; i++) { if(endsWith(request, mediaType[i].extension)) return(mediaType[i].type); } return(MEDIA_TEXT); } void url_decode(char *dest, char *src) { char code[3] = {0}; unsigned int ascii = 0; char *end = NULL; while(*src) { if(*src == '%') { memcpy(code, ++src, 2); ascii = strtoul(code, &end, 16); *dest++ = (char)ascii; src += 2; } else *dest++ = *src++; } *dest = 0; } #endif /* ifndef EMSCRIPTEN */ #endif /* ifndef LIBRARY */ /* eof */