diff options
author | tenox <as@tenoware.com> | 2016-01-11 23:57:03 -0800 |
---|---|---|
committer | tenox <as@tenoware.com> | 2016-01-11 23:57:03 -0800 |
commit | 245eaad379742cc0ba9992c858523664b02102fb (patch) | |
tree | 47eb4faed0a498e42aaf927ed24eb66bac601a9e /cgic.c | |
download | wfm-245eaad379742cc0ba9992c858523664b02102fb.tar.gz |
initial commit1.0.0
Diffstat (limited to 'cgic.c')
-rw-r--r-- | cgic.c | 2559 |
1 files changed, 2559 insertions, 0 deletions
@@ -0,0 +1,2559 @@ +/* cgicTempDir is the only setting you are likely to need + to change in this file. */ + +/* Used only in Unix environments, in conjunction with mkstemp(). + Elsewhere (Windows), temporary files go where the tmpnam() + function suggests. If this behavior does not work for you, + modify the getTempFileName() function to suit your needs. */ + +#define cgicTempDir "/tmp" + +#if CGICDEBUG +#define CGICDEBUGSTART \ + { \ + FILE *dout; \ + dout = fopen("/home/boutell/public_html/debug", "a"); \ + +#define CGICDEBUGEND \ + fclose(dout); \ + } +#else /* CGICDEBUG */ +#define CGICDEBUGSTART +#define CGICDEBUGEND +#endif /* CGICDEBUG */ + +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> +#include <sys/shm.h> + +#ifdef WIN32 +#include <io.h> + +/* cgic 2.01 */ +#include <fcntl.h> + +#else +#include <unistd.h> +#endif /* WIN32 */ +#include "cgic.h" + +#define cgiStrEq(a, b) (!strcmp((a), (b))) + +char *cgiServerSoftware; +char *cgiServerName; +char *cgiGatewayInterface; +char *cgiServerProtocol; +char *cgiServerPort; +char *cgiRequestMethod; +char *cgiPathInfo; +char *cgiPathTranslated; +char *cgiScriptName; +char *cgiQueryString; +char *cgiRemoteHost; +char *cgiRemoteAddr; +char *cgiAuthType; +char *cgiRemoteUser; +char *cgiRemoteIdent; +char cgiContentTypeData[1024]; +char *cgiContentType = cgiContentTypeData; +char *cgiMultipartBoundary; +char *cgiCookie; +int cgiContentLength; +char *cgiAccept; +char *cgiUserAgent; +char *cgiReferrer; + +char *shm_addr='\0'; +int shm_key=0; +int shm_id=0; +#define SHM_SIZE 16 + + +FILE *cgiIn; +FILE *cgiOut; + +/* True if CGI environment was restored from a file. */ +static int cgiRestored = 0; + +static void cgiGetenv(char **s, char *var); + +typedef enum { + cgiParseSuccess, + cgiParseMemory, + cgiParseIO +} cgiParseResultType; + +/* One form entry, consisting of an attribute-value pair, + and an optional filename and content type. All of + these are guaranteed to be valid null-terminated strings, + which will be of length zero in the event that the + field is not present, with the exception of tfileName + which will be null when 'in' is null. DO NOT MODIFY THESE + VALUES. Make local copies if modifications are desired. */ + +typedef struct cgiFormEntryStruct { + char *attr; + /* value is populated for regular form fields only. + For file uploads, it points to an empty string, and file + upload data should be read from the file tfileName. */ + char *value; + /* When fileName is not an empty string, tfileName is not null, + and 'value' points to an empty string. */ + /* Valid for both files and regular fields; does not include + terminating null of regular fields. */ + int valueLength; + char *fileName; + char *contentType; + /* Temporary file name for working storage of file uploads. */ + char *tfileName; + struct cgiFormEntryStruct *next; +} cgiFormEntry; + +/* The first form entry. */ +static cgiFormEntry *cgiFormEntryFirst; + +static cgiParseResultType cgiParseGetFormInput(); +static cgiParseResultType cgiParsePostFormInput(); +static cgiParseResultType cgiParsePostMultipartInput(); +static cgiParseResultType cgiParseFormInput(char *data, int length); +static void cgiSetupConstants(); +static void cgiFreeResources(); +static int cgiStrEqNc(char *s1, char *s2); +static int cgiStrBeginsNc(char *s1, char *s2); + +/* Dirty little hack to get file upload progress - Part 4: SHM Cleanup */ +void shmcleanup(void) { + if(shm_id) + shmctl(shm_id, IPC_RMID, NULL); +} + +int main(int argc, char *argv[]) { + int result; + char *cgiContentLengthString; + char *e; + cgiSetupConstants(); + cgiGetenv(&cgiServerSoftware, "SERVER_SOFTWARE"); + cgiGetenv(&cgiServerName, "SERVER_NAME"); + cgiGetenv(&cgiGatewayInterface, "GATEWAY_INTERFACE"); + cgiGetenv(&cgiServerProtocol, "SERVER_PROTOCOL"); + cgiGetenv(&cgiServerPort, "SERVER_PORT"); + cgiGetenv(&cgiRequestMethod, "REQUEST_METHOD"); + cgiGetenv(&cgiPathInfo, "PATH_INFO"); + cgiGetenv(&cgiPathTranslated, "PATH_TRANSLATED"); + cgiGetenv(&cgiScriptName, "SCRIPT_NAME"); + cgiGetenv(&cgiQueryString, "QUERY_STRING"); + cgiGetenv(&cgiRemoteHost, "REMOTE_HOST"); + cgiGetenv(&cgiRemoteAddr, "REMOTE_ADDR"); + cgiGetenv(&cgiAuthType, "AUTH_TYPE"); + cgiGetenv(&cgiRemoteUser, "REMOTE_USER"); + cgiGetenv(&cgiRemoteIdent, "REMOTE_IDENT"); + /* 2.0: the content type string needs to be parsed and modified, so + copy it to a buffer. */ + e = getenv("CONTENT_TYPE"); + if (e) { + if (strlen(e) < sizeof(cgiContentTypeData)) { + strcpy(cgiContentType, e); + } else { + /* Truncate safely in the event of what is almost certainly + a hack attempt */ + strncpy(cgiContentType, e, sizeof(cgiContentTypeData)); + cgiContentType[sizeof(cgiContentTypeData) - 1] = '\0'; + } + } else { + cgiContentType[0] = '\0'; + } + /* Never null */ + cgiMultipartBoundary = ""; + /* 2.0: parse semicolon-separated additional parameters of the + content type. The one we're interested in is 'boundary'. + We discard the rest to make cgiContentType more useful + to the typical programmer. */ + if (strchr(cgiContentType, ';')) { + char *sat = strchr(cgiContentType, ';'); + while (sat) { + *sat = '\0'; + sat++; + while (isspace((int)*sat)) { + sat++; + } + if (cgiStrBeginsNc(sat, "boundary=")) { + char *s; + cgiMultipartBoundary = sat + strlen("boundary="); + s = cgiMultipartBoundary; + while ((*s) && (!isspace((int)*s))) { + s++; + } + *s = '\0'; + break; + } else { + sat = strchr(sat, ';'); + } + } + } + cgiGetenv(&cgiContentLengthString, "CONTENT_LENGTH"); + cgiContentLength = atoi(cgiContentLengthString); + cgiGetenv(&cgiAccept, "HTTP_ACCEPT"); + cgiGetenv(&cgiUserAgent, "HTTP_USER_AGENT"); + cgiGetenv(&cgiReferrer, "HTTP_REFERER"); + cgiGetenv(&cgiCookie, "HTTP_COOKIE"); +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "%d\n", cgiContentLength); + fprintf(dout, "%s\n", cgiRequestMethod); + fprintf(dout, "%s\n", cgiContentType); + CGICDEBUGEND +#endif /* CGICDEBUG */ +#ifdef WIN32 + /* 1.07: Must set stdin and stdout to binary mode */ + /* 2.0: this is particularly crucial now and must not be removed */ + _setmode( _fileno( stdin ), _O_BINARY ); + _setmode( _fileno( stdout ), _O_BINARY ); +#endif /* WIN32 */ + cgiFormEntryFirst = 0; + cgiIn = stdin; + cgiOut = stdout; + cgiRestored = 0; + + + /* These five lines keep compilers from + producing warnings that argc and argv + are unused. They have no actual function. */ + if (argc) { + if (argv[0]) { + cgiRestored = 0; + } + } + + + if (cgiStrEqNc(cgiRequestMethod, "post")) { +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "POST recognized\n"); + CGICDEBUGEND +#endif /* CGICDEBUG */ + if (cgiStrEqNc(cgiContentType, "application/x-www-form-urlencoded")) { +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "Calling PostFormInput\n"); + CGICDEBUGEND +#endif /* CGICDEBUG */ + if (cgiParsePostFormInput() != cgiParseSuccess) { +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "PostFormInput failed\n"); + CGICDEBUGEND +#endif /* CGICDEBUG */ + cgiFreeResources(); + return -1; + } +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "PostFormInput succeeded\n"); + CGICDEBUGEND +#endif /* CGICDEBUG */ + } else if (cgiStrEqNc(cgiContentType, "multipart/form-data")) { +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "Calling PostMultipartInput\n"); + CGICDEBUGEND +#endif /* CGICDEBUG */ + if (cgiParsePostMultipartInput() != cgiParseSuccess) { +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "PostMultipartInput failed\n"); + CGICDEBUGEND +#endif /* CGICDEBUG */ + cgiFreeResources(); + return -1; + } +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "PostMultipartInput succeeded\n"); + CGICDEBUGEND +#endif /* CGICDEBUG */ + } + } else if (cgiStrEqNc(cgiRequestMethod, "get")) { + /* The spec says this should be taken care of by + the server, but... it isn't */ + cgiContentLength = strlen(cgiQueryString); + if (cgiParseGetFormInput() != cgiParseSuccess) { +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "GetFormInput failed\n"); + CGICDEBUGEND +#endif /* CGICDEBUG */ + cgiFreeResources(); + return -1; + } else { +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "GetFormInput succeeded\n"); + CGICDEBUGEND +#endif /* CGICDEBUG */ + } + } + result = cgiMain(); + cgiFreeResources(); + return result; +} + +static void cgiGetenv(char **s, char *var){ + *s = getenv(var); + if (!(*s)) { + *s = ""; + } +} + +static cgiParseResultType cgiParsePostFormInput() { + char *input; + cgiParseResultType result; + if (!cgiContentLength) { + return cgiParseSuccess; + } + input = (char *) malloc(cgiContentLength); + if (!input) { + return cgiParseMemory; + } + if (((int) fread(input, 1, cgiContentLength, cgiIn)) + != cgiContentLength) + { + return cgiParseIO; + } + result = cgiParseFormInput(input, cgiContentLength); + free(input); + return result; +} + +/* 2.0: A virtual datastream supporting putback of + enough characters to handle multipart boundaries easily. + A simple memset(&mp, 0, sizeof(mp)) is suitable initialization. */ + +typedef struct { + /* Buffer for putting characters back */ + char putback[1024]; + /* Position in putback from which next character will be read. + If readPos == writePos, then next character should + come from cgiIn. */ + int readPos; + /* Position in putback to which next character will be put back. + If writePos catches up to readPos, as opposed to the other + way around, the stream no longer functions properly. + Calling code must guarantee that no more than + sizeof(putback) bytes are put back at any given time. */ + int writePos; + /* Offset in the virtual datastream; can be compared + to cgiContentLength */ + int offset; +} mpStream, *mpStreamPtr; + +int mpRead(mpStreamPtr mpp, char *buffer, int len) +{ + int ilen = len; + int got = 0; + while (len) { + if (mpp->readPos != mpp->writePos) { + *buffer++ = mpp->putback[mpp->readPos++]; + mpp->readPos %= sizeof(mpp->putback); + got++; + len--; + } else { + break; + } + } + /* Refuse to read past the declared length in order to + avoid deadlock */ + if (len > (cgiContentLength - mpp->offset)) { + len = cgiContentLength - mpp->offset; + } + if (len) { + int fgot = fread(buffer, 1, len, cgiIn); + if (fgot >= 0) { + mpp->offset += (got + fgot); + return got + fgot; + } else if (got > 0) { + mpp->offset += got; + return got; + } else { + /* EOF or error */ + return fgot; + } + } else if (got) { + return got; + } else if (ilen) { + return EOF; + } else { + /* 2.01 */ + return 0; + } +} + +void mpPutBack(mpStreamPtr mpp, char *data, int len) +{ + mpp->offset -= len; + while (len) { + mpp->putback[mpp->writePos++] = *data++; + mpp->writePos %= sizeof(mpp->putback); + len--; + } +} + +/* This function copies the body to outf if it is not null, otherwise to + a newly allocated character buffer at *outP, which will be null + terminated; if both outf and outP are null the body is not stored. + If bodyLengthP is not null, the size of the body in bytes is stored + to *bodyLengthP, not including any terminating null added to *outP. + If 'first' is nonzero, a preceding newline is not expected before + the boundary. If 'first' is zero, a preceding newline is expected. + Upon return mpp is positioned after the boundary and its trailing + newline, if any; if the boundary is followed by -- the next two + characters read after this function returns will be --. Upon error, + if outP is not null, *outP is a null pointer; *bodyLengthP + is set to zero. Returns cgiParseSuccess, cgiParseMemory + or cgiParseIO. */ + +static cgiParseResultType afterNextBoundary(mpStreamPtr mpp, + FILE *outf, + char **outP, + int *bodyLengthP, + int first + ); + +static int readHeaderLine( + mpStreamPtr mpp, + char *attr, + int attrSpace, + char *value, + int valueSpace); + +static void decomposeValue(char *value, + char *mvalue, int mvalueSpace, + char **argNames, + char **argValues, + int argValueSpace); + +/* tfileName must be 1024 bytes to ensure adequacy on + win32 (1024 exceeds the maximum path length and + certainly exceeds observed behavior of _tmpnam). + May as well also be 1024 bytes on Unix, although actual + length is strlen(cgiTempDir) + a short unique pattern. */ + +static cgiParseResultType getTempFileName(char *tfileName); + +static cgiParseResultType cgiParsePostMultipartInput() { + cgiParseResultType result; + cgiFormEntry *n = 0, *l = 0; + int got; + int sig; + FILE *outf = 0; + char *out = 0; + char tfileName[1024]; + mpStream mp; + mpStreamPtr mpp = ∓ + memset(&mp, 0, sizeof(mp)); + if (!cgiContentLength) { + return cgiParseSuccess; + } + /* Read first boundary, including trailing newline */ + result = afterNextBoundary(mpp, 0, 0, 0, 1); + if (result == cgiParseIO) { + /* An empty submission is not necessarily an error */ + return cgiParseSuccess; + } else if (result != cgiParseSuccess) { + return result; + } + while (1) { + char d[1024]; + char fvalue[1024]; + char fname[1024]; + int bodyLength = 0; + char ffileName[1024]; + char fcontentType[1024]; + char attr[1024]; + char value[1024]; + fvalue[0] = 0; + fname[0] = 0; + ffileName[0] = 0; + fcontentType[0] = 0; + out = 0; + outf = 0; + /* Check for EOF */ + got = mpRead(mpp, d, 2); + if (got < 2) { + /* Crude EOF */ + break; + } + if ((d[0] == '-') && (d[1] == '-')) { + /* Graceful EOF */ + break; + } + mpPutBack(mpp, d, 2); + /* Read header lines until end of header */ + while (readHeaderLine( + mpp, attr, sizeof(attr), value, sizeof(value))) + { + char *argNames[3]; + char *argValues[2]; + /* Content-Disposition: form-data; + name="test"; filename="googley.gif" */ + if (cgiStrEqNc(attr, "Content-Disposition")) { + argNames[0] = "name"; + argNames[1] = "filename"; + argNames[2] = 0; + argValues[0] = fname; + argValues[1] = ffileName; + decomposeValue(value, + fvalue, sizeof(fvalue), + argNames, + argValues, + 1024); + } else if (cgiStrEqNc(attr, "Content-Type")) { + argNames[0] = 0; + decomposeValue(value, + fcontentType, sizeof(fcontentType), + argNames, + 0, + 0); + } + } + if (!cgiStrEqNc(fvalue, "form-data")) { + /* Not form data */ + continue; + } + /* Body is everything from here until the next + boundary. So, set it aside and move past boundary. + If a filename was submitted as part of the + disposition header, store to a temporary file. + Otherwise, store to a memory buffer (it is + presumably a regular form field). */ + if (strlen(ffileName)) { + if (getTempFileName(tfileName) != cgiParseSuccess) { + return cgiParseIO; + } + outf = fopen(tfileName, "w+b"); + /* Dirty little hack to get file upload progress - Part 1: Initialize */ + if (cgiFormInteger("upload_id", &shm_key, 0) == cgiFormSuccess && shm_key) { + if ((shm_id = shmget(shm_key, 16, IPC_CREAT | 0666)) > 0) { + for(sig=1; sig<=31; sig++) + signal(sig, (void*)shmcleanup); + if ((shm_addr = shmat(shm_id, NULL, 0)) > 0) + *shm_addr='\0'; + } + } + } else { + outf = 0; + tfileName[0] = '\0'; + } + result = afterNextBoundary(mpp, outf, &out, &bodyLength, 0); + /* Dirty little hack to get file upload progress - Part 2: Clean up */ + if(shm_addr) + shmdt(shm_addr); + if(shm_id) + shmctl(shm_id, IPC_RMID, NULL); + if (result != cgiParseSuccess) { + /* Lack of a boundary here is an error. */ + if (outf) { + fclose(outf); + unlink(tfileName); + } + if (out) { + free(out); + } + return result; + } + /* OK, we have a new pair, add it to the list. */ + n = (cgiFormEntry *) malloc(sizeof(cgiFormEntry)); + if (!n) { + goto outOfMemory; + } + memset(n, 0, sizeof(cgiFormEntry)); + /* 2.01: one of numerous new casts required + to please C++ compilers */ + n->attr = (char *) malloc(strlen(fname) + 1); + if (!n->attr) { + goto outOfMemory; + } + strcpy(n->attr, fname); + if (out) { + n->value = out; + out = 0; + } else if (outf) { + n->value = (char *) malloc(1); + if (!n->value) { + goto outOfMemory; + } + n->value[0] = '\0'; + fclose(outf); + } + n->valueLength = bodyLength; + n->next = 0; + if (!l) { + cgiFormEntryFirst = n; + } else { + l->next = n; + } + n->fileName = (char *) malloc(strlen(ffileName) + 1); + if (!n->fileName) { + goto outOfMemory; + } + strcpy(n->fileName, ffileName); + n->contentType = (char *) malloc(strlen(fcontentType) + 1); + if (!n->contentType) { + goto outOfMemory; + } + strcpy(n->contentType, fcontentType); + n->tfileName = (char *) malloc(strlen(tfileName) + 1); + if (!n->tfileName) { + goto outOfMemory; + } + strcpy(n->tfileName, tfileName); + + l = n; + } + return cgiParseSuccess; +outOfMemory: + if (n) { + if (n->attr) { + free(n->attr); + } + if (n->value) { + free(n->value); + } + if (n->fileName) { + free(n->fileName); + } + if (n->tfileName) { + free(n->tfileName); + } + if (n->contentType) { + free(n->contentType); + } + free(n); + } + if (out) { + free(out); + } + if (outf) { + fclose(outf); + unlink(tfileName); + } + return cgiParseMemory; +} + +static cgiParseResultType getTempFileName(char *tfileName) +{ +#ifndef WIN32 + /* Unix. Use the robust 'mkstemp' function to create + a temporary file that is truly unique, with + permissions that are truly safe. The + fopen-for-write destroys any bogus information + written by potential hackers during the brief + window between the file's creation and the + chmod call (glibc 2.0.6 and lower might + otherwise have allowed this). */ + int outfd; + strcpy(tfileName, cgicTempDir "/cgicXXXXXX"); + outfd = mkstemp(tfileName); + if (outfd == -1) { + return cgiParseIO; + } + close(outfd); + /* Fix the permissions */ + if (chmod(tfileName, 0600) != 0) { + unlink(tfileName); + return cgiParseIO; + } +#else + /* Non-Unix. Do what we can. */ + if (!tmpnam(tfileName)) { + return cgiParseIO; + } +#endif + return cgiParseSuccess; +} + + +#define APPEND(string, char) \ + { \ + if ((string##Len + 1) < string##Space) { \ + string[string##Len++] = (char); \ + } \ + } + +#define RAPPEND(string, ch) \ + { \ + if ((string##Len + 1) == string##Space) { \ + char *sold = string; \ + string##Space *= 2; \ + string = (char *) realloc(string, string##Space); \ + if (!string) { \ + string = sold; \ + goto outOfMemory; \ + } \ + } \ + string[string##Len++] = (ch); \ + } + +#define BAPPEND(ch) \ + { \ + if (outf) { \ + putc(ch, outf); \ + outLen++; \ + } else if (out) { \ + RAPPEND(out, ch); \ + } \ + } + +cgiParseResultType afterNextBoundary(mpStreamPtr mpp, FILE *outf, char **outP, + int *bodyLengthP, int first) +{ + int outLen = 0; + int outSpace = 256; + char *out = 0; + cgiParseResultType result; + int boffset; + int got; + off_t tot=0; + char d[2]; + /* This is large enough, because the buffer into which the + original boundary string is fetched is shorter by more + than four characters due to the space required for + the attribute name */ + char workingBoundaryData[1024]; + char *workingBoundary = workingBoundaryData; + int workingBoundaryLength; + if ((!outf) && (outP)) { + out = (char *) malloc(outSpace); + if (!out) { + goto outOfMemory; + } + } + boffset = 0; + sprintf(workingBoundaryData, "\r\n--%s", cgiMultipartBoundary); + if (first) { + workingBoundary = workingBoundaryData + 2; + } + workingBoundaryLength = strlen(workingBoundary); + while (1) { + got = mpRead(mpp, d, 1); + /* Dirty little hack to get file upload progress - Part 3: Set value */ + if(outf && shm_addr) + snprintf(shm_addr, SHM_SIZE, "%.1f M", (float)(tot+=got)/1024/1024); + if (got != 1) { + /* 2.01: cgiParseIO, not cgiFormIO */ + result = cgiParseIO; + goto error; + } + if (d[0] == workingBoundary[boffset]) { + /* We matched the next byte of the boundary. + Keep track of our progress into the + boundary and don't emit anything. */ + boffset++; + if (boffset == workingBoundaryLength) { + break; + } + } else if (boffset > 0) { + /* We matched part, but not all, of the + boundary. Now we have to be careful: + put back all except the first + character and try again. The + real boundary could begin in the + middle of a false match. We can + emit the first character only so far. */ + BAPPEND(workingBoundary[0]); + mpPutBack(mpp, + workingBoundary + 1, boffset - 1); + mpPutBack(mpp, d, 1); + boffset = 0; + } else { + /* Not presently in the middle of a boundary + match; just emit the character. */ + BAPPEND(d[0]); + } + } + /* Read trailing newline or -- EOF marker. A literal EOF here + would be an error in the input stream. */ + got = mpRead(mpp, d, 2); + if (got != 2) { + result = cgiParseIO; + goto error; + } + if ((d[0] == '\r') && (d[1] == '\n')) { + /* OK, EOL */ + } else if (d[0] == '-') { + /* Probably EOF, but we check for + that later */ + mpPutBack(mpp, d, 2); + } + if (out && outSpace) { + char *oout = out; + out[outLen] = '\0'; + out = (char *) realloc(out, outLen + 1); + if (!out) { + /* Surprising if it happens; and not fatal! We were + just trying to give some space back. We can + keep it if we have to. */ + out = oout; + } + *outP = out; + } + if (bodyLengthP) { + *bodyLengthP = outLen; + } + return cgiParseSuccess; +outOfMemory: + result = cgiParseMemory; + if (outP) { + if (out) { + free(out); + } + *outP = '\0'; + } +error: + if (bodyLengthP) { + *bodyLengthP = 0; + } + if (out) { + free(out); + } + if (outP) { + *outP = 0; + } + return result; +} + +static void decomposeValue(char *value, + char *mvalue, int mvalueSpace, + char **argNames, + char **argValues, + int argValueSpace) +{ + char argName[1024]; + int argNameSpace = sizeof(argName); + int argNameLen = 0; + int mvalueLen = 0; + char *argValue; + int argNum = 0; + while (argNames[argNum]) { + if (argValueSpace) { + argValues[argNum][0] = '\0'; + } + argNum++; + } + while (isspace((int)*value)) { + value++; + } + /* Quoted mvalue */ + if (*value == '\"') { + value++; + while ((*value) && (*value != '\"')) { + APPEND(mvalue, *value); + value++; + } + while ((*value) && (*value != ';')) { + value++; + } + } else { + /* Unquoted mvalue */ + while ((*value) && (*value != ';')) { + APPEND(mvalue, *value); + value++; + } + } + if (mvalueSpace) { + mvalue[mvalueLen] = '\0'; + } + while (*value == ';') { + int argNum; + int argValueLen = 0; + /* Skip the ; between parameters */ + value++; + /* Now skip leading whitespace */ + while ((*value) && (isspace((int)*value))) { + value++; + } + /* Now read the parameter name */ + argNameLen = 0; + while ((*value) && (isalnum((int)*value))) { + APPEND(argName, *value); + value++; + } + if (argNameSpace) { + argName[argNameLen] = '\0'; + } + while ((*value) && isspace((int)*value)) { + value++; + } + if (*value != '=') { + /* Malformed line */ + return; + } + value++; + while ((*value) && isspace((int)*value)) { + value++; + } + /* Find the parameter in the argument list, if present */ + argNum = 0; + argValue = 0; + while (argNames[argNum]) { + if (cgiStrEqNc(argName, argNames[argNum])) { + argValue = argValues[argNum]; + break; + } + argNum++; + } + /* Finally, read the parameter value */ + if (*value == '\"') { + value++; + while ((*value) && (*value != '\"')) { + if (argValue) { + APPEND(argValue, *value); + } + value++; + } + while ((*value) && (*value != ';')) { + value++; + } + } else { + /* Unquoted value */ + while ((*value) && (*value != ';')) { + if (argNames[argNum]) { + APPEND(argValue, *value); + } + value++; + } + } + if (argValueSpace) { + argValue[argValueLen] = '\0'; + } + } +} + +static int readHeaderLine( + mpStreamPtr mpp, + char *attr, + int attrSpace, + char *value, + int valueSpace) +{ + int attrLen = 0; + int valueLen = 0; + int valueFound = 0; + while (1) { + char d[1]; + int got = mpRead(mpp, d, 1); + if (got != 1) { + return 0; + } + if (d[0] == '\r') { + got = mpRead(mpp, d, 1); + if (got == 1) { + if (d[0] == '\n') { + /* OK */ + } else { + mpPutBack(mpp, d, 1); + } + } + break; + } else if (d[0] == '\n') { + break; + } else if ((d[0] == ':') && attrLen) { + valueFound = 1; + while (mpRead(mpp, d, 1) == 1) { + if (!isspace((int)d[0])) { + mpPutBack(mpp, d, 1); + break; + } + } + } else if (!valueFound) { + if (!isspace((int)*d)) { + if (attrLen < (attrSpace - 1)) { + attr[attrLen++] = *d; + } + } + } else if (valueFound) { + if (valueLen < (valueSpace - 1)) { + value[valueLen++] = *d; + } + } + } + if (attrSpace) { + attr[attrLen] = '\0'; + } + if (valueSpace) { + value[valueLen] = '\0'; + } + if (attrLen && valueLen) { + return 1; + } else { + return 0; + } +} + +static cgiParseResultType cgiParseGetFormInput() { + return cgiParseFormInput(cgiQueryString, cgiContentLength); +} + +typedef enum { + cgiEscapeRest, + cgiEscapeFirst, + cgiEscapeSecond +} cgiEscapeState; + +typedef enum { + cgiUnescapeSuccess, + cgiUnescapeMemory +} cgiUnescapeResultType; + +static cgiUnescapeResultType cgiUnescapeChars(char **sp, char *cp, int len); + +static cgiParseResultType cgiParseFormInput(char *data, int length) { + /* Scan for pairs, unescaping and storing them as they are found. */ + int pos = 0; + cgiFormEntry *n; + cgiFormEntry *l = 0; + while (pos != length) { + int foundEq = 0; + int foundAmp = 0; + int start = pos; + int len = 0; + char *attr; + char *value; + while (pos != length) { + if (data[pos] == '=') { + foundEq = 1; + pos++; + break; + } + pos++; + len++; + } + if (!foundEq) { + break; + } + if (cgiUnescapeChars(&attr, data+start, len) + != cgiUnescapeSuccess) { + return cgiParseMemory; + } + start = pos; + len = 0; + while (pos != length) { + if (data[pos] == '&') { + foundAmp = 1; + pos++; + break; + } + pos++; + len++; + } + /* The last pair probably won't be followed by a &, but + that's fine, so check for that after accepting it */ + if (cgiUnescapeChars(&value, data+start, len) + != cgiUnescapeSuccess) { + free(attr); + return cgiParseMemory; + } + /* OK, we have a new pair, add it to the list. */ + n = (cgiFormEntry *) malloc(sizeof(cgiFormEntry)); + if (!n) { + free(attr); + free(value); + return cgiParseMemory; + } + n->attr = attr; + n->value = value; + n->valueLength = strlen(n->value); + n->fileName = (char *) malloc(1); + if (!n->fileName) { + free(attr); + free(value); + free(n); + return cgiParseMemory; + } + n->fileName[0] = '\0'; + n->contentType = (char *) malloc(1); + if (!n->contentType) { + free(attr); + free(value); + free(n->fileName); + free(n); + return cgiParseMemory; + } + n->contentType[0] = '\0'; + n->tfileName = (char *) malloc(1); + if (!n->tfileName) { + free(attr); + free(value); + free(n->fileName); + free(n->contentType); + free(n); + return cgiParseMemory; + } + n->tfileName[0] = '\0'; + n->next = 0; + if (!l) { + cgiFormEntryFirst = n; + } else { + l->next = n; + } + l = n; + if (!foundAmp) { + break; + } + } + return cgiParseSuccess; +} + +static int cgiHexValue[256]; + +cgiUnescapeResultType cgiUnescapeChars(char **sp, char *cp, int len) { + char *s; + cgiEscapeState escapeState = cgiEscapeRest; + int escapedValue = 0; + int srcPos = 0; + int dstPos = 0; + s = (char *) malloc(len + 1); + if (!s) { + return cgiUnescapeMemory; + } + while (srcPos < len) { + int ch = cp[srcPos]; + switch (escapeState) { + case cgiEscapeRest: + if (ch == '%') { + escapeState = cgiEscapeFirst; + } else if (ch == '+') { + s[dstPos++] = ' '; + } else { + s[dstPos++] = ch; + } + break; + case cgiEscapeFirst: + escapedValue = cgiHexValue[ch] << 4; + escapeState = cgiEscapeSecond; + break; + case cgiEscapeSecond: + escapedValue += cgiHexValue[ch]; + s[dstPos++] = escapedValue; + escapeState = cgiEscapeRest; + break; + } + srcPos++; + } + s[dstPos] = '\0'; + *sp = s; + return cgiUnescapeSuccess; +} + +static void cgiSetupConstants() { + int i; + for (i=0; (i < 256); i++) { + cgiHexValue[i] = 0; + } + cgiHexValue['0'] = 0; + cgiHexValue['1'] = 1; + cgiHexValue['2'] = 2; + cgiHexValue['3'] = 3; + cgiHexValue['4'] = 4; + cgiHexValue['5'] = 5; + cgiHexValue['6'] = 6; + cgiHexValue['7'] = 7; + cgiHexValue['8'] = 8; + cgiHexValue['9'] = 9; + cgiHexValue['A'] = 10; + cgiHexValue['B'] = 11; + cgiHexValue['C'] = 12; + cgiHexValue['D'] = 13; + cgiHexValue['E'] = 14; + cgiHexValue['F'] = 15; + cgiHexValue['a'] = 10; + cgiHexValue['b'] = 11; + cgiHexValue['c'] = 12; + cgiHexValue['d'] = 13; + cgiHexValue['e'] = 14; + cgiHexValue['f'] = 15; +} + +static void cgiFreeResources() { + cgiFormEntry *c = cgiFormEntryFirst; + cgiFormEntry *n; + while (c) { + n = c->next; + free(c->attr); + free(c->value); + free(c->fileName); + free(c->contentType); + if (strlen(c->tfileName)) { + unlink(c->tfileName); + } + free(c->tfileName); + free(c); + c = n; + } + /* If the cgi environment was restored from a saved environment, + then these are in allocated space and must also be freed */ + if (cgiRestored) { + free(cgiServerSoftware); + free(cgiServerName); + free(cgiGatewayInterface); + free(cgiServerProtocol); + free(cgiServerPort); + free(cgiRequestMethod); + free(cgiPathInfo); + free(cgiPathTranslated); + free(cgiScriptName); + free(cgiQueryString); + free(cgiRemoteHost); + free(cgiRemoteAddr); + free(cgiAuthType); + free(cgiRemoteUser); + free(cgiRemoteIdent); + free(cgiContentType); + free(cgiAccept); + free(cgiUserAgent); + free(cgiReferrer); + } + /* 2.0: to clean up the environment for cgiReadEnvironment, + we must set these correctly */ + cgiFormEntryFirst = 0; + cgiRestored = 0; +} + +static cgiFormResultType cgiFormEntryString( + cgiFormEntry *e, char *result, int max, int newlines); + +static cgiFormEntry *cgiFormEntryFindFirst(char *name); +static cgiFormEntry *cgiFormEntryFindNext(); + +cgiFormResultType cgiFormString( + char *name, char *result, int max) { + cgiFormEntry *e; + e = cgiFormEntryFindFirst(name); + if (!e) { + strcpy(result, ""); + return cgiFormNotFound; + } + return cgiFormEntryString(e, result, max, 1); +} + +cgiFormResultType cgiFormFileName( + char *name, char *result, int resultSpace) +{ + cgiFormEntry *e; + int resultLen = 0; + char *s; + e = cgiFormEntryFindFirst(name); + if (!e) { + strcpy(result, ""); + return cgiFormNotFound; + } + s = e->fileName; + while (*s) { + APPEND(result, *s); + s++; + } + if (resultSpace) { + result[resultLen] = '\0'; + } + if (!strlen(e->fileName)) { + return cgiFormNoFileName; + } else if (((int) strlen(e->fileName)) > (resultSpace - 1)) { + return cgiFormTruncated; + } else { + return cgiFormSuccess; + } +} + +cgiFormResultType cgiFormFileContentType( + char *name, char *result, int resultSpace) +{ + cgiFormEntry *e; + int resultLen = 0; + char *s; + e = cgiFormEntryFindFirst(name); + if (!e) { + if (resultSpace) { + result[0] = '\0'; + } + return cgiFormNotFound; + } + s = e->contentType; + while (*s) { + APPEND(result, *s); + s++; + } + if (resultSpace) { + result[resultLen] = '\0'; + } + if (!strlen(e->contentType)) { + return cgiFormNoContentType; + } else if (((int) strlen(e->contentType)) > (resultSpace - 1)) { + return cgiFormTruncated; + } else { + return cgiFormSuccess; + } +} + +cgiFormResultType cgiFormFileSize( + char *name, int *sizeP) +{ + cgiFormEntry *e; + e = cgiFormEntryFindFirst(name); + if (!e) { + if (sizeP) { + *sizeP = 0; + } + return cgiFormNotFound; + } else if (!strlen(e->tfileName)) { + if (sizeP) { + *sizeP = 0; + } + return cgiFormNotAFile; + } else { + if (sizeP) { + *sizeP = e->valueLength; + } + return cgiFormSuccess; + } +} + +typedef struct cgiFileStruct { + FILE *in; +} cgiFile; + +cgiFormResultType cgiFormFileOpen( + char *name, cgiFilePtr *cfpp) +{ + cgiFormEntry *e; + cgiFilePtr cfp; + e = cgiFormEntryFindFirst(name); + if (!e) { + *cfpp = 0; + return cgiFormNotFound; + } + if (!strlen(e->tfileName)) { + *cfpp = 0; + return cgiFormNotAFile; + } + cfp = (cgiFilePtr) malloc(sizeof(cgiFile)); + if (!cfp) { + *cfpp = 0; + return cgiFormMemory; + } + cfp->in = fopen(e->tfileName, "rb"); + if (!cfp->in) { + free(cfp); + return cgiFormIO; + } + *cfpp = cfp; + return cgiFormSuccess; +} + +cgiFormResultType cgiFormFileRead( + cgiFilePtr cfp, char *buffer, + int bufferSize, int *gotP) +{ + int got = 0; + if (!cfp) { + return cgiFormOpenFailed; + } + got = fread(buffer, 1, bufferSize, cfp->in); + if (got <= 0) { + return cgiFormEOF; + } + *gotP = got; + return cgiFormSuccess; +} + +cgiFormResultType cgiFormFileClose(cgiFilePtr cfp) +{ + if (!cfp) { + return cgiFormOpenFailed; + } + fclose(cfp->in); + free(cfp); + return cgiFormSuccess; +} + +cgiFormResultType cgiFormStringNoNewlines( + char *name, char *result, int max) { + cgiFormEntry *e; + e = cgiFormEntryFindFirst(name); + if (!e) { + strcpy(result, ""); + return cgiFormNotFound; + } + return cgiFormEntryString(e, result, max, 0); +} + +cgiFormResultType cgiFormStringMultiple( + char *name, char ***result) { + char **stringArray; + cgiFormEntry *e; + int i; + int total = 0; + /* Make two passes. One would be more efficient, but this + function is not commonly used. The select menu and + radio box functions are faster. */ + e = cgiFormEntryFindFirst(name); + if (e != 0) { + do { + total++; + } while ((e = cgiFormEntryFindNext()) != 0); + } + stringArray = (char **) malloc(sizeof(char *) * (total + 1)); + if (!stringArray) { + *result = 0; + return cgiFormMemory; + } + /* initialize all entries to null; the last will stay that way */ + for (i=0; (i <= total); i++) { + stringArray[i] = 0; + } + /* Now go get the entries */ + e = cgiFormEntryFindFirst(name); +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "StringMultiple Beginning\n"); + CGICDEBUGEND +#endif /* CGICDEBUG */ + if (e) { + i = 0; + do { + int max = (int) (strlen(e->value) + 1); + stringArray[i] = (char *) malloc(max); + if (stringArray[i] == 0) { + /* Memory problems */ + cgiStringArrayFree(stringArray); + *result = 0; + return cgiFormMemory; + } + strcpy(stringArray[i], e->value); + cgiFormEntryString(e, stringArray[i], max, 1); + i++; + } while ((e = cgiFormEntryFindNext()) != 0); + *result = stringArray; +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "StringMultiple Succeeding\n"); + CGICDEBUGEND +#endif /* CGICDEBUG */ + return cgiFormSuccess; + } else { + *result = stringArray; +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "StringMultiple found nothing\n"); + CGICDEBUGEND +#endif /* CGICDEBUG */ + return cgiFormNotFound; + } +} + +cgiFormResultType cgiFormStringSpaceNeeded( + char *name, int *result) { + cgiFormEntry *e; + e = cgiFormEntryFindFirst(name); + if (!e) { + *result = 1; + return cgiFormNotFound; + } + *result = ((int) strlen(e->value)) + 1; + return cgiFormSuccess; +} + +static cgiFormResultType cgiFormEntryString( + cgiFormEntry *e, char *result, int max, int newlines) { + char *dp, *sp; + int truncated = 0; + int len = 0; + int avail = max-1; + int crCount = 0; + int lfCount = 0; + dp = result; + sp = e->value; + while (1) { + int ch; + /* 1.07: don't check for available space now. + We check for it immediately before adding + an actual character. 1.06 handled the + trailing null of the source string improperly, + resulting in a cgiFormTruncated error. */ + ch = *sp; + /* Fix the CR/LF, LF, CR nightmare: watch for + consecutive bursts of CRs and LFs in whatever + pattern, then actually output the larger number + of LFs. Consistently sane, yet it still allows + consecutive blank lines when the user + actually intends them. */ + if ((ch == 13) || (ch == 10)) { + if (ch == 13) { + crCount++; + } else { + lfCount++; + } + } else { + if (crCount || lfCount) { + int lfsAdd = crCount; + if (lfCount > crCount) { + lfsAdd = lfCount; + } + /* Stomp all newlines if desired */ + if (!newlines) { + lfsAdd = 0; + } + while (lfsAdd) { + if (len >= avail) { + truncated = 1; + break; + } + *dp = 10; + dp++; + lfsAdd--; + len++; + } + crCount = 0; + lfCount = 0; + } + if (ch == '\0') { + /* The end of the source string */ + break; + } + /* 1.06: check available space before adding + the character, because a previously added + LF may have brought us to the limit */ + if (len >= avail) { + truncated = 1; + break; + } + *dp = ch; + dp++; + len++; + } + sp++; + } + *dp = '\0'; + if (truncated) { + return cgiFormTruncated; + } else if (!len) { + return cgiFormEmpty; + } else { + return cgiFormSuccess; + } +} + +static int cgiFirstNonspaceChar(char *s); + +cgiFormResultType cgiFormInteger( + char *name, int *result, int defaultV) { + cgiFormEntry *e; + int ch; + e = cgiFormEntryFindFirst(name); + if (!e) { + *result = defaultV; + return cgiFormNotFound; + } + if (!strlen(e->value)) { + *result = defaultV; + return cgiFormEmpty; + } + ch = cgiFirstNonspaceChar(e->value); + if (!(isdigit(ch)) && (ch != '-') && (ch != '+')) { + *result = defaultV; + return cgiFormBadType; + } else { + *result = atoi(e->value); + return cgiFormSuccess; + } +} + +cgiFormResultType cgiFormIntegerBounded( + char *name, int *result, int min, int max, int defaultV) { + cgiFormResultType error = cgiFormInteger(name, result, defaultV); + if (error != cgiFormSuccess) { + return error; + } + if (*result < min) { + *result = min; + return cgiFormConstrained; + } + if (*result > max) { + *result = max; + return cgiFormConstrained; + } + return cgiFormSuccess; +} + +cgiFormResultType cgiFormDouble( + char *name, double *result, double defaultV) { + cgiFormEntry *e; + int ch; + e = cgiFormEntryFindFirst(name); + if (!e) { + *result = defaultV; + return cgiFormNotFound; + } + if (!strlen(e->value)) { + *result = defaultV; + return cgiFormEmpty; + } + ch = cgiFirstNonspaceChar(e->value); + if (!(isdigit(ch)) && (ch != '.') && (ch != '-') && (ch != '+')) { + *result = defaultV; + return cgiFormBadType; + } else { + *result = atof(e->value); + return cgiFormSuccess; + } +} + +cgiFormResultType cgiFormDoubleBounded( + char *name, double *result, double min, double max, double defaultV) { + cgiFormResultType error = cgiFormDouble(name, result, defaultV); + if (error != cgiFormSuccess) { + return error; + } + if (*result < min) { + *result = min; + return cgiFormConstrained; + } + if (*result > max) { + *result = max; + return cgiFormConstrained; + } + return cgiFormSuccess; +} + +cgiFormResultType cgiFormSelectSingle( + char *name, char **choicesText, int choicesTotal, + int *result, int defaultV) +{ + cgiFormEntry *e; + int i; + e = cgiFormEntryFindFirst(name); +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "%d\n", (int) e); + CGICDEBUGEND +#endif /* CGICDEBUG */ + if (!e) { + *result = defaultV; + return cgiFormNotFound; + } + for (i=0; (i < choicesTotal); i++) { +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "%s %s\n", choicesText[i], e->value); + CGICDEBUGEND +#endif /* CGICDEBUG */ + if (cgiStrEq(choicesText[i], e->value)) { +#ifdef CGICDEBUG + CGICDEBUGSTART + fprintf(dout, "MATCH\n"); + CGICDEBUGEND +#endif /* CGICDEBUG */ + *result = i; + return cgiFormSuccess; + } + } + *result = defaultV; + return cgiFormNoSuchChoice; +} + +cgiFormResultType cgiFormSelectMultiple( + char *name, char **choicesText, int choicesTotal, + int *result, int *invalid) +{ + cgiFormEntry *e; + int i; + int hits = 0; + int invalidE = 0; + for (i=0; (i < choicesTotal); i++) { + result[i] = 0; + } + e = cgiFormEntryFindFirst(name); + if (!e) { + *invalid = invalidE; + return cgiFormNotFound; + } + do { + int hit = 0; + for (i=0; (i < choicesTotal); i++) { + if (cgiStrEq(choicesText[i], e->value)) { + result[i] = 1; + hits++; + hit = 1; + break; + } + } + if (!(hit)) { + invalidE++; + } + } while ((e = cgiFormEntryFindNext()) != 0); + + *invalid = invalidE; + + if (hits) { + return cgiFormSuccess; + } else { + return cgiFormNotFound; + } +} + +cgiFormResultType cgiFormCheckboxSingle( + char *name) +{ + cgiFormEntry *e; + e = cgiFormEntryFindFirst(name); + if (!e) { + return cgiFormNotFound; + } + return cgiFormSuccess; +} + +extern cgiFormResultType cgiFormCheckboxMultiple( + char *name, char **valuesText, int valuesTotal, + int *result, int *invalid) +{ + /* Implementation is identical to cgiFormSelectMultiple. */ + return cgiFormSelectMultiple(name, valuesText, + valuesTotal, result, invalid); +} + +cgiFormResultType cgiFormRadio( + char *name, + char **valuesText, int valuesTotal, int *result, int defaultV) +{ + /* Implementation is identical to cgiFormSelectSingle. */ + return cgiFormSelectSingle(name, valuesText, valuesTotal, + result, defaultV); +} + +cgiFormResultType cgiCookieString( + char *name, + char *value, + int space) +{ + char *p = cgiCookie; + while (*p) { + char *n = name; + /* 2.02: if cgiCookie is exactly equal to name, this + can cause an overrun. The server probably wouldn't + allow it, since a name without values makes no sense + -- but then again it might not check, so this is a + genuine security concern. Thanks to Nicolas + Tomadakis. */ + while (*p == *n) { + if ((p == '\0') && (n == '\0')) { + /* Malformed cookie header from client */ + return cgiFormNotFound; + } + p++; + n++; + } + if ((!*n) && (*p == '=')) { + p++; + while ((*p != ';') && (*p != '\0') && + (space > 1)) + { + *value = *p; + value++; + p++; + space--; + } + if (space > 0) { + *value = '\0'; + } + /* Correct parens: 2.02. Thanks to + Mathieu Villeneuve-Belair. */ + if (!(((*p) == ';') || ((*p) == '\0'))) + { + return cgiFormTruncated; + } else { + return cgiFormSuccess; + } + } else { + /* Skip to next cookie */ + while (*p) { + if (*p == ';') { + break; + } + p++; + } + if (!*p) { + /* 2.01: default to empty */ + if (space) { + *value = '\0'; + } + return cgiFormNotFound; + } + p++; + /* Allow whitespace after semicolon */ + while ((*p) && isspace((int)*p)) { + p++; + } + } + } + /* 2.01: actually the above loop never terminates except + with a return, but do this to placate gcc */ + if (space) { + *value = '\0'; + } + return cgiFormNotFound; +} + +cgiFormResultType cgiCookieInteger( + char *name, + int *result, + int defaultV) +{ + char buffer[256]; + cgiFormResultType r = + cgiCookieString(name, buffer, sizeof(buffer)); + if (r != cgiFormSuccess) { + *result = defaultV; + } else { + *result = atoi(buffer); + } + return r; +} + +void cgiHeaderCookieSetInteger(char *name, int value, int secondsToLive, + char *path, char *domain) +{ + char svalue[256]; + sprintf(svalue, "%d", value); + cgiHeaderCookieSetString(name, svalue, secondsToLive, path, domain); +} + +char *days[] = { + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat" +}; + +char *months[] = { + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" +}; + +void cgiHeaderCookieSetString(char *name, char *value, int secondsToLive, + char *path, char *domain) +{ + /* cgic 2.02: simpler and more widely compatible implementation. + Thanks to Chunfu Lai. + cgic 2.03: yes, but it didn't work. Reimplemented by + Thomas Boutell. ; after last element was a bug. + Examples of real world cookies that really work: + Set-Cookie: MSNADS=UM=; domain=.slate.com; + expires=Tue, 26-Apr-2022 19:00:00 GMT; path=/ + Set-Cookie: MC1=V=3&ID=b5bc08af2b8a43ff85fcb5efd8b238f0; + domain=.slate.com; expires=Mon, 04-Oct-2021 19:00:00 GMT; path=/ + */ + time_t now; + time_t then; + struct tm *gt; + time(&now); + then = now + secondsToLive; + gt = gmtime(&then); + fprintf(cgiOut, + "Set-Cookie: %s=%s; domain=%s; expires=%s, %02d-%s-%04d %02d:%02d:%02d GMT; path=%s\r\n", + name, value, domain, + days[gt->tm_wday], + gt->tm_mday, + months[gt->tm_mon], + gt->tm_year + 1900, + gt->tm_hour, + gt->tm_min, + gt->tm_sec, + path); +} + +void cgiHeaderLocation(char *redirectUrl) { + fprintf(cgiOut, "Location: %s\r\n\r\n", redirectUrl); +} + +void cgiHeaderStatus(int status, char *statusMessage) { + fprintf(cgiOut, "Status: %d %s\r\n\r\n", status, statusMessage); +} + +void cgiHeaderContentType(char *mimeType) { + fprintf(cgiOut, "Content-type: %s\r\n\r\n", mimeType); +} + +static int cgiWriteString(FILE *out, char *s); + +static int cgiWriteInt(FILE *out, int i); + +#define CGIC_VERSION "2.0" + +cgiEnvironmentResultType cgiWriteEnvironment(char *filename) { + FILE *out; + cgiFormEntry *e; + /* Be sure to open in binary mode */ + out = fopen(filename, "wb"); + if (!out) { + /* Can't create file */ + return cgiEnvironmentIO; + } + if (!cgiWriteString(out, "CGIC2.0")) { + goto error; + } + if (!cgiWriteString(out, cgiServerSoftware)) { + goto error; + } + if (!cgiWriteString(out, cgiServerName)) { + goto error; + } + if (!cgiWriteString(out, cgiGatewayInterface)) { + goto error; + } + if (!cgiWriteString(out, cgiServerProtocol)) { + goto error; + } + if (!cgiWriteString(out, cgiServerPort)) { + goto error; + } + if (!cgiWriteString(out, cgiRequestMethod)) { + goto error; + } + if (!cgiWriteString(out, cgiPathInfo)) { + goto error; + } + if (!cgiWriteString(out, cgiPathTranslated)) { + goto error; + } + if (!cgiWriteString(out, cgiScriptName)) { + goto error; + } + if (!cgiWriteString(out, cgiQueryString)) { + goto error; + } + if (!cgiWriteString(out, cgiRemoteHost)) { + goto error; + } + if (!cgiWriteString(out, cgiRemoteAddr)) { + goto error; + } + if (!cgiWriteString(out, cgiAuthType)) { + goto error; + } + if (!cgiWriteString(out, cgiRemoteUser)) { + goto error; + } + if (!cgiWriteString(out, cgiRemoteIdent)) { + goto error; + } + if (!cgiWriteString(out, cgiContentType)) { + goto error; + } + if (!cgiWriteString(out, cgiAccept)) { + goto error; + } + if (!cgiWriteString(out, cgiUserAgent)) { + goto error; + } + if (!cgiWriteString(out, cgiReferrer)) { + goto error; + } + if (!cgiWriteString(out, cgiCookie)) { + goto error; + } + if (!cgiWriteInt(out, cgiContentLength)) { + goto error; + } + e = cgiFormEntryFirst; + while (e) { + cgiFilePtr fp; + if (!cgiWriteString(out, e->attr)) { + goto error; + } + if (!cgiWriteString(out, e->value)) { + goto error; + } + /* New 2.0 fields and file uploads */ + if (!cgiWriteString(out, e->fileName)) { + goto error; + } + if (!cgiWriteString(out, e->contentType)) { + goto error; + } + if (!cgiWriteInt(out, e->valueLength)) { + goto error; + } + if (cgiFormFileOpen(e->attr, &fp) == cgiFormSuccess) { + char buffer[1024]; + int got; + if (!cgiWriteInt(out, 1)) { + cgiFormFileClose(fp); + goto error; + } + while (cgiFormFileRead(fp, buffer, + sizeof(buffer), &got) == cgiFormSuccess) + { + if (((int) fwrite(buffer, 1, got, out)) != got) { + cgiFormFileClose(fp); + goto error; + } + } + if (cgiFormFileClose(fp) != cgiFormSuccess) { + goto error; + } + } else { + if (!cgiWriteInt(out, 0)) { + goto error; + } + } + e = e->next; + } + fclose(out); + return cgiEnvironmentSuccess; +error: + fclose(out); + /* If this function is not defined in your system, + you must substitute the appropriate + file-deletion function. */ + unlink(filename); + return cgiEnvironmentIO; +} + +static int cgiWriteString(FILE *out, char *s) { + int len = (int) strlen(s); + cgiWriteInt(out, len); + if (((int) fwrite(s, 1, len, out)) != len) { + return 0; + } + return 1; +} + +static int cgiWriteInt(FILE *out, int i) { + if (!fwrite(&i, sizeof(int), 1, out)) { + return 0; + } + return 1; +} + +static int cgiReadString(FILE *out, char **s); + +static int cgiReadInt(FILE *out, int *i); + +cgiEnvironmentResultType cgiReadEnvironment(char *filename) { + FILE *in; + cgiFormEntry *e = 0, *p; + char *version; + /* Prevent compiler warnings */ + cgiEnvironmentResultType result = cgiEnvironmentIO; + /* Free any existing data first */ + cgiFreeResources(); + /* Be sure to open in binary mode */ + in = fopen(filename, "rb"); + if (!in) { + /* Can't access file */ + return cgiEnvironmentIO; + } + if (!cgiReadString(in, &version)) { + goto error; + } + if (strcmp(version, "CGIC" CGIC_VERSION)) { + /* 2.02: Merezko Oleg */ + free(version); + return cgiEnvironmentWrongVersion; + } + /* 2.02: Merezko Oleg */ + free(version); + if (!cgiReadString(in, &cgiServerSoftware)) { + goto error; + } + if (!cgiReadString(in, &cgiServerName)) { + goto error; + } + if (!cgiReadString(in, &cgiGatewayInterface)) { + goto error; + } + if (!cgiReadString(in, &cgiServerProtocol)) { + goto error; + } + if (!cgiReadString(in, &cgiServerPort)) { + goto error; + } + if (!cgiReadString(in, &cgiRequestMethod)) { + goto error; + } + if (!cgiReadString(in, &cgiPathInfo)) { + goto error; + } + if (!cgiReadString(in, &cgiPathTranslated)) { + goto error; + } + if (!cgiReadString(in, &cgiScriptName)) { + goto error; + } + if (!cgiReadString(in, &cgiQueryString)) { + goto error; + } + if (!cgiReadString(in, &cgiRemoteHost)) { + goto error; + } + if (!cgiReadString(in, &cgiRemoteAddr)) { + goto error; + } + if (!cgiReadString(in, &cgiAuthType)) { + goto error; + } + if (!cgiReadString(in, &cgiRemoteUser)) { + goto error; + } + if (!cgiReadString(in, &cgiRemoteIdent)) { + goto error; + } + if (!cgiReadString(in, &cgiContentType)) { + goto error; + } + if (!cgiReadString(in, &cgiAccept)) { + goto error; + } + if (!cgiReadString(in, &cgiUserAgent)) { + goto error; + } + if (!cgiReadString(in, &cgiReferrer)) { + goto error; + } + /* 2.0 */ + if (!cgiReadString(in, &cgiCookie)) { + goto error; + } + if (!cgiReadInt(in, &cgiContentLength)) { + goto error; + } + p = 0; + while (1) { + int fileFlag; + e = (cgiFormEntry *) calloc(1, sizeof(cgiFormEntry)); + if (!e) { + cgiFreeResources(); + fclose(in); + return cgiEnvironmentMemory; + } + memset(e, 0, sizeof(cgiFormEntry)); + if (!cgiReadString(in, &e->attr)) { + /* This means we've reached the end of the list. */ + /* 2.02: thanks to Merezko Oleg */ + free(e); + break; + } + if (!cgiReadString(in, &e->value)) { + goto outOfMemory; + } + if (!cgiReadString(in, &e->fileName)) { + goto outOfMemory; + } + if (!cgiReadString(in, &e->contentType)) { + goto outOfMemory; + } + if (!cgiReadInt(in, &e->valueLength)) { + goto outOfMemory; + } + if (!cgiReadInt(in, &fileFlag)) { + goto outOfMemory; + } + if (fileFlag) { + char buffer[1024]; + FILE *out; + char tfileName[1024]; + int got; + int len = e->valueLength; + if (getTempFileName(tfileName) + != cgiParseSuccess) + { + result = cgiEnvironmentIO; + goto error; + } + out = fopen(tfileName, "w+b"); + if (!out) { + result = cgiEnvironmentIO; + goto error; + } + while (len > 0) { + /* 2.01: try is a bad variable name in + C++, and it wasn't being used + properly either */ + int tryr = len; + if (tryr > ((int) sizeof(buffer))) { + tryr = sizeof(buffer); + } + got = fread(buffer, 1, tryr, in); + if (got <= 0) { + result = cgiEnvironmentIO; + fclose(out); + unlink(tfileName); + goto error; + } + if (((int) fwrite(buffer, 1, got, out)) != got) { + result = cgiEnvironmentIO; + fclose(out); + unlink(tfileName); + goto error; + } + len -= got; + } + /* cgic 2.05: should be fclose not rewind */ + fclose(out); + e->tfileName = (char *) malloc((int) strlen(tfileName) + 1); + if (!e->tfileName) { + result = cgiEnvironmentMemory; + unlink(tfileName); + goto error; + } + strcpy(e->tfileName, tfileName); + } else { + e->tfileName = (char *) malloc(1); + if (!e->tfileName) { + result = cgiEnvironmentMemory; + goto error; + } + } + e->next = 0; + if (p) { + p->next = e; + } else { + cgiFormEntryFirst = e; + } + p = e; + } + fclose(in); + cgiRestored = 1; + return cgiEnvironmentSuccess; +outOfMemory: + result = cgiEnvironmentMemory; +error: + cgiFreeResources(); + fclose(in); + if (e) { + if (e->attr) { + free(e->attr); + } + if (e->value) { + free(e->value); + } + if (e->fileName) { + free(e->fileName); + } + if (e->contentType) { + free(e->contentType); + } + if (e->tfileName) { + free(e->tfileName); + } + free(e); + } + return result; +} + +static int cgiReadString(FILE *in, char **s) { + int len; + /* 2.0 fix: test cgiReadInt for failure! */ + if (!cgiReadInt(in, &len)) { + return 0; + } + *s = (char *) malloc(len + 1); + if (!(*s)) { + return 0; + } + if (((int) fread(*s, 1, len, in)) != len) { + return 0; + } + (*s)[len] = '\0'; + return 1; +} + +static int cgiReadInt(FILE *out, int *i) { + if (!fread(i, sizeof(int), 1, out)) { + return 0; + } + return 1; +} + +static int cgiStrEqNc(char *s1, char *s2) { + while(1) { + if (!(*s1)) { + if (!(*s2)) { + return 1; + } else { + return 0; + } + } else if (!(*s2)) { + return 0; + } + if (isalpha((int)*s1)) { + if (tolower(*s1) != tolower(*s2)) { + return 0; + } + } else if ((*s1) != (*s2)) { + return 0; + } + s1++; + s2++; + } +} + +static int cgiStrBeginsNc(char *s1, char *s2) { + while(1) { + if (!(*s2)) { + return 1; + } else if (!(*s1)) { + return 0; + } + if (isalpha((int)*s1)) { + if (tolower(*s1) != tolower(*s2)) { + return 0; + } + } else if ((*s1) != (*s2)) { + return 0; + } + s1++; + s2++; + } +} + +static char *cgiFindTarget = 0; +static cgiFormEntry *cgiFindPos = 0; + +static cgiFormEntry *cgiFormEntryFindFirst(char *name) { + cgiFindTarget = name; + cgiFindPos = cgiFormEntryFirst; + return cgiFormEntryFindNext(); +} + +static cgiFormEntry *cgiFormEntryFindNext() { + while (cgiFindPos) { + cgiFormEntry *c = cgiFindPos; + cgiFindPos = c->next; + if (!strcmp(c -> attr, cgiFindTarget)) { + return c; + } + } + return 0; +} + +static int cgiFirstNonspaceChar(char *s) { + int len = strspn(s, " \n\r\t"); + return s[len]; +} + +void cgiStringArrayFree(char **stringArray) { + char *p; + char **arrayItself = stringArray; + p = *stringArray; + while (p) { + free(p); + stringArray++; + p = *stringArray; + } + /* 2.0: free the array itself! */ + free(arrayItself); +} + +cgiFormResultType cgiCookies(char ***result) { + char **stringArray; + int i; + int total = 0; + char *p; + char *n; + p = cgiCookie; + while (*p) { + if (*p == '=') { + total++; + } + p++; + } + stringArray = (char **) malloc(sizeof(char *) * (total + 1)); + if (!stringArray) { + *result = 0; + return cgiFormMemory; + } + /* initialize all entries to null; the last will stay that way */ + for (i=0; (i <= total); i++) { + stringArray[i] = 0; + } + i = 0; + p = cgiCookie; + while (*p) { + while (*p && isspace((int)*p)) { + p++; + } + n = p; + while (*p && (*p != '=')) { + p++; + } + if (p != n) { + stringArray[i] = (char *) malloc((p - n) + 1); + if (!stringArray[i]) { + cgiStringArrayFree(stringArray); + *result = 0; + return cgiFormMemory; + } + memcpy(stringArray[i], n, p - n); + stringArray[i][p - n] = '\0'; + i++; + } + while (*p && (*p != ';')) { + p++; + } + if (!*p) { + break; + } + if (*p == ';') { + p++; + } + } + *result = stringArray; + return cgiFormSuccess; +} + +cgiFormResultType cgiFormEntries(char ***result) { + char **stringArray; + cgiFormEntry *e, *pe; + int i; + int total = 0; + e = cgiFormEntryFirst; + while (e) { + /* Don't count a field name more than once if + multiple values happen to be present for it */ + pe = cgiFormEntryFirst; + while (pe != e) { + if (!strcmp(e->attr, pe->attr)) { + goto skipSecondValue; + } + pe = pe->next; + } + total++; +skipSecondValue: + e = e->next; + } + stringArray = (char **) malloc(sizeof(char *) * (total + 1)); + if (!stringArray) { + *result = 0; + return cgiFormMemory; + } + /* initialize all entries to null; the last will stay that way */ + for (i=0; (i <= total); i++) { + stringArray[i] = 0; + } + /* Now go get the entries */ + e = cgiFormEntryFirst; + i = 0; + while (e) { + int space; + /* Don't return a field name more than once if + multiple values happen to be present for it */ + pe = cgiFormEntryFirst; + while (pe != e) { + if (!strcmp(e->attr, pe->attr)) { + goto skipSecondValue2; + } + pe = pe->next; + } + space = (int) strlen(e->attr) + 1; + stringArray[i] = (char *) malloc(space); + if (stringArray[i] == 0) { + /* Memory problems */ + cgiStringArrayFree(stringArray); + *result = 0; + return cgiFormMemory; + } + strcpy(stringArray[i], e->attr); + i++; +skipSecondValue2: + e = e->next; + } + *result = stringArray; + return cgiFormSuccess; +} + +#define TRYPUTC(ch) \ + { \ + if (putc((ch), cgiOut) == EOF) { \ + return cgiFormIO; \ + } \ + } + +cgiFormResultType cgiHtmlEscapeData(char *data, int len) +{ + while (len--) { + if (*data == '<') { + TRYPUTC('&'); + TRYPUTC('l'); + TRYPUTC('t'); + TRYPUTC(';'); + } else if (*data == '&') { + TRYPUTC('&'); + TRYPUTC('a'); + TRYPUTC('m'); + TRYPUTC('p'); + TRYPUTC(';'); + } else if (*data == '>') { + TRYPUTC('&'); + TRYPUTC('g'); + TRYPUTC('t'); + TRYPUTC(';'); + } else { + TRYPUTC(*data); + } + data++; + } + return cgiFormSuccess; +} + +cgiFormResultType cgiHtmlEscape(char *s) +{ + return cgiHtmlEscapeData(s, (int) strlen(s)); +} + +/* Output data with the " character HTML-escaped, and no + other characters escaped. This is useful when outputting + the contents of a tag attribute such as 'href' or 'src'. + 'data' is not null-terminated; 'len' is the number of + bytes in 'data'. Returns cgiFormIO in the event + of error, cgiFormSuccess otherwise. */ +cgiFormResultType cgiValueEscapeData(char *data, int len) +{ + while (len--) { + if (*data == '\"') { + TRYPUTC('&'); + TRYPUTC('#'); + TRYPUTC('3'); + TRYPUTC('4'); + TRYPUTC(';'); + } else { + TRYPUTC(*data); + } + data++; + } + return cgiFormSuccess; +} + +cgiFormResultType cgiValueEscape(char *s) +{ + return cgiValueEscapeData(s, (int) strlen(s)); +} + + |