cgic205/0040755000031000004540000000000010146160236011212 5ustar boutelldevcgic205/Makefile0100644000031000004540000000107710042027753012655 0ustar boutelldevCFLAGS=-g -Wall CC=gcc AR=ar RANLIB=ranlib LIBS=-L./ -lcgic all: libcgic.a cgictest.cgi capture install: libcgic.a cp libcgic.a /usr/local/lib cp cgic.h /usr/local/include @echo libcgic.a is in /usr/local/lib. cgic.h is in /usr/local/include. libcgic.a: cgic.o cgic.h rm -f libcgic.a $(AR) rc libcgic.a cgic.o $(RANLIB) libcgic.a #mingw32 and cygwin users: replace .cgi with .exe cgictest.cgi: cgictest.o libcgic.a gcc cgictest.o -o cgictest.cgi ${LIBS} capture: capture.o libcgic.a gcc capture.o -o capture ${LIBS} clean: rm -f *.o *.a cgictest.cgi capture cgic205/capture.c0100644000031000004540000000052210042027753013016 0ustar boutelldev#include "cgic.h" int cgiMain() { cgiWriteEnvironment("/CHANGE/THIS/PATH/capcgi.dat"); cgiHeaderContentType("text/html"); fprintf(cgiOut, "Captured\n"); fprintf(cgiOut, "

Captured

\n"); fprintf(cgiOut, "Your form submission was captured for use in\n"); fprintf(cgiOut, "debugging CGI code.\n"); return 0; } cgic205/cgic.c0100644000031000004540000015377710146160027012302 0ustar boutelldev/* 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 #include #include #include #include #include #include #ifdef WIN32 #include /* cgic 2.01 */ #include #else #include #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; 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); 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(*sat)) { sat++; } if (cgiStrBeginsNc(sat, "boundary=")) { char *s; cgiMultipartBoundary = sat + strlen("boundary="); s = cgiMultipartBoundary; while ((*s) && (!isspace(*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; 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"); } else { outf = 0; tfileName[0] = '\0'; } result = afterNextBoundary(mpp, outf, &out, &bodyLength, 0); 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; 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); 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(*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(*value))) { value++; } /* Now read the parameter name */ argNameLen = 0; while ((*value) && (isalnum(*value))) { APPEND(argName, *value); value++; } if (argNameSpace) { argName[argNameLen] = '\0'; } while ((*value) && isspace(*value)) { value++; } if (*value != '=') { /* Malformed line */ return; } value++; while ((*value) && isspace(*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(d[0])) { mpPutBack(mpp, d, 1); break; } } } else if (!valueFound) { if (!isspace(*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(*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(*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(*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(*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)); } cgic205/cgic.h0100644000031000004540000001635310042027753012276 0ustar boutelldev/* The CGI_C library, by Thomas Boutell, version 2.01. CGI_C is intended to be a high-quality API to simplify CGI programming tasks. */ /* Make sure this is only included once. */ #ifndef CGI_C #define CGI_C 1 /* Bring in standard I/O since some of the functions refer to types defined by it, such as FILE *. */ #include /* The various CGI environment variables. Instead of using getenv(), the programmer should refer to these, which are always valid null-terminated strings (they may be empty, but they will never be null). If these variables are used instead of calling getenv(), then it will be possible to save and restore CGI environments, which is highly convenient for debugging. */ extern char *cgiServerSoftware; extern char *cgiServerName; extern char *cgiGatewayInterface; extern char *cgiServerProtocol; extern char *cgiServerPort; extern char *cgiRequestMethod; extern char *cgiPathInfo; extern char *cgiPathTranslated; extern char *cgiScriptName; extern char *cgiQueryString; extern char *cgiRemoteHost; extern char *cgiRemoteAddr; extern char *cgiAuthType; extern char *cgiRemoteUser; extern char *cgiRemoteIdent; extern char *cgiContentType; extern char *cgiAccept; extern char *cgiUserAgent; extern char *cgiReferrer; /* Cookies as sent to the server. You can also get them individually, or as a string array; see the documentation. */ extern char *cgiCookie; /* A macro providing the same incorrect spelling that is found in the HTTP/CGI specifications */ #define cgiReferer cgiReferrer /* The number of bytes of data received. Note that if the submission is a form submission the library will read and parse all the information directly from cgiIn; the programmer need not do so. */ extern int cgiContentLength; /* Pointer to CGI output. The cgiHeader functions should be used first to output the mime headers; the output HTML page, GIF image or other web document should then be written to cgiOut by the programmer. In the standard CGIC library, cgiOut is always equivalent to stdout. */ extern FILE *cgiOut; /* Pointer to CGI input. The programmer does not read from this. We have continued to export it for backwards compatibility so that cgic 1.x applications link properly. */ extern FILE *cgiIn; /* Possible return codes from the cgiForm family of functions (see below). */ typedef enum { cgiFormSuccess, cgiFormTruncated, cgiFormBadType, cgiFormEmpty, cgiFormNotFound, cgiFormConstrained, cgiFormNoSuchChoice, cgiFormMemory, cgiFormNoFileName, cgiFormNoContentType, cgiFormNotAFile, cgiFormOpenFailed, cgiFormIO, cgiFormEOF } cgiFormResultType; /* These functions are used to retrieve form data. See cgic.html for documentation. */ extern cgiFormResultType cgiFormString( char *name, char *result, int max); extern cgiFormResultType cgiFormStringNoNewlines( char *name, char *result, int max); extern cgiFormResultType cgiFormStringSpaceNeeded( char *name, int *length); extern cgiFormResultType cgiFormStringMultiple( char *name, char ***ptrToStringArray); extern void cgiStringArrayFree(char **stringArray); extern cgiFormResultType cgiFormInteger( char *name, int *result, int defaultV); extern cgiFormResultType cgiFormIntegerBounded( char *name, int *result, int min, int max, int defaultV); extern cgiFormResultType cgiFormDouble( char *name, double *result, double defaultV); extern cgiFormResultType cgiFormDoubleBounded( char *name, double *result, double min, double max, double defaultV); extern cgiFormResultType cgiFormSelectSingle( char *name, char **choicesText, int choicesTotal, int *result, int defaultV); extern cgiFormResultType cgiFormSelectMultiple( char *name, char **choicesText, int choicesTotal, int *result, int *invalid); /* Just an alias; users have asked for this */ #define cgiFormSubmitClicked cgiFormCheckboxSingle extern cgiFormResultType cgiFormCheckboxSingle( char *name); extern cgiFormResultType cgiFormCheckboxMultiple( char *name, char **valuesText, int valuesTotal, int *result, int *invalid); extern cgiFormResultType cgiFormRadio( char *name, char **valuesText, int valuesTotal, int *result, int defaultV); /* The paths returned by this function are the original names of files as reported by the uploading web browser and shoult NOT be blindly assumed to be "safe" names for server-side use! */ extern cgiFormResultType cgiFormFileName( char *name, char *result, int max); /* The content type of the uploaded file, as reported by the browser. It should NOT be assumed that browsers will never falsify such information. */ extern cgiFormResultType cgiFormFileContentType( char *name, char *result, int max); extern cgiFormResultType cgiFormFileSize( char *name, int *sizeP); typedef struct cgiFileStruct *cgiFilePtr; extern cgiFormResultType cgiFormFileOpen( char *name, cgiFilePtr *cfpp); extern cgiFormResultType cgiFormFileRead( cgiFilePtr cfp, char *buffer, int bufferSize, int *gotP); extern cgiFormResultType cgiFormFileClose( cgiFilePtr cfp); extern cgiFormResultType cgiCookieString( char *name, char *result, int max); extern cgiFormResultType cgiCookieInteger( char *name, int *result, int defaultV); cgiFormResultType cgiCookies( char ***ptrToStringArray); /* path can be null or empty in which case a path of / (entire site) is set. domain can be a single web site; if it is an entire domain, such as 'boutell.com', it should begin with a dot: '.boutell.com' */ extern void cgiHeaderCookieSetString(char *name, char *value, int secondsToLive, char *path, char *domain); extern void cgiHeaderCookieSetInteger(char *name, int value, int secondsToLive, char *path, char *domain); extern void cgiHeaderLocation(char *redirectUrl); extern void cgiHeaderStatus(int status, char *statusMessage); extern void cgiHeaderContentType(char *mimeType); typedef enum { cgiEnvironmentIO, cgiEnvironmentMemory, cgiEnvironmentSuccess, cgiEnvironmentWrongVersion } cgiEnvironmentResultType; extern cgiEnvironmentResultType cgiWriteEnvironment(char *filename); extern cgiEnvironmentResultType cgiReadEnvironment(char *filename); extern int cgiMain(); extern cgiFormResultType cgiFormEntries( char ***ptrToStringArray); /* Output string with the <, &, and > characters HTML-escaped. 's' is null-terminated. Returns cgiFormIO in the event of error, cgiFormSuccess otherwise. */ cgiFormResultType cgiHtmlEscape(char *s); /* Output data with the <, &, and > characters HTML-escaped. 'data' is not null-terminated; 'len' is the number of bytes in 'data'. Returns cgiFormIO in the event of error, cgiFormSuccess otherwise. */ cgiFormResultType cgiHtmlEscapeData(char *data, int len); /* Output string 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'. 's' is null-terminated. Returns cgiFormIO in the event of error, cgiFormSuccess otherwise. */ cgiFormResultType cgiValueEscape(char *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); #endif /* CGI_C */ cgic205/cgic.html0100644000031000004540000031663510146160147013021 0ustar boutelldev cgic: an ANSI C library for CGI Programming

cgic 2.05: an ANSI C library for CGI Programming

By Thomas Boutell

The LATEST documentation is available here. Check often for new releases.
IMPORTANT NOTICES:

If you have CGIC 1.05 or earlier, you should upgrade to CGIC 1.07, or to CGIC 2.02 or better, in order to obtain important security fixes.

If you have CGIC 2.0 or CGIC 2.01 and you use the cgiCookie routines, you should upgrade to CGIC 2.02 or better, in order to obtain important security fixes.

Table of Contents

Credits and License Terms

cgic can be used free of charge, provided that a credit notice is provided online. Alternatively, a nonexclusive Commercial License can be purchased, which grants the right to use cgic without a public credit notice.

Please see the file license.txt for the details of the Basic License and Commercial License, including ordering information for the Commercial License.

Thanks are due to Robert Gustavsson, Ken Holervich, Bob Nestor, Jon Ribbens, Thomas Strangert, Wu Yongwei, and other CGIC users who have corresponded over the years. Although the implementation of multipart/form-data file upload support in CGIC 2.x is my own, I particularly wish to thank those who submitted their own implementations of this feature.

How to Get Support

STOP! READ THIS FIRST! REALLY!

Are you getting a "server error," indicating that your web server "cannot allow POST to this URL," or a similar message? YOU MUST CONFIGURE YOUR WEB SERVER TO ALLOW CGI PROGRAMS, AND YOU MUST INSTALL CGI PROGRAMS IN THE LOCATION (OR WITH THE EXTENSION) THAT YOUR WEB SERVER EXPECTS TO SEE. Please don't send me email about this, unless you wish me to configure your web server for you; I can certainly do that for $50/hr, but you can probably straighten this out yourself or have your web server administrator do it.

Free Support

Please submit support inquiries about CGIC via our contact page. Please note that we receive a large volume of inquiries and cannot always respond personally. Sometimes the response must take the form of an eventual new release or an addition to a FAQ or other document, as opposed to an detailed individual response.

Hourly Support

Those requiring support in detail may arrange for direct support from the author, Thomas Boutell, at the rate of $50/hr, billed directly by credit card. To make arrangements, contact us via our our secure message page. To avoid delay, be sure to specifically mention that you wish to purchase CGIC support at the hourly rate above.

What's new in version 2.05?

Uploaded files properly closed; corrects a resource leak and enables file uploads to work properly on platforms with particular file locking semantics.

What's new in version 2.04?

Documentation fixes: the cgiHtmlEscape, cgiHtmlEscapeData, cgiValueEscape, and cgiValueEscapeData routines were named incorrectly in the manual. No code changes in version 2.04.

What's new in version 2.03?

  • Support for setting cookies has been reimplemented. The new code closely follows the actual practice of web sites that successfully use cookies, rather than attempting to implement the specification. The new code can successfully set more than one cookie at a time in typical web browsers.

What's new in version 2.02?

  • In CGIC 2.0 and 2.01, if the HTTP_COOKIE environment variable was exactly equal to the name of a cookie requested with cgiCookieString, with no value or equal sign or other characters present, a buffer overrun could take place. This was not normal behavior and it is unknown whether any actual web server would allow it to occur, however we have of course released a patch to correct it. Thanks to Nicolas Tomadakis.
  • cgiCookieString returned cgiFormTruncated when cgiFormSuccess would be appropriate. Fixed; thanks to Mathieu Villeneuve-Belair.
  • Cookies are now set using a simpler Set-Cookie: header, and with one header line per cookie, based on data collected by Chunfu Lai.
  • Memory leaks in cgiReadEnvironment fixed by Merezko Oleg. These memory leaks were not experienced in a normal CGI situation, only when reading a saved CGI environment.

What's new in version 2.01?

  • Makefile supports "make install"
  • Compiles without warnings under both C and C++ with strict warnings and strict ANSI compliance enabled
  • Builds out of the box on Windows (#include <fcntl.h> was needed)
  • Rare problem in cgiReadEnvironment corrected; no impact on normal CGI operations
  • cgiCookieString now sets the result to an empty string when returning cgiFormNotFound
  • Minor code cleanups

What's new in version 2.0?

1. CGIC 2.0 provides support for file upload fields. User-uploaded files are kept in temporary files, to avoid the use of excessive swap space (Solaris users may wish to change the cgicTempDir macro in cgic.c before compiling). The cgiFormFileName, cgiFormFileContentType, cgiFormFileSize, cgiFormFileOpen, cgiFormFileRead, and cgiFormFileClose functions provide a complete interface to this new functionality. Remember, the enctype attribute of the form tag must be set to multipart/form-data when <input type="file"> tags are used.

2. CGIC 2.0 provides support for setting and examining cookies (persistent data storage on the browser side). The cgiCookieString, and cgiCookieInteger and cgiCookies functions retrieve cookies. The cgiHeaderCookieSetString and cgiHeaderCookieSetInteger functions set cookies.

3. CGIC 2.0 offers a convenient way to retrieve a list of all form fields. The new cgiFormEntries function performs this operation.

4. CGIC 2.0 provides convenience functions to correctly escape text before outputting it as part of HTML, or as part of the value of a tag attribute, such as the HREF or VALUE attribute. See cgiHtmlEscape, cgiHtmlEscapeData, cgiValueEscape and cgiValueEscapeData.

5. Users have often asked the correct way to determine which submit button was clicked. This could always be accomplished in previous versions, but CGIC 2.0 also provides cgiFormSubmitClicked, a convenient alternate label for the cgiFormCheckboxSingle function.

What's new in version 1.07?

A problem with the cgiFormString and related functions has been corrected. These functions were previously incorrectly returning cgiFormTruncated in cases where the returned string fit the buffer exactly.

What's new in version 1.06?

1. A potentially significant buffer overflow problem has been corrected. Jon Ribbens correctly pointed out to me (and to the Internet's bugtraq mailing list) that the cgiFormEntryString function, which is used directly or indirectly by almost all CGIC programs, can potentially write past the buffer passed to it by the programmer. This bug has been corrected. Upgrading to version 1.06 is strongly recommended.

2. The function cgiSaferSystem() has been removed entirely. This function escaped only a few metacharacters, while most shells have many, and there was no way to account for the many different operating system shells that might be in use on different operating systems. Since this led to a false sense of security, the function has been removed. It is our recommendation that user input should never be passed directly on the command line unless it has been carefully shown to contain only characters regarded as safe and appropriate by the programmer. Even then, it is better to design your utilities to accept their input from standard input rather than the command line.

What's new in version 1.05?

Non-exclusive commercial license fee reduced to $200.

What's new in version 1.04?

For consistency with other packages, the standard Makefile now produces a true library for cgic (libcgic.a).

What's new in version 1.03?

Version 1.03 sends line feeds only (ascii 10) to end Content-type:, Status:, and other HTTP protocol output lines, instead of CR/LF sequences. The standard specifies CR/LF. Unfortunately, too many servers reject CR/LF to make implementation of that standard practical. No server tested ever rejects LF alone in this context.

What's new in version 1.02?

Version 1.02 corrects bugs in previous versions:
  • cgiFormDoubleBounded specified its arguments in the wrong order, with surprising results. This bug has been corrected.
  • Many small changes have been made to increase compatibility. cgic now compiles with no warnings under the compilers available at boutell.com.

What's new in version 1.01?

Version 1.01 adds no major functionality but corrects significant bugs and incompatibilities:
  • cgiFormInteger, cgiFormIntegerBounded, cgiFormDouble and cgiFormDoubleBounded now accept negative numbers properly. They also accept positive numbers with an explicit + sign.
  • Hex values containing the digit 9 are now properly decoded.
  • cgiFormString now represents each newline as a single line feed (ascii 10 decimal) as described in the documentation, not a carriage return (ascii 13 decimal) as in version 1.0. The latter approach pleased no one.
  • cgiFormString and cgiFormStringNoNewlines no longer erroneously return cgiFormEmpty in place of cgiFormSuccess.
  • The main() function of cgic now flushes standard output and sleeps for one second before exiting in order to inhibit problems with the completion of I/O on some platforms. This was not a cgic bug per se, but has been reported as a common problem with CGI when used with the CERN server. This change should improve compatibility.
  • The single selection example in the testform.html example now works properly. This was an error in the form itself, not cgic.
  • cgiRemoteUser and cgiRemoteIdent are now documented accurately. They were reversed earlier.

What is cgic?

cgic is an ANSI C-language library for the creation of CGI-based World Wide Web applications. For basic information about the CGI standard, see the CGI documentation at NCSA.

cgic performs the following tasks:

  • Parses form data, correcting for defective and/or inconsistent browsers
  • Transparently accepts both GET and POST form data
  • Accepts uploaded files as well as regular form fields
  • Provides functions to set and retrieve "cookies" (browser-side persistent information)
  • Handles line breaks in form fields in a consistent manner
  • Provides string, integer, floating-point, and single- and multiple-choice functions to retrieve form data
  • Provides bounds checking for numeric fields
  • Loads CGI environment variables into C strings which are always non-null
  • Provides a way to capture CGI situations for replay in a debugging environment, including file uploads and cookies

cgic is compatible with any CGI-compliant server environment, and compiles without modification in Posix/Unix/Linux and Windows environments.

Obtaining cgic

cgic is distributed via the web in two forms: as a Windows-compatible .ZIP file, and as a gzipped tar file. Most users of Windows and related operating systems have access to 'unzip' or 'pkunzip'. All modern Unix systems come with 'gunzip' and 'tar' as standard equipment, and gzip/gunzip is not difficult to find if yours does not. Versions of these programs for other operating systems are widely available if you do not already have them.

Important: to use cgic, you will need an ANSI-standard C compiler. Under Unix, just obtain and use gcc. Most Unix systems have standardiszed on gcc. Users of Windows operating systems should not have ANSI C-related problems as all of the popular compilers follow the ANSI standard.

Note for Windows Programmers: you must use a modern 32-bit compiler. Visual C++ 2.0 or higher, Borland C++ and the mingw32 gcc compiler are all appropriate, as is cygwin. Do NOT use an ancient 16-bit DOS executable compiler, please.

What Operating System Does Your WEB SERVER Run?

Remember, the computer on your desk is usually NOT your web server. Compiling a Windows console executable will not give you a CGI program that can be installed on a Linux-based server.
Your web browser should inquire whether to save the file to disk when you select one of the links below. Under Unix and compatible operating systems, save it, then issue the following commands to unpack it:
gunzip cgic205.tar.gz
tar -xf cgic205.tar
This should produce the subdirectory 'cgic205', which will contain the complete cgic distribution for version 2.05, including a copy of this documentation in the file cgic.html.

Under Windows and compatible operating systems, save it, open a console ("DOS") window, and issue the following commands to unpack it:

unzip /d cgic205.zip
Or use the unzip utility of your choice.

This command also produces the subdirectory 'cgic205', which will contain the complete cgic distribution for version 2.0, including a copy of this documentation in the file cgic.html.

cgic is available via the web from www.boutell.com:

Building cgic: a sample application

The sample application 'cgictest.c' is provided as part of the cgic distribution. This CGI program displays an input form, accepts a submission, and then displays what was submitted. In the process essentially all of cgic's features are tested.

On a Unix system, you can build cgictest simply by typing 'make cgictest.cgi'. cgic.c and cgictest.c will be compiled and linked together to produce the cgictest application. Under non-Unix operating systems, you will need to create and compile an appropriate project containing the files cgic.c and cgictest.c.

IMPORTANT: after compiling cgictest.cgi, you will need to place it in a location on your server system which is designated by your server administrator as an appropriate location for CGI scripts. Some servers are configured to recognize any file ending in .cgi as a CGI program when it is found in any subdirectory of the server's web space, but this is not always the case! The right locations for CGI programs vary greatly from one server to another. Resolving this issue is between you, your web server administrator, and your web server documentation. Before submitting a bug report for cgic, make certain that the CGI example programs which came with your server do work for you. Otherwise it is very likely that you have a server configuration problem.

Once you have moved cgictest.cgi (or cgictest.exe, under Windows) to an appropriate cgi directory, use the web browser of your choice to access the URL at which you have installed it (for instance, www.mysite.com/cgi-bin/cgictest.cgi). Fill out the various fields in any manner you wish, then select the SUBMIT button.

If all goes well, cgictest.cgi will respond with a page which indicates the various settings you submitted. If not, please reread the section above regarding the correct location in which to install your CGI program on your web server.

What to do if it won't compile

  • Are you using Visual C++ or Borland C++? Did you forget to add cgic.c to your project?
  • Make sure you are using an ANSI C or C++ compiler. (All of the Windows compilers are ANSI C compliant.)
If none of the above proves effective, please see the section regarding support.

How to write a cgic application

Note: All cgic applications must be linked to the cgic.c module itself. How to do this depends on your operating system; under Unix, just use the provided Makefile as an example.

Since all CGI applications must perform certain initial tasks, such as parsing form data and examining environment variables, the cgic library provides its own main() function. When you write applications that use cgic, you will begin your own programs by writing a cgiMain() function, which cgic will invoke when the initial cgi work has been successfully completed. Your program must also be sure to #include the file cgic.h.

Important: if you write your own main() function, your program will not link properly. Your own code should begin with cgiMain(). The library provides main() for you. (Those who prefer different behavior can easily modify cgic.c.)

Consider the cgiMain function of cgictest.c:

int cgiMain() {
#ifdef DEBUG
  LoadEnvironment();
#endif /* DEBUG */
  /* Load a previously saved CGI scenario if that button
    has been pressed. */
  if (cgiFormSubmitClicked("loadenvironment") == cgiFormSuccess) {
    LoadEnvironment();
  }
  /* Set any new cookie requested. Must be done *before*
    outputting the content type. */
  CookieSet();
  /* Send the content type, letting the browser know this is HTML */
  cgiHeaderContentType("text/html");
  /* Top of the page */
  fprintf(cgiOut, "<HTML><HEAD>\n");
  fprintf(cgiOut, "<TITLE>cgic test</TITLE></HEAD>\n");
  fprintf(cgiOut, "<BODY><H1>cgic test</H1>\n");
  /* If a submit button has already been clicked, act on the 
    submission of the form. */
  if ((cgiFormSubmitClicked("testcgic") == cgiFormSuccess) ||
    cgiFormSubmitClicked("saveenvironment") == cgiFormSuccess)
  {
    HandleSubmit();
    fprintf(cgiOut, "<hr>\n");
  }
  /* Now show the form */
  ShowForm();
  /* Finish up the page */
  fprintf(cgiOut, "</BODY></HTML>\n");
  return 0;
}
Note the DEBUG #ifdef. If DEBUG is defined at compile time, either by inserting the line "#define DEBUG 1" into the program or by setting it in the Makefile or other development environment, then the LoadEnvironment function is invoked. This function calls cgiReadEnvironment() to restore a captured CGI environment for debugging purposes. See also the discussion of the capture program, which is provided for use in CGI debugging. Because this is a test program, the cgiFormSubmitClicked function is also called to check for the use of a button that requests the reloading of a saved CGI environment. A completed CGI program typically would never allow the end user to make that decision.

Setting Cookies

Next, one of the cgiHeader functions should be called. This particular program demonstrates many features, including the setting of cookies. If the programmer wishes to set a cookie, the cookie-setting function must be called first, before other headers are output. This is done by the CookieSet() function of cgictest.c:
void CookieSet()
{
  char cname[1024];
  char cvalue[1024];
  /* Must set cookies BEFORE calling 
    cgiHeaderContentType */
  cgiFormString("cname", cname, sizeof(cname));  
  cgiFormString("cvalue", cvalue, sizeof(cvalue));  
  if (strlen(cname)) {
    /* Cookie lives for one day (or until 
      browser chooses to get rid of it, which 
      may be immediately), and applies only to 
      this script on this site. */  
    cgiHeaderCookieSetString(cname, cvalue,
      86400, cgiScriptName, cgiServerName);
  }
}
Since this is a test program, the cgiFormString function is used to fetch the name and value from the form previously filled in by the user. Normally, cookie names and values are chosen to meet the needs of the programmer and provide a means of identifying the same user again later.

The cgiHeaderCookieSetString function sets the cookie by requesting that the web browser store it. There is never any guarantee that this will happen! Many browsers reject cookies completely; others do not necessarily keep them as long as requested or return them with their values intact. Always code defensively when using cookies.

The cname and cvalue parameters are of course the namd and value for the cookie. The third argument is the time, in seconds, that the cookie should "live" on the browser side before it expires; in this case it has been set to 86,400 seconds, which is exactly one day. The browser may or may not respect this setting, as with everything else about cookies.

The fourth argument identifies the "path" within the web site for which the cookie is considered valid. A cookie that should be sent back for every access to the site should be set with a path of /. In this case the cookie is relevant only to the CGI program itself, so cgiScriptName (the URL of the CGI program, not including the domain name) is sent. Similarly, a cookie can be considered relevant to a single web site or to an entire domain, such as www.boutell.com or the entire .boutell.com domain. In this case, the current site on which the program is running is the only relevant site, so cgiServerName is used as the domain.

Outputting the Content Type Header

Next, cgiHeaderContentType() is called to indicate the MIME type of the document being output, in this case "text/html" (a normal HTML document). A few other common MIME types are "image/gif", "image/jpeg" and "audio/wav".

Note that cgiHeaderStatus() or cgiHeaderLocation() could have been invoked instead to output an error code or redirect the request to a different URL. Only one of the cgiHeader functions should be called in a single execution of the program.

Important: one of the cgiHeader functions, usually cgiHeaderContentType(), must be invoked before outputting any other response to the user. Otherwise, the result will not be a valid document and the browser's behavior will be unpredictable. You may, of course, output your own ContentType and other header information to cgiOut if you prefer. The cgiHeader functions are provided as a convenience.

Handling Form Submissions

Like many CGI programs, cgictest makes decisions about the way it should behave based on whether various submit buttons have been clicked. When either the testcgic or saveenvironment button is present, cgictest invokes the HandleSubmit function, which invokes additional functions to handle various parts of the form:
void HandleSubmit()
{
  Name();
  Address();
  Hungry();
  Temperature();
  Frogs();
  Color();
  Flavors();
  NonExButtons();
  RadioButtons();
  File();
  Entries();
  Cookies();
  /* The saveenvironment button, in addition to submitting 
    the form, also saves the resulting CGI scenario to 
    disk for later replay with the 'load saved environment' 
    button. */
  if (cgiFormSubmitClicked("saveenvironment") == cgiFormSuccess) {
    SaveEnvironment();
  }
}

Handling Text Input

The Name() function of cgictest is shown below, in its simplest possible form:
void Name() {
        char name[81];
        cgiFormStringNoNewlines("name", name, 81);
        fprintf(cgiOut, "Name: ");
        cgicHtmlEscape(name);
        fprintf(cgiOut, "
\n"); }
The purpose of this function is to retrieve and display the name that was input by the user. Since the programmer has decided that names should be permitted to have up to 80 characters, a buffer of 81 characters has been declared (allowing for the final null character). The cgiFormStringNoNewlines() function is then invoked to retrieve the name and ensure that carriage returns are not present in the name (despite the incorrect behavior of some web browsers). The first argument is the name of the input field in the form, the second argument is the buffer to which the data should be copied, and the third argument is the size of the buffer. cgic will never write beyond the size of the buffer, and will always provide a null-terminated string in response; if the buffer is too small, the string will be shortened. If this is not acceptable, the cgiFormStringSpaceNeeded() function can be used to check the amount of space needed; the return value of cgiFormStringNoNewlines() can also be checked to determine whether truncation occurred. See the full description of cgiFormStringNoNewlines().

Handling Output

Note that Name() writes its HTML output to cgiOut, not to stdout.

The actual name submitted by the user may or may not contain characters that have special meaning in HTML, specifically the the <, >, and & characters. The cgiHtmlEscape function is used to output the user-entered name with any occurrences of these characters correctly escaped as &lt;, &gt;, and &amp;.

Important: cgiOut is normally equivalent to stdout, and there is no performance penalty for using it. It is recommended that you write output to cgiOut to ensure compatibility with modified versions of the cgic library for special environments that do not provide stdin and stdout for each cgi connection.

Note that, for text input areas in which carriage returns are desired, the function cgiFormString should be used instead. cgiFormString ensures that line breaks are always represented by a single carriage return (ascii decimal 13), making life easier for the programmer. See the source code to the Address() function of cgictest.c for an example.

Handling Single Checkboxes

Consider the Hungry() function, which determines whether the user has selected the "hungry" checkbox:
void Hungry() {
        if (cgiFormCheckboxSingle("hungry") == cgiFormSuccess) {
                fprintf(cgiOut, "I'm Hungry!<BR>\n");
        } else {
                fprintf(cgiOut, "I'm Not Hungry!<BR>\n");
        }
}
This function takes advantage of the cgiFormCheckboxSingle() function, which determines whether a single checkbox has been selected. cgiFormCheckboxSingle() accepts the name attribute of the checkbox as its sole argument and returns cgiFormSuccess if the checkbox is selected, or cgiFormNotFound if it is not. If multiple checkboxes with the same name are in use, consider the cgiFormCheckboxMultiple() and cgiFormStringMultiple() functions.

Handling Numeric Input

Now consider the Temperature() function, which retrieves a temperature in degrees (a floating-point value) and ensures that it lies within particular bounds:
void Temperature() {
        double temperature;
        cgiFormDoubleBounded("temperature", &temperature, 80.0, 120.0, 98.6);
        fprintf(cgiOut, "My temperature is %f.<BR>\n", temperature);
}
The temperature is retrieved by the function cgiFormDoubleBounded(). The first argument is the name of the temperature input field in the form; the second argument points to the address of the variable that will contain the result. The next two arguments are the lower and upper bounds, respectively. The final argument is the default value to be returned if the user did not submit a value.

This function always retrieves a reasonable value within the specified bounds; values above or below bounds are constrained to fit the bounds. However, the return value of cgiFormDoubleBounded can be checked to make sure the actual user entry was in bounds, not blank, and so forth; see the description of cgiFormDoubleBounded() for more details. If bounds checking is not desired, consider using cgiFormDouble() instead.

Note that, for integer input, the functions cgiFormInteger and cgiFormIntegerBounded are available. The behavior of these functions is similar to that of their floating-point counterparts above.

Handling Single-Choice Input

The <SELECT> tag of HTML is used to provide the user with several choices. Radio buttons and checkboxes can also be used when the number of choices is relatively small. Consider the Color() function of cgictest.c:
char *colors[] = {
        "Red",
        "Green",
        "Blue"
};

void Color() {
        int colorChoice;
        cgiFormSelectSingle("colors", colors, 3, &colorChoice, 0);
        fprintf(cgiOut, "I am: %s<BR>\n", colors[colorChoice]);
}
This function determines which of several colors the user chose from a <SELECT> list in the form. An array of colors is declared; the cgiFormSelectSingle() function is then invoked to determine which, if any, of those choices was selected. The first argument indicates the name of the input field in the form. The second argument points to the list of acceptable colors. The third argument indicates the number of entries in the color array. The fourth argument points to the variable which will accept the chosen color, and the last argument indicates the index of the default value to be set if no selection was submitted by the browser.

cgiFormSelectSingle() will always indicate a reasonable selection value. However, if the programmer wishes to know for certain that a value was actually submitted, that the value submitted was a legal response, and so on, the return value of cgiFormSelectSingle() can be consulted. See the full description of cgiFormSelectSingle() for more information.

Note that radio button groups and <SELECT> lists can both be handled by this function. If you are processing radio button groups, you may prefer to invoke cgiFormRadio(), which functions identically.

"What if I won't know the acceptable choices at runtime?"

If the acceptable choices aren't known until runtime, one can simply load the choices from disk. But if the acceptable choices aren't fixed at all (consider a list of country names; new names may be added to the form at any time and it is inconvenient to also update program code or a separate list of countries), simply invoke cgiFormStringNoNewlines() instead to retrieve the string directly. Keep in mind that, if you do so, validating the response to make sure it is safe and legitimate becomes a problem for your own program to solve. The advantage of cgiFormSelectSingle() is that invalid responses are never returned.

To handle multiple-selection <SELECT> lists and groups of checkboxes with the same name, see the discussion of the NonExButtons() function of cgictest.c, immediately below.

Handling Multiple-Choice Input

Consider the first half of the NonExButtons() function of cgictest.c:
char *votes[] = {
  "A",
  "B",
  "C",
  "D"
};

void NonExButtons() {
  int voteChoices[4];
  int i;
  int result;  
  int invalid;

  char **responses;

  /* Method #1: check for valid votes. This is a good idea,
    since votes for nonexistent candidates should probably
    be discounted... */
  fprintf(cgiOut, "Votes (method 1):<BR>\n");
  result = cgiFormCheckboxMultiple("vote", votes, 4, 
    voteChoices, &invalid);
  if (result == cgiFormNotFound) {
    fprintf(cgiOut, "I hate them all!<p>\n");
  } else {  
    fprintf(cgiOut, "My preferred candidates are:\n");
    fprintf(cgiOut, "<ul>\n");
    for (i=0; (i < 4); i++) {
      if (voteChoices[i]) {
        fprintf(cgiOut, "<li>%s\n", votes[i]);
      }
    }
    fprintf(cgiOut, "</ul>\n");
  }
This function takes advantage of cgiFormCheckboxMultiple(), which is used to identify one or more selected checkboxes with the same name. This function performs identically to cgiFormSelectMultiple(). That is, <SELECT> tags with the MULTIPLE attribute are handled just like a group of several checkboxes with the same name.

The first argument to cgiFormCheckboxMultiple() is the name given to all checkbox input fields in the group. The second argument points to an array of legitimate values; these should correspond to the VALUE attributes of the checkboxes (or OPTION tags in a <SELECT> list). The third argument indicates the number of entries in the array of legitimate values. The fourth argument points to an array of integers with the same number of entries as the array of legitimate values; each entry will be set true if that checkbox or option was selected, false otherwise.

The last argument points to an integer which will be set to the number of invalid responses (responses not in the array of valid responses) that were submitted. If this value is not of interest, the last argument may be a null pointer (0).

Note that the return value of cgiFormCheckboxMultiple is inspected to determine whether any choices at all were set. See the full description of cgiFormCheckboxMultiple for other possible return values.

"What if I won't know the acceptable choices at runtime?"

If the acceptable choices aren't known until runtime, one can simply load the choices from disk. But if the acceptable choices aren't fixed at all (consider a list of ice cream flavors; new names may be added to the form at any time and it is inconvenient to also update program code or a separate list of countries), a more dynamic approach is needed. Consider the second half of the NonExButtons() function of cgictest.c:

  /* Method #2: get all the names voted for and trust them.
    This is good if the form will change more often
    than the code and invented responses are not a danger
    or can be checked in some other way. */
  fprintf(cgiOut, "Votes (method 2):<BR>\n");
  result = cgiFormStringMultiple("vote", &responses);
  if (result == cgiFormNotFound) {  
    fprintf(cgiOut, "I hate them all!<p>\n");
  } else {
    int i = 0;
    fprintf(cgiOut, "My preferred candidates are:\n");
    fprintf(cgiOut, "<ul>\n");
    while (responses[i]) {
      fprintf(cgiOut, "<li>%s\n", responses[i]);
      i++;
    }
    fprintf(cgiOut, "</ul>\n");
  }
  /* We must be sure to free the string array or a memory
    leak will occur. Simply calling free() would free
    the array but not the individual strings. The
    function cgiStringArrayFree() does the job completely. */  
  cgiStringArrayFree(responses);
}
This code excerpt demonstrates an alternate means of retrieving a list of choices. The function cgiFormStringMultiple() is used to retrieve an array consisting of all the strings submitted for with a particular input field name. This works both for <SELECT> tags with the MULTIPLE attribute and for groups of checkboxes with the same name.

The first argument to cgiFormStringMultiple() is the name of the input field or group of input fields in question. The second argument should be the address of a pointer to a pointer to a string, which isn't as bad as it sounds. Consider the following simple call of the function:

/* An array of strings; each C string is an array of characters */
char **responses; 

cgiFormStringMultiple("vote", &responses);
"How do I know how many responses there are?"

After the call, the last entry in the string array will be a null pointer. Thus the simple loop:

int i = 0;
while (responses[i]) {
  /* Do something with the string responses[i] */
  i++;
}
can be used to walk through the array until the last entry is encountered.

Important: the cgiFormStringMultiple function returns a pointer to allocated memory. Your code should not modify the strings in the responses array or the responses array itself; if modification is needed, the strings should be copied. When your code is done examining the responses array, you MUST call cgiStringArrayFree() with the array as an argument to free the memory associated with the array. Otherwise, the memory will not be available again until the program exists. Don't just call the free() function; if you do, the individual strings will not be freed.

Accessing Uploaded Files

CGIC provides functions to access files that have been uploaded as part of a form submission. IMPORTANT: you MUST set the enctype attribute of your form tag to multipart/form-data for this feature to work! For an example, see the ShowForm function of cgictest.c, examined below.

The File function of cgictest.c takes care of receiving uploaded files:

void File()
{
  cgiFilePtr file;
  char name[1024];
  char contentType[1024];
  char buffer[1024];
  int size;
  int got;
  if (cgiFormFileName("file", name, sizeof(name)) != 
    cgiFormSuccess) 
  {
    printf("<p>No file was uploaded.<p>\n");
    return;
  } 
        fprintf(cgiOut, "The filename submitted was: ");
        cgiHtmlEscape(name);
        fprintf(cgiOut, "<p>\n");
        cgiFormFileSize("file", &size);
        fprintf(cgiOut, "The file size was: %d bytes<p>\n", size);
        cgiFormFileContentType("file", contentType, sizeof(contentType));
        fprintf(cgiOut, "The alleged content type of the file was: ");
        cgiHtmlEscape(contentType);
        fprintf(cgiOut, "<p>\n");
  fprintf(cgiOut, "Of course, this is only the claim the browser "
    "made when uploading the file. Much like the filename, "
    "it cannot be trusted.<p>\n");
  fprintf(cgiOut, "The file's contents are shown here:<p>\n");
  if (cgiFormFileOpen("file", &file) != cgiFormSuccess) {
    fprintf(cgiOut, "Could not open the file.<p>\n");
    return;
  }
  fprintf(cgiOut, "<pre>\n");
  while (cgiFormFileRead(file, buffer, sizeof(buffer), &got) ==
    cgiFormSuccess)
  {
    cgiHtmlEscapeData(buffer, got);
  }
  fprintf(cgiOut, "</pre>\n");
  cgiFormFileClose(file);
}
First, the File function checks to determine the filename that was submitted by the user. VERY IMPORTANT: this filename may or may not bear any relation to the real name of the file on the user's computer, may be deliberately manipulated with malicious intent, and should not be used for any purpose unless you have determined that its content is safe for your intended use and will not, at the very least, overwrite another file of importance to you, especially if you intend to use it as a file name on the server side. The cgic library itself does not use this file name for temporary storage.

If the cgiFormFileName function does not succeed, no file was uploaded.

Next, the cgiFormFileSize function is called to determine the size of the uploaded file, in bytes.

The File function then proceeds to query the content type of the uploaded file. Files uploaded by the user have their own content type information, which may be useful in determining whether the file is an image, HTML document, word processing document, or other type of file. However, as with the filename and any other claim made by the browser, this information should not be blindly trusted. The browser may upload a file with the name picture.jpg and the content type image/jpeg, but this does not guarantee that the actual file will contain a valid JPEG image suitable for display.

The content type submitted by the browser can be queried using the cgiFormFileContentType function.

Of course, CGIC also provides access to the actual uploded file. First, the programmer calls cgiFormFileOpen, passing the address of a cgiFilePtr object. If this function succeeds, the cgiFilePtr object becomes valid, and can be used in subsequent calls to cgiFormFileRead. Notice that the number of bytes read may be less than the number requested, in particular on the last successful call before cgiFormFileRead begins to return cgiFormEOF. When cgiFormFileRead no longer returns cgiFormSuccess, the programmer calls cgiFormFileClose to release the cgiFilePtr object.

The uploaded file data may contain anything, including binary data, null characters, and so on. The example program uses the cgiHtmlEscapeData function to output the data with any special characters that have meaning in HTML escaped. Most programs will save the uploaded information to a server-side file or database.

Fetching All Form Entries

From time to time, the programmer may not know the names of all form fields in advance. In such situations it is convenient to use the cgiFormEntries function. The Entries function of cgictest.c demonstrates the use of cgiFormEntries:
void Entries()
{
        char **array, **arrayStep;
        fprintf(cgiOut, "List of All Submitted Form Field Names:<p>\n");
        if (cgiFormEntries(&array) != cgiFormSuccess) {
                return;
        }
        arrayStep = array;
        fprintf(cgiOut, "<ul>\n");
        while (*arrayStep) {
                fprintf(cgiOut, "<li>");
                cgiHtmlEscape(*arrayStep);
                fprintf(cgiOut, "\n");
                arrayStep++;
        }
        fprintf(cgiOut, "</ul>\n");
        cgiStringArrayFree(array);
}
The cgiFormEntries function retrieves an array of form field names. This array consists of pointers to strings, with a final null pointer to mark the end of the list. The above code illustrates one way of looping through the returned strings. Note the final call to cgiStringArrayFree, which is essential in order to return the memory used to store the strings and the string array.

Retrieving Cookies

The Cookies function of cgictest.c displays a list of all cookies submitted by the browser with the current form submission, along with their values:
void Cookies()
{
  char **array, **arrayStep;
  char cname[1024], cvalue[1024];
  fprintf(cgiOut, "Cookies Submitted On This Call, With Values "
    "(Many Browsers NEVER Submit Cookies):<p>\n");
  if (cgiCookies(&array) != cgiFormSuccess) {
    return;
  }
  arrayStep = array;
  fprintf(cgiOut, "<table border=1>\n");
  fprintf(cgiOut, "<tr><th>Cookie<th>Value</tr>\n");
  while (*arrayStep) {
    char value[1024];
    fprintf(cgiOut, "<tr>");
    fprintf(cgiOut, "<td>");
    cgiHtmlEscape(*arrayStep);
    fprintf(cgiOut, "<td>");
    cgiCookieString(*arrayStep, value, sizeof(value));
    cgiHtmlEscape(value);
    fprintf(cgiOut, "\n");
    arrayStep++;
  }
  fprintf(cgiOut, "</table>\n");
  cgiFormString("cname", cname, sizeof(cname));  
  cgiFormString("cvalue", cvalue, sizeof(cvalue));  
  if (strlen(cname)) {
    fprintf(cgiOut, "New Cookie Set On This Call:<p>\n");
    fprintf(cgiOut, "Name: ");  
    cgiHtmlEscape(cname);
    fprintf(cgiOut, "Value: ");  
    cgiHtmlEscape(cvalue);
    fprintf(cgiOut, "<p>\n");
    fprintf(cgiOut, "If your browser accepts cookies "
      "(many do not), this new cookie should appear "
      "in the above list the next time the form is "
      "submitted.<p>\n"); 
  }
  cgiStringArrayFree(array);
}
VERY IMPORTANT: YOUR BROWSER MIGHT NOT SUBMIT COOKIES, EVER, REGARDLESS OF WHAT VALUES YOU ENTER INTO THE TEST FORM. Many, many browsers are configured not to accept or send cookies; others are configured to send them as little as possible to meet the bare minimum requirements for entry into popular sites. Users will often refuse your cookies; make sure your code still works in that situation!

The above code uses the cgiCookies function to retrieve a list of all currently set cookies as a null-terminated array of strings. The cgiCookieString function is then used to fetch the value associated with each cookie; this function works much like cgiFormString, discussed earlier. Note that a cookie set as a part of the current form submission process does not appear on this list immediately, as it has not yet been sent back by the browser. It should appear on future submissions, provided that the browser chooses to accept and resend the cookie at all.

Displaying a Form That Submits to the Current Program

CGI programmers often need to display HTML pages as part of the output of CGI programs; these HTML pages often contain forms which should submit fields back to the same program they came from. Provided that your web server is well-configured, this can be done conveniently using the cgiScriptName environment variable, as shown below. Here is the source code of the ShowForm function of cgictest.c:
void ShowForm()
{
  fprintf(cgiOut, "<!-- 2.0: multipart/form-data is required 
    "for file uploads. -->");
  fprintf(cgiOut, "<form method=\"POST\" "
    "enctype=\"multipart/form-data\" ");
  fprintf(cgiOut, "  action=\"");
  cgiValueEscape(cgiScriptName);
  fprintf(cgiOut, "\">\n");
  fprintf(cgiOut, "<p>\n");
  fprintf(cgiOut, "Text Field containing Plaintext\n");
  fprintf(cgiOut, "<p>\n");
  fprintf(cgiOut, "<input type=\"text\" name=\"name\">Your Name\n");
  fprintf(cgiOut, "<p>\n");
  fprintf(cgiOut, "Multiple-Line Text Field\n");
  fprintf(cgiOut, "<p>\n");
  fprintf(cgiOut, "<textarea NAME=\"address\" ROWS=4 COLS=40>\n");
  fprintf(cgiOut, "Default contents go here. \n");
  fprintf(cgiOut, "</textarea>\n");
  fprintf(cgiOut, "<p>\n");
  fprintf(cgiOut, "Checkbox\n");
  fprintf(cgiOut, "<p>\n");
  fprintf(cgiOut, "<input type=\"checkbox\" name=\"hungry\" checked>Hungry\n");
  fprintf(cgiOut, "<p>\n");
  fprintf(cgiOut, "Text Field containing a Numeric Value\n");
  fprintf(cgiOut, "<p>\n");
  fprintf(cgiOut, "<input type=\"text\" name=\"temperature\" value=\"98.6\">\n");
  fprintf(cgiOut, "Blood Temperature (80.0-120.0)\n");
  fprintf(cgiOut, "<p>\n");
  fprintf(cgiOut, "Text Field containing an Integer Value\n");
  fprintf(cgiOut, "<p>\n");
  fprintf(cgiOut, "<input type=\"text\" name=\"frogs\" value=\"1\">\n");
  fprintf(cgiOut, "Frogs Eaten\n");
  fprintf(cgiOut, "<p>\n");
  fprintf(cgiOut, "Single-SELECT\n");
  fprintf(cgiOut, "<br>\n");
  fprintf(cgiOut, "<select name=\"colors\">\n");
  fprintf(cgiOut, "<option value=\"Red\">Red\n");
  fprintf(cgiOut, "<option value=\"Green\">Green\n");
  fprintf(cgiOut, "<option value=\"Blue\">Blue\n");
  fprintf(cgiOut, "</select>\n");
  fprintf(cgiOut, "<br>\n");
  fprintf(cgiOut, "Multiple-SELECT\n");
  fprintf(cgiOut, "<br>\n");
  fprintf(cgiOut, "<select name=\"flavors\" multiple>\n");
  fprintf(cgiOut, "<option value=\"pistachio\">Pistachio\n");
  fprintf(cgiOut, "<option value=\"walnut\">Walnut\n");
  fprintf(cgiOut, "<option value=\"creme\">Creme\n");
  fprintf(cgiOut, "</select>\n");
  fprintf(cgiOut, "<p>Exclusive Radio Button Group: Age of "
    "Truck in Years\n");
  fprintf(cgiOut, "<input type=\"radio\" name=\"age\" "
    "value=\"1\">1\n");
  fprintf(cgiOut, "<input type=\"radio\" name=\"age\" "
    "value=\"2\">2\n");
  fprintf(cgiOut, "<input type=\"radio\" name=\"age\" "
    "value=\"3\" checked>3\n");
  fprintf(cgiOut, "<input type=\"radio\" name=\"age\" "
    "value=\"4\">4\n");
  fprintf(cgiOut, "<p>Nonexclusive Checkbox Group: "
    "Voting for Zero through Four Candidates\n");
  fprintf(cgiOut, "<input type=\"checkbox\" name=\"vote\" "
    "value=\"A\">A\n");
  fprintf(cgiOut, "<input type=\"checkbox\" name=\"vote\" "
    "value=\"B\">B\n");
  fprintf(cgiOut, "<input type=\"checkbox\" name=\"vote\" "
    "value=\"C\">C\n");
  fprintf(cgiOut, "<input type=\"checkbox\" name=\"vote\" "
    "value=\"D\">D\n");
  fprintf(cgiOut, "<p>File Upload:\n");
  fprintf(cgiOut, "<input type=\"file\" name=\"file\" "
    "value=\"\"> (Select A Local File)\n");
  fprintf(cgiOut, "<p>\n");
  fprintf(cgiOut, "<p>Set a Cookie<p>\n");
  fprintf(cgiOut, "<input name=\"cname\" "
    "value=\"\"> Cookie Name\n");
  fprintf(cgiOut, "<input name=\"cvalue\" "
    "value=\"\"> Cookie Value<p>\n");
  fprintf(cgiOut, "<input type=\"submit\" "
    "name=\"testcgic\" value=\"Submit Request\">\n");
  fprintf(cgiOut, "<input type=\"reset\" "
    "value=\"Reset Request\">\n");
  fprintf(cgiOut, "<p>Save the CGI Environment<p>\n");
  fprintf(cgiOut, "Pressing this button will submit the form, then "
    "save the CGI environment so that it can be replayed later "
    "by calling cgiReadEnvironment (in a debugger, for "
    "instance).<p>\n");
  fprintf(cgiOut, "<input type=\"submit\" name=\"saveenvironment\" "
    "value=\"Save Environment\">\n");
  fprintf(cgiOut, "</form>\n");
}
Note the use of enctype="multipart/form-data" in the FORM tag. This is absolutely required if the form will contain file upload fields, as in the above example. Most browsers will not even attempt file uploads without the presence of this attribute.

Examining CGI environment variables

The CGI standard specifies a number of environment variables which are set by the server. However, servers are somewhat unpredictable as to whether these variables will be null or point to empty strings when an environment variable is not set. Also, in order to allow the programmer to restore saved CGI environments, the cgic library needs have a way of insulating the programmer from the actual environment variables.

Instead of calling getenv() to determine the value of a variable such as HTTP_USER_AGENT (the browser software being used), always use the cgic copies of the environment variables, which are always valid C strings (they are never null, although they may point to an empty string). For instance, the cgic variable containing the name of the browser software is cgiUserAgent. The referring URL appears in the variable cgiReferrer.

How can I generate images from my cgic application?

cgic can be used in conjunction with the gd graphics library, which can produce GIF images on the fly.

The following short sample program hints at the possibilities:

#include "cgic.h"
#include "gd.h"

char *colors[] = {
  "red", "green", "blue"
};

#define colorsTotal 3

int cgiMain() {
  int colorChosen;
  gdImagePtr im;
  int r, g, b;
  /* Use gd to create an image */
  im = gdImageCreate(64, 64);
  r = gdImageColorAllocate(im, 255, 0, 0);  
  g = gdImageColorAllocate(im, 0, 255, 0);  
  b = gdImageColorAllocate(im, 0, 0, 255);  
  /* Now use cgic to find out what color the user requested */
  cgiFormSelectSingle("color", 3, &colorChosen, 0);  
  /* Now fill with the desired color */
  switch(colorChosen) {
    case 0:
    gdImageFill(im, 32, 32, r);
    break;
    case 1:
    gdImageFill(im, 32, 32, g);
    break;
    case 2:
    gdImageFill(im, 32, 32, b);
    break;
  }  
  /* Now output the image. Note the content type! */
  cgiHeaderContentType("image/gif");
  /* Send the image to cgiOut */
  gdImageGif(im, cgiOut);
  /* Free the gd image */
  gdImageDestroy(im);
  return 0;
}
Note that this program would need to be linked with both cgic.o and libgd.a. Often programs of this type respond to one cgiPathInfo value or set of form fields by returning an HTML page with an inline image reference that, in turn, generates a GIF image.

Debugging CGI applications: using capture

Debugging CGI applications can be a painful task. Since CGI applications run in a special environment created by the web server, it is difficult to execute them in a debugger. However, the cgic library provides a way of capturing "live" CGI environments to a file, and also provides a way to reload saved environments.

The provided program 'capture.c' can be used to capture CGI environments. Just change the first line of the cgiMain() function of capture.c to save the CGI environment to a filename appropriate on your system and type 'make capture'. Then place capture in your cgi directory and set the form action or other link you want to test to point to it. When the form submission or other link takes place, capture will write the CGI environment active at that time to the filename you specified in the source. The cgiReadEnvironment() function can then be invoked on the same filename at the beginning of the cgiMain() function of the application you want to test in order to restore the captured environment. You can then execute your program in the debugger of your choice, and it should perform exactly as it would have performed had it been launched by the actual web server, including file uploads, cookies and all other phenomena within the purview of cgic.

Important: Make sure you specify the full path, as the current working directory of a CGI script may not be what you think it is!

Even More Important: If you call getenv() yourself in your code, instead of using the provided cgic copies of the CGI environment variables, you will not get the values you expect when running with a saved CGI environment. Always use the cgic variables instead of calling getenv().

cgic function reference


cgiFormResultType cgiFormString( char *name, char *result, int max)
cgiFormString attempts to retrieve the string sent for the specified input field. The text will be copied into the buffer specified by result, up to but not exceeding max-1 bytes; a terminating null is then added to complete the string. Regardless of the newline format submitted by the browser, cgiFormString always encodes each newline as a single line feed (ascii decimal 10); as a result the final string may be slightly shorter than indicated by a call to cgiFormStringSpaceNeeded but will never be longer. cgiFormString returns cgiFormSuccess if the string was successfully retrieved, cgiFormTruncated if the string was retrieved but was truncated to fit the buffer, cgiFormEmpty if the string was retrieved but was empty, and cgiFormNotFound if no such input field was submitted. In the last case, an empty string is copied to result.

cgiFormResultType cgiFormStringNoNewlines( char *name, char *result, int max)
cgiFormStringNoNewlines() is exactly equivalent to cgiFormString(), except that any carriage returns or line feeds that occur in the input will be stripped out. The use of this function is recommended for single-line text input fields, as some browsers will submit carriage returns and line feeds when they should not.

cgiFormResultType cgiFormStringSpaceNeeded( char *name, int *length)
cgiFormStringSpaceNeeded() is used to determine the length of the input text buffer needed to receive the contents of the specified input field. This is useful if the programmer wishes to allocate sufficient memory for input of arbitrary length. The actual length of the string retrieved by a subsequent call to cgiFormString() may be slightly shorter but will never be longer than *result. On success, cgiFormStringSpaceNeeded() sets the value pointed to by length to the number of bytes of data, including the terminating null, and returns cgiFormSuccess. If no value was submitted for the specified field, cgiFormStringSpaceNeeded sets the value pointed to by length to 1 and returns cgiFormNotFound. 1 is set to ensure space for an empty string (a single null character) if cgiFormString is called despite the return value.

cgiFormResultType cgiFormStringMultiple( char *name, char ***ptrToStringArray)
cgiFormStringMultiple is useful in the unusual case in which several input elements in the form have the same name and, for whatever reason, the programmer does not wish to use the checkbox, radio button and selection menu functions provided below. This is occasionally needed if the programmer cannot know in advance what values might appear in a multiple-selection list or group of checkboxes on a form. The value pointed to by result will be set to a pointer to an array of strings; the last entry in the array will be a null pointer. This array is allocated by the CGI library. Important: when done working with the array, you must call cgiStringArrayFree() with the array pointer as the argument. cgiFormStringMultiple() returns cgiFormSuccess if at least one occurrence of the name is found, cgiFormNotFound if no occurrences are found, or cgiFormMemory if not enough memory is available to allocate the array to be returned. In all cases except the last, ptrToStringArray is set to point to a valid array of strings, with the last element in the array being a null pointer; in the out-of-memory case ptrToStringArray is set to a null pointer.

cgiFormResultType cgiFormEntries( char ***ptrToStringArray)
cgiFormEntries is useful when the programmer cannot know the names of all relevant form fields in advance. The value pointed to by result will be set to a pointer to an array of strings; the last entry in the array will be a null pointer. This array is allocated by the CGI library. Important: when done working with the array, you must call cgiStringArrayFree() with the array pointer as the argument. cgiFormEntries() returns cgiFormSuccess except in the event of an out of memory error. On success, ptrToStringArray is set to point to a valid array of strings, with the last element in the array being a null pointer; in the out-of-memory case ptrToStringArray is set to a null pointer, and cgiFormOutOfMemory is returned.

void cgiStringArrayFree(char **stringArray)
cgiStringArrayFree() is used to free the memory associated with a string array created by cgiFormStringMultiple(), cgiFormEntries(), or cgiFormCookies().

cgiFormResultType cgiFormInteger( char *name, int *result, int defaultV)
cgiFormInteger() attempts to retrieve the integer sent for the specified input field. The value pointed to by result will be set to the value submitted. cgiFormInteger() returns cgiFormSuccess if the value was successfully retrieved, cgiFormEmpty if the value submitted is an empty string, cgiFormBadType if the value submitted is not an integer, and cgiFormNotFound if no such input field was submitted. In the last three cases, the value pointed to by result is set to the specified default.

cgiFormResultType cgiFormIntegerBounded( char *name, int *result, int min, int max, int defaultV)
cgiFormIntegerBounded() attempts to retrieve the integer sent for the specified input field, and constrains the result to be within the specified bounds. The value pointed to by result will be set to the value submitted. cgiFormIntegerBounded() returns cgiFormSuccess if the value was successfully retrieved, cgiFormConstrained if the value was out of bounds and result was adjusted accordingly, cgiFormEmpty if the value submitted is an empty string, cgiFormBadType if the value submitted is not an integer, and cgiFormNotFound if no such input field was submitted. In the last three cases, the value pointed to by result is set to the specified default.

cgiFormResultType cgiFormDouble( char *name, double *result, double defaultV)
cgiFormDouble attempts to retrieve the floating-point value sent for the specified input field. The value pointed to by result will be set to the value submitted. cgiFormDouble returns cgiFormSuccess if the value was successfully retrieved, cgiFormEmpty if the value submitted is an empty string, cgiFormBadType if the value submitted is not a number, and cgiFormNotFound if no such input field was submitted. In the last three cases, the value pointed to by result is set to the specified default.

cgiFormResultType cgiFormDoubleBounded( char *name, double *result, double min, double max, double defaultV)
cgiFormDoubleBounded() attempts to retrieve the floating-point value sent for the specified input field, and constrains the result to be within the specified bounds. The value pointed to by result will be set to the value submitted. cgiFormDoubleBounded() returns cgiFormSuccess if the value was successfully retrieved, cgiFormConstrained if the value was out of bounds and result was adjusted accordingly, cgiFormEmpty if the value submitted is an empty string, cgiFormBadType if the value submitted is not a number, and cgiFormNotFound if no such input field was submitted. In the last three cases, the value pointed to by result is set to the specified default.

cgiFormResultType cgiFormSelectSingle( char *name, char **choicesText, int choicesTotal, int *result, int defaultV)
cgiFormSelectSingle() retrieves the selection number associated with a <SELECT> element that does not allow multiple selections. name should identify the NAME attribute of the <SELECT> element. choicesText should point to an array of strings identifying each choice; choicesTotal should indicate the total number of choices. The value pointed to by result will be set to the position of the actual choice selected within the choicesText array, if any, or to the value of default, if no selection was submitted or an invalid selection was made. cgiFormSelectSingle() returns cgiFormSuccess if the value was successfully retrieved, cgiFormNotFound if no selection was submitted, and cgiFormNoSuchChoice if the selection does not match any of the possibilities in the choicesText array.
cgiFormResultType cgiFormSelectMultiple( char *name, char **choicesText, int choicesTotal, int *result, int *invalid)
cgiFormSelectMultiple() retrieves the selection numbers associated with a <SELECT> element that does allow multiple selections. name should identify the NAME attribute of the <SELECT> element. choicesText should point to an array of strings identifying each choice; choicesTotal should indicate the total number of choices. result should point to an array of integers with as many elements as there are strings in the choicesText array. For each choice in the choicesText array that is selected, the corresponding integer in the result array will be set to one; other entries in the result array will be set to zero. cgiFormSelectMultiple() returns cgiFormSuccess if at least one valid selection was successfully retrieved or cgiFormNotFound if no valid selections were submitted. The integer pointed to by invalid is set to the number of invalid selections that were submitted, which should be zero unless the form and the choicesText array do not agree.

cgiFormResultType cgiFormSubmitClicked( char *name)
It is often desirable to know whether a particular submit button was clicked, when multiple submit buttons with different name attributes exist. cgiFormSubmitClicked is an alternative name for the cgiFormCheckboxSingle function, which is suitable for testing whether a particular submit button was used.

cgiFormResultType cgiFormCheckboxSingle( char *name)
cgiFormCheckboxSingle determines whether the checkbox with the specified name is checked. cgiFormCheckboxSingle returns cgiFormSuccess if the button is checked, cgiFormNotFound if the checkbox is not checked. cgiFormCheckboxSingle is intended for single checkboxes with a unique name; see below for functions to deal with multiple checkboxes with the same name, and with radio buttons.

cgiFormResultType cgiFormCheckboxMultiple( char *name, char **valuesText, int valuesTotal, int *result, int *invalid)
cgiFormCheckboxMultiple() determines which checkboxes among a group of checkboxes with the same name are checked. This is distinct from radio buttons (see cgiFormRadio). valuesText should point to an array of strings identifying the VALUE attribute of each checkbox; valuesTotal should indicate the total number of checkboxes. result should point to an array of integers with as many elements as there are strings in the valuesText array. For each choice in the valuesText array that is selected, the corresponding integer in the result array will be set to one; other entries in the result array will be set to zero. cgiFormCheckboxMultiple returns cgiFormSuccess if at least one valid checkbox was checked or cgiFormNotFound if no valid checkboxes were checked. The integer pointed to by invalid is set to the number of invalid selections that were submitted, which should be zero unless the form and the valuesText array do not agree.

cgiFormResultType cgiFormRadio( char *name, char **valuesText, int valuesTotal, int *result, int defaultV)
cgiFormRadio() determines which, if any, of a group of radio boxes with the same name was selected. valuesText should point to an array of strings identifying the VALUE attribute of each radio box; valuesTotal should indicate the total number of radio boxes. The value pointed to by result will be set to the position of the actual choice selected within the valuesText array, if any, or to the value of default, if no radio box was checked or an invalid selection was made. cgiFormRadio() returns cgiFormSuccess if a checked radio box was found in the group, cgiFormNotFound if no box was checked, and cgiFormNoSuchChoice if the radio box submitted does not match any of the possibilities in the valuesText array.
cgiFormResultType cgiFormFileName( char *name, char *fileName, int max)
cgiFormFileName attempts to retrieve the file name uploaded by the user for the specified form input field of type file. NEVER, EVER TRUST THIS FILENAME TO BE REASONABLE AND SAFE FOR DIRECT USE ON THE SERVER SIDE. The text will be copied into the buffer specified by fileName, up to but not exceeding max-1 bytes; a terminating null is then added to complete the string. cgiFormFileName returns cgiFormSuccess if the string was successfully retrieved and was not empty, cgiFormNoFileName if the string was successfully retrieved but empty indicating that no file was uploaded, cgiFormTruncated if the string was retrieved but was truncated to fit the buffer, and cgiFormNotFound if no such input field was submitted. In the last case, an empty string is copied to result.
cgiFormResultType cgiFormFileSize( char *name, int *sizeP)
cgiFormFileSize attempts to retrieve the size, in bytes, of a file uploaded by the browser in response to the input field of type file specified by the name parameter. On success, the size is stored to *sizeP, and this function returns cgiFormSuccess. If the form field does not exist, this function returns cgiFormNotFound. If the form field exists but no file was uploaded, this function returns cgiFormNotAFile.
cgiFormResultType cgiFormFileContentType( char *name, char *contentType, int max)
cgiFormString attempts to retrieve the content name claimed by the user for the specified form input field of type file. THERE IS NO GUARANTEE THAT THE CONTENT TYPE WILL BE ACCURATE. The content type string will be copied into the buffer specified by contentType, up to but not exceeding max-1 bytes; a terminating null is then added to complete the string. cgiFormFileContentType returns cgiFormSuccess if the string was successfully retrieved and was not empty, cgiFormNoContentType if the string was successfully retrieved but empty indicating that no file was uploaded or the browser did not know the content type, cgiFormTruncated if the string was retrieved but was truncated to fit the buffer, and cgiFormNotFound if no such input field was submitted. In the last case, an empty string is copied to result.
cgiFormResultType cgiFormFileOpen( char *name, cgiFilePtr *cfpp)
cgiFormFileOpen attempts to open the actual uploaded file data for the specified form field of type file. Upon success, this function returns retrieve the content name claimed by the user for the specified form input field of type file. On success, this function sets *cfpp to a valid cgiFilePtr object for use with cgiFormSuccess. On failure, this function sets *cfpp to a null pointer, and returns cgiFormNotFound, cgiFormNotAFile, cgiFormMemory or cgiFormIO as appropriate.

See also cgiFormFileRead and cgiFormFileClose.

cgiFormResultType cgiFormFileRead( cgiFilePtr cfp, char *buffer, int bufferSize, int *gotP)
cgiFormFileRead attempts to read up to bufferSize bytes from a cgiFilePtr object previously opened with cgiFormFileOpen. If any data is successfully read, it is copied to buffer, and the number of bytes successfully read is stored to *gotP. This function returns cgiFormSuccess if any data is successfully read. At end of file, this function returns cgiFormEOF. In the event of an I/O error, this function returns cgiFormIO. If cfp is a null pointer, this function returns cgiFormOpenFailed.

See also cgiFormFileOpen and cgiFormFileClose.

cgiFormResultType cgiFormFileClose( cgiFilePtr cfp)
cgiFormFileClose closes a cgiFilePtr object previously opened with cgiFormFileOpen, freeing memory and other system resources. This function returns cgiFormSuccess unless cfp is null, in which case cgiFormOpenFailed is returned.

See also cgiFormFileOpen and cgiFormFileRead.

void cgiHeaderLocation(char *redirectUrl)
cgiHeaderLocation() should be called if the programmer wishes to redirect the user to a different URL. No futher output is needed in this case.

If you wish to set cookies, you must make your calls to cgiHeaderCookieSetString and cgiHeaderCookieSetInteger BEFORE invoking cgiHeaderLocation.

void cgiHeaderStatus(int status, char *statusMessage)
cgiHeaderStatus() should be called if the programmer wishes to output an HTTP error status code instead of a document. The status code is the first argument; the second argument is the status message to be displayed to the user.

If you wish to set cookies, you must make your calls to cgiHeaderCookieSetString and cgiHeaderCookieSetInteger BEFORE invoking cgiHeaderStatus.

void cgiHeaderContentType(char *mimeType)
cgiHeaderContentType() should be called if the programmer wishes to output a new document in response to the user's request. This is the normal case. The single argument is the MIME document type of the response; typical values are "text/html" for HTML documents, "text/plain" for plain ASCII without HTML tags, "image/gif" for a GIF image and "audio/basic" for .au-format audio.

If you wish to set cookies, you must make your calls to cgiHeaderCookieSetString and cgiHeaderCookieSetInteger BEFORE invoking cgiHeaderContentType.

void cgiHeaderCookieSetString(char *name, char *value, int secondsToLive, char *path, char *domain)
cgiHeaderCookieSetString() should be called when the programmer wishes to store a piece of information in the user's browser, so that the stored information is again presented to the server on subsequent accesses to the relevant site. The first argument is the name of the cookie to be stored; for best results in all browsers, use a short name without spaces or unusual punctuation. The second argument is the value of the cookie to be stored. Again, for best results, use a short string; it is recommended that cookies be used to store a unique identifier which is then used to look up more detailed information in a database on the server side. Attempts to store elaborate information on the browser side are much more likely to fail. The third argument is the number of seconds that the cookie should be kept by the browser; 86400 is a single full day, 365*86400 is roughly one year. The fourth argument is the partial URL of the web site within which the cookie is relevant. If the cookie should be sent to the server for every access to the entire site, set this argument to /. The final argument is the web site name or entire domain for which this cookie should be submitted; if you choose to have the cookie sent back for an entire domain, this argument must begin with a dot, such as .boutell.com. The cgic variables cgiServerName are convenient values for the fourth and fifth arguments. See also cgiHeaderCookieSetInteger, cgiCookieString, cgiCookieInteger and cgiCookies.

void cgiHeaderCookieSetInteger(char *name, int value, int secondsToLive, char *path, char *domain)
cgiHeaderCookieSetInteger() is identical to cgiHeaderCookieSetString, except that the value to be set is an integer rather than a string. See cgiHeaderCookieSetString for complete information.

cgiFormResultType cgiCookieString( char *name, char *result, int max)
cgiFormString attempts to retrieve the string sent for the specified cookie (browser-side persistent storage). The text will be copied into the buffer specified by result, up to but not exceeding max-1 bytes; a terminating null is then added to complete the string. cgiCookieString returns cgiFormSuccess if the string was successfully retrieved, cgiFormTruncated if the string was retrieved but was truncated to fit the buffer, cgiFormEmpty if the string was retrieved but was empty, and cgiFormNotFound if no such cookie was submitted. In the last case, an empty string is copied to result.

cgiFormResultType cgiCookieInteger( char *name, int *result, int defaultV) See also cgiCookieInteger, cgiCookies, cgiHeaderCookieSetInteger.
cgiCookieInteger() attempts to retrieve the integer sent for the specified cookie (browser-side persistent storage). The value pointed to by result will be set to the value submitted. cgiCookieInteger() returns cgiFormSuccess if the value was successfully retrieved, cgiFormEmpty if the value submitted is an empty string, cgiFormBadType if the value submitted is not an integer, and cgiFormNotFound if no such input field was submitted. In the last three cases, the value pointed to by result is set to the specified default. See also cgiCookieString, cgiCookies, cgiHeaderCookieSetInteger.

cgiFormResultType cgiCookies( char *name, char ***ptrToStringArray)
cgiCookies is useful when the programmer cannot know the names of all relevant cookies (browser-side persistent strings) in advance. The value pointed to by result will be set to a pointer to an array of strings; the last entry in the array will be a null pointer. This array is allocated by the CGI library. Important: when done working with the array, you must call cgiStringArrayFree() with the array pointer as the argument. cgiCookies() returns cgiFormSuccess except in the event of an out of memory error. On success, ptrToStringArray is set to point to a valid array of strings, with the last element in the array being a null pointer; in the out-of-memory case ptrToStringArray is set to a null pointer, and cgiFormOutOfMemory is returned.

cgiFormResultType cgiHtmlEscape(char *s)
cgiHtmlEscape() outputs the specified null-terminated string to cgiOut, escaping any <, &, and > characters encountered correctly so that they do not interfere with HTML markup. Returns cgiFormSuccess, or cgiFormIO in the event of an I/O error.



cgiFormResultType cgiHtmlEscapeData(char *data, int len)
cgiHtmlEscapeData() is identical to cgiHtmlEscape, except that the data is not null-terminated. This version of the function outputs len bytes. See cgiHtmlEscape for more information.

cgiFormResultType cgiValueEscape(char *s)
cgiValueEscape() outputs the specified null-terminated string to cgiOut, escaping any " characters encountered correctly so that they do not interfere with the quotation marks of HTML attribute values. This is useful when outputting a string as part of the value attribute of an input tag, or the href attribute of a link or form tag. This function returns cgiFormSuccess, or cgiFormIO in the event of an I/O error.



cgiFormResultType cgiValueEscapeData(char *data, int len)
cgiValueEscapeData() is identical to cgiValueEscape, except that the data is not null-terminated. This version of the function outputs len bytes. See cgiValueEscape for more information.

cgiEnvironmentResultType cgiWriteEnvironment(char *filename)
cgiWriteEnvironment() can be used to write the entire CGI environment, including form data, to the specified output file; cgiReadEnvironment() can then be used to restore that environment from the specified input file for debugging. Of course, these will only work as expected if you use the cgic copies of the CGI environment variables and cgiIn and cgiOut rather than stdin and stdout (also see above). These functions are useful in order to capture real CGI situations while the web server is running, then recreate them in a debugging environment. Both functions return cgiEnvironmentSuccess on success, cgiEnvironmentIO on an I/O error, and cgiEnvironmentMemory on an out-of-memory error.

cgiEnvironmentResultType cgiReadEnvironment(char *filename)
cgiReadEnvironment() restores a CGI environment saved to the specified file by cgiWriteEnvironment(). Of course, these will only work as expected if you use the cgic copies of the CGI environment variables and cgiIn and cgiOut rather than stdin and stdout (also see above). These functions are useful in order to capture real CGI situations while the web server is running, then recreate them in a debugging environment. Both functions return cgiEnvironmentSuccess on success, cgiEnvironmentIO on an I/O error, and cgiEnvironmentMemory on an out-of-memory error.

int cgiMain()
The programmer must write this function, which performs the unique task of the program and is invoked by the true main() function, found in the cgic library itself. The return value from cgiMain will be the return value of the program. It is expected that the user will make numerous calls to the cgiForm functions from within this function. See how to write a cgic application for details.

cgic variable reference

This section provides a reference guide to the various global variables provided by cgic for the programmer to utilize. These variables should always be used in preference to stdin, stdout, and calls to getenv() in order to ensure compatibility with the cgic CGI debugging features.

Most of these variables are equivalent to various CGI environment variables. The most important difference is that the cgic environment string variables are never null pointers. They will always point to valid C strings of zero or more characters.


char *cgiServerSoftware
Points to the name of the server software, or to an empty string if unknown.
char *cgiServerName
Points to the name of the server, or to an empty string if unknown.
char *cgiGatewayInterface
Points to the name of the gateway interface (usually CGI/1.1), or to an empty string if unknown.
char *cgiServerProtocol
Points to the protocol in use (usually HTTP/1.0), or to an empty string if unknown.
char *cgiServerPort
Points to the port number on which the server is listening for HTTP connections (usually 80), or an empty string if unknown.
char *cgiRequestMethod
Points to the method used in the request (usually GET or POST), or an empty string if unknown (this should not happen).
char *cgiPathInfo
Most web servers recognize any additional path information in the URL of the request beyond the name of the CGI program itself and pass that information on to the program. cgiPathInfo points to this additional path information.
char *cgiPathTranslated
Most web servers recognize any additional path information in the URL of the request beyond the name of the CGI program itself and pass that information on to the program. cgiPathTranslated points to this additional path information, translated by the server into a filesystem path on the local server.
char *cgiScriptName
Points to the name under which the program was invoked.
char *cgiQueryString
Contains any query information submitted by the user as a result of a GET-method form or an <ISINDEX> tag. Note that this information need not be parsed directly unless an <ISINDEX> tag was used; normally it is parsed automatically by the cgic library. Use the cgiForm family of functions to retrieve the values associated with form input fields. See how to write a cgic application for more information.
char *cgiRemoteHost
Points to the fully resolved hostname of the browser, if known, or an empty string if unknown.
char *cgiRemoteAddr
Points to the dotted-decimal IP address of the browser, if known, or an empty string if unknown.
char *cgiAuthType
Points to the type of authorization used for the request, if any, or an empty string if none or unknown.
char *cgiRemoteUser
Points to the user name under which the user has authenticated; an empty string if no authentication has taken place. The certainty of this information depends on the type of authorization in use; see cgiAuthType.
char *cgiRemoteIdent
Points to the user name volunteered by the user via the user identification protocol; an empty string if unknown. This information is not secure. Identification demons can be installed by users on insecure systems such as Windows machines.
char *cgiContentType
Points to the MIME content type of the information submitted by the user, if any; an empty string if no information was submitted. If this string is equal to application/x-www-form-urlencoded or multipart/form-data, the cgic library will automatically examine the form data submitted. If this string has any other non-empty value, a different type of data has been submitted. This is currently very rare, as most browsers can only submit forms and file uploads which cgic parses directly.
char *cgiCookie
Points to the raw cookie (browser-side persistent storage) data submitted by the web browser. Programmers should use the functions cgiCookies, cgiCookieString and cgiCookieInteger instead of examining this string directly.
char *cgiAccept
Points to a space-separated list of MIME content types acceptable to the browser (see cgiHeaderContentType() ), or an empty string. Unfortunately, this variable is not supplied in a useful form by most current browsers. Programmers wishing to make decisions based on the capabilities of the browser are advised to check the cgiUserAgent variable against a list of browsers and capabilities instead.
char *cgiUserAgent
Points to the name of the browser in use, or an empty string if this information is not available.
char *cgiReferrer
Points to the URL of the previous page visited by the user. This is often the URL of the form that brought the user to your program. Note that reporting this information is entirely up to the browser, which may choose not do so, and may choose not to do so truthfully. However, this variable is typically accurate. The frequently used misspelling cgiReferer is also supplied as a macro.
int cgiContentLength
The number of bytes of form or query data received. Note that if the submission is a form or query submission the library will read and parse all the information directly from cgiIn and/or cgiQueryString. The programmer should not do so, and indeed the cgiIn pointer will be at end-of-file in such cases.
FILE *cgiOut
Pointer to CGI output. The cgiHeader functions, such as cgiHeaderContentType, should be used first to output the mime headers; the output HTML page, GIF image or other web document should then be written to cgiOut by the programmer using standard C I/O functions such as fprintf() and fwrite(). cgiOut is normally equivalent to stdout; however, it is recommended that cgiOut be used to ensure compatibility with future versions of cgic for specialized environments.
FILE *cgiIn
Pointer to CGI input. In 99.99% of cases, you will not need this. CGIC 2.0 supports both regular POST form submissions and multipart/form-data file upload form submissions directly.

cgic result code reference

In most cases, cgic functions are designed to produce reasonable results even when browsers and users do unreasonable things. However, it is sometimes important to know precisely which unreasonable things took place, especially when assigning a default value or bounding a value is an inadequate solution. The following result codes are useful in making this determination.


cgiFormSuccess
Indicates that the function successfully performed at least one action (or retrieved at least one value, where applicable).
cgiFormTruncated
Indicates that a string value retrieved from the user was cut short to avoid overwriting the end of a buffer.
cgiFormBadType
Indicates that a "numeric" value submitted by the user was in fact not a legal number.
cgiFormEmpty
Indicates that a field was retrieved but contained no data.
cgiFormNotFound
Indicates that no value was submitted for a particular field.
cgiFormConstrained
Indicates that a numeric value was beyond the specified bounds and was forced to the lower or upper bound as appropriate.
cgiFormNoSuchChoice
Indicates that the value submitted for a single-choice field (such as a radio-button group) was not one of the acceptable values. This usually indicates a discrepancy between the form and the program.
cgiFormEOF
Returned by cgiFormFileRead when, at the start of the call, the cgiFilePtr object is already positioned at the end of the uploaded file data.
cgiFormIO
Returned by cgiFormFileRead when an I/O error occurs while reading uploaded file data.
cgiFormNotAFile
Returned in response to an attempt to manipulate a form field that is not a file upload field using a file-related function.
cgiFormNoContentType
Returned in response to an attempt to fetch the content type of a file-upload field when the content type is not specified by the browser.
cgiFormNoFileName
Returned in response to an attempt to fetch the file name of a file-upload field when a file name is not specified by the browser.
cgiFormOpenFailed
Returned in response to an attempt to read from a null cgiFilePtr object, typically when the programmer has failed to check the result of a call to cgiFormFileOpen.
cgiEnvironmentMemory
Indicates that an attempt to read or write the CGI environment to or from a capture file failed due to an out-of-memory error.
cgiEnvironmentSuccess
Indicates that an attempt to read or write the CGI environment to or from a capture file was successful.
cgiEnvironmentIO
Indicates that an attempt to read or write the CGI environment to or from a capture file failed due to an I/O error.
cgiEnvironmentWrongVersion
Indicates that an attempt to read from a saved debugging CGI environment produced by a pre-2.0 version of CGIC was made.

cgic quick index

cgiAccept | cgiAuthType | cgiContentLength | cgiContentType | cgiEnvironmentIO | cgiEnvironmentMemory | cgiEnvironmentSuccess | cgiCookieInteger | cgiCookies | cgiCookieSetInteger | cgiCookieSetString | cgiCookieString | cgiHtmlEscape | cgiHtmlEscapeData | cgiValueEscape | cgiValueEscapeData | cgiFormBadType | cgiFormCheckboxMultiple() | cgiFormCheckboxSingle() | cgiFormConstrained | cgiFormDouble() | cgiFormDoubleBounded() | cgiFormEOF | cgiFormEmpty | cgiFormEntries | cgiFormFileClose | cgiFormFileContentType | cgiFormFileName | cgiFormFileOpen | cgiFormFileRead | cgiFormFileSize | cgiFormInteger() | cgiFormIntegerBounded() | cgiFormNoFileName | cgiFormNoSuchChoice | cgiFormNotFound | cgiFormRadio() | cgiFormSelectMultiple() | cgiFormSelectSingle() | cgiFormString() | cgiFormStringMultiple() | cgiFormStringNoNewlines() | cgiFormStringSpaceNeeded() | cgiFormSuccess | cgiFormTruncated | cgiGatewayInterface | cgiHeaderContentType() | cgiHeaderLocation() | cgiHeaderStatus() | cgiIn | cgiMain() cgiOut | cgiPathInfo | cgiPathTranslated | cgiQueryString | cgiReadEnvironment() | cgiReferrer() | cgiRemoteAddr | cgiRemoteHost | cgiRemoteIdent | cgiRemoteUser | cgiRequestMethod | cgiScriptName | cgiServerName | cgiServerPort | cgiServerProtocol | cgiServerSoftware | cgiStringArrayFree() | cgiUserAgent | cgiWriteEnvironment()


Boutell.Com, Inc. cgic205/cgictest.c0100644000031000004540000003367310042027753013175 0ustar boutelldev/* Change this if the SERVER_NAME environment variable does not report the true name of your web server. */ #if 1 #define SERVER_NAME cgiServerName #endif #if 0 #define SERVER_NAME "www.boutell.com" #endif /* You may need to change this, particularly under Windows; it is a reasonable guess as to an acceptable place to store a saved environment in order to test that feature. If that feature is not important to you, you needn't concern yourself with this. */ #ifdef WIN32 #define SAVED_ENVIRONMENT "c:\\cgicsave.env" #else #define SAVED_ENVIRONMENT "/tmp/cgicsave.env" #endif /* WIN32 */ #include #include "cgic.h" #include #include void HandleSubmit(); void ShowForm(); void CookieSet(); void Name(); void Address(); void Hungry(); void Temperature(); void Frogs(); void Color(); void Flavors(); void NonExButtons(); void RadioButtons(); void File(); void Entries(); void Cookies(); void LoadEnvironment(); void SaveEnvironment(); int cgiMain() { #ifdef DEBUG LoadEnvironment(); #endif /* DEBUG */ /* Load a previously saved CGI scenario if that button has been pressed. */ if (cgiFormSubmitClicked("loadenvironment") == cgiFormSuccess) { LoadEnvironment(); } /* Set any new cookie requested. Must be done *before* outputting the content type. */ CookieSet(); /* Send the content type, letting the browser know this is HTML */ cgiHeaderContentType("text/html"); /* Top of the page */ fprintf(cgiOut, "\n"); fprintf(cgiOut, "cgic test\n"); fprintf(cgiOut, "

cgic test

\n"); /* If a submit button has already been clicked, act on the submission of the form. */ if ((cgiFormSubmitClicked("testcgic") == cgiFormSuccess) || cgiFormSubmitClicked("saveenvironment") == cgiFormSuccess) { HandleSubmit(); fprintf(cgiOut, "
\n"); } /* Now show the form */ ShowForm(); /* Finish up the page */ fprintf(cgiOut, "\n"); return 0; } void HandleSubmit() { Name(); Address(); Hungry(); Temperature(); Frogs(); Color(); Flavors(); NonExButtons(); RadioButtons(); File(); Entries(); Cookies(); /* The saveenvironment button, in addition to submitting the form, also saves the resulting CGI scenario to disk for later replay with the 'load saved environment' button. */ if (cgiFormSubmitClicked("saveenvironment") == cgiFormSuccess) { SaveEnvironment(); } } void Name() { char name[81]; cgiFormStringNoNewlines("name", name, 81); fprintf(cgiOut, "Name: "); cgiHtmlEscape(name); fprintf(cgiOut, "
\n"); } void Address() { char address[241]; cgiFormString("address", address, 241); fprintf(cgiOut, "Address:
\n");
	cgiHtmlEscape(address);
	fprintf(cgiOut, "
\n"); } void Hungry() { if (cgiFormCheckboxSingle("hungry") == cgiFormSuccess) { fprintf(cgiOut, "I'm Hungry!
\n"); } else { fprintf(cgiOut, "I'm Not Hungry!
\n"); } } void Temperature() { double temperature; cgiFormDoubleBounded("temperature", &temperature, 80.0, 120.0, 98.6); fprintf(cgiOut, "My temperature is %f.
\n", temperature); } void Frogs() { int frogsEaten; cgiFormInteger("frogs", &frogsEaten, 0); fprintf(cgiOut, "I have eaten %d frogs.
\n", frogsEaten); } char *colors[] = { "Red", "Green", "Blue" }; void Color() { int colorChoice; cgiFormSelectSingle("colors", colors, 3, &colorChoice, 0); fprintf(cgiOut, "I am: %s
\n", colors[colorChoice]); } char *flavors[] = { "pistachio", "walnut", "creme" }; void Flavors() { int flavorChoices[3]; int i; int result; int invalid; result = cgiFormSelectMultiple("flavors", flavors, 3, flavorChoices, &invalid); if (result == cgiFormNotFound) { fprintf(cgiOut, "I hate ice cream.

\n"); } else { fprintf(cgiOut, "My favorite ice cream flavors are:\n"); fprintf(cgiOut, "

    \n"); for (i=0; (i < 3); i++) { if (flavorChoices[i]) { fprintf(cgiOut, "
  • %s\n", flavors[i]); } } fprintf(cgiOut, "
\n"); } } char *ages[] = { "1", "2", "3", "4" }; void RadioButtons() { int ageChoice; char ageText[10]; /* Approach #1: check for one of several valid responses. Good if there are a short list of possible button values and you wish to enumerate them. */ cgiFormRadio("age", ages, 4, &ageChoice, 0); fprintf(cgiOut, "Age of Truck: %s (method #1)
\n", ages[ageChoice]); /* Approach #2: just get the string. Good if the information is not critical or if you wish to verify it in some other way. Note that if the information is numeric, cgiFormInteger, cgiFormDouble, and related functions may be used instead of cgiFormString. */ cgiFormString("age", ageText, 10); fprintf(cgiOut, "Age of Truck: %s (method #2)
\n", ageText); } char *votes[] = { "A", "B", "C", "D" }; void NonExButtons() { int voteChoices[4]; int i; int result; int invalid; char **responses; /* Method #1: check for valid votes. This is a good idea, since votes for nonexistent candidates should probably be discounted... */ fprintf(cgiOut, "Votes (method 1):
\n"); result = cgiFormCheckboxMultiple("vote", votes, 4, voteChoices, &invalid); if (result == cgiFormNotFound) { fprintf(cgiOut, "I hate them all!

\n"); } else { fprintf(cgiOut, "My preferred candidates are:\n"); fprintf(cgiOut, "

    \n"); for (i=0; (i < 4); i++) { if (voteChoices[i]) { fprintf(cgiOut, "
  • %s\n", votes[i]); } } fprintf(cgiOut, "
\n"); } /* Method #2: get all the names voted for and trust them. This is good if the form will change more often than the code and invented responses are not a danger or can be checked in some other way. */ fprintf(cgiOut, "Votes (method 2):
\n"); result = cgiFormStringMultiple("vote", &responses); if (result == cgiFormNotFound) { fprintf(cgiOut, "I hate them all!

\n"); } else { int i = 0; fprintf(cgiOut, "My preferred candidates are:\n"); fprintf(cgiOut, "

    \n"); while (responses[i]) { fprintf(cgiOut, "
  • %s\n", responses[i]); i++; } fprintf(cgiOut, "
\n"); } /* We must be sure to free the string array or a memory leak will occur. Simply calling free() would free the array but not the individual strings. The function cgiStringArrayFree() does the job completely. */ cgiStringArrayFree(responses); } void Entries() { char **array, **arrayStep; fprintf(cgiOut, "List of All Submitted Form Field Names:

\n"); if (cgiFormEntries(&array) != cgiFormSuccess) { return; } arrayStep = array; fprintf(cgiOut, "

    \n"); while (*arrayStep) { fprintf(cgiOut, "
  • "); cgiHtmlEscape(*arrayStep); fprintf(cgiOut, "\n"); arrayStep++; } fprintf(cgiOut, "
\n"); cgiStringArrayFree(array); } void Cookies() { char **array, **arrayStep; char cname[1024], cvalue[1024]; fprintf(cgiOut, "Cookies Submitted On This Call, With Values (Many Browsers NEVER Submit Cookies):

\n"); if (cgiCookies(&array) != cgiFormSuccess) { return; } arrayStep = array; fprintf(cgiOut, "\n"); fprintf(cgiOut, "\n"); while (*arrayStep) { char value[1024]; fprintf(cgiOut, ""); fprintf(cgiOut, "
CookieValue
"); cgiHtmlEscape(*arrayStep); fprintf(cgiOut, ""); cgiCookieString(*arrayStep, value, sizeof(value)); cgiHtmlEscape(value); fprintf(cgiOut, "\n"); arrayStep++; } fprintf(cgiOut, "
\n"); cgiFormString("cname", cname, sizeof(cname)); cgiFormString("cvalue", cvalue, sizeof(cvalue)); if (strlen(cname)) { fprintf(cgiOut, "New Cookie Set On This Call:

\n"); fprintf(cgiOut, "Name: "); cgiHtmlEscape(cname); fprintf(cgiOut, "Value: "); cgiHtmlEscape(cvalue); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "If your browser accepts cookies (many do not), this new cookie should appear in the above list the next time the form is submitted.

\n"); } cgiStringArrayFree(array); } void File() { cgiFilePtr file; char name[1024]; char contentType[1024]; char buffer[1024]; int size; int got; if (cgiFormFileName("file", name, sizeof(name)) != cgiFormSuccess) { printf("

No file was uploaded.

\n"); return; } fprintf(cgiOut, "The filename submitted was: "); cgiHtmlEscape(name); fprintf(cgiOut, "

\n"); cgiFormFileSize("file", &size); fprintf(cgiOut, "The file size was: %d bytes

\n", size); cgiFormFileContentType("file", contentType, sizeof(contentType)); fprintf(cgiOut, "The alleged content type of the file was: "); cgiHtmlEscape(contentType); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "Of course, this is only the claim the browser made when uploading the file. Much like the filename, it cannot be trusted.

\n"); fprintf(cgiOut, "The file's contents are shown here:

\n"); if (cgiFormFileOpen("file", &file) != cgiFormSuccess) { fprintf(cgiOut, "Could not open the file.

\n"); return; } fprintf(cgiOut, "

\n");
	while (cgiFormFileRead(file, buffer, sizeof(buffer), &got) ==
		cgiFormSuccess)
	{
		cgiHtmlEscapeData(buffer, got);
	}
	fprintf(cgiOut, "
\n"); cgiFormFileClose(file); } void ShowForm() { fprintf(cgiOut, ""); fprintf(cgiOut, "
\n"); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "Text Field containing Plaintext\n"); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "Your Name\n"); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "Multiple-Line Text Field\n"); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "Checkbox\n"); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "Hungry\n"); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "Text Field containing a Numeric Value\n"); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "Blood Temperature (80.0-120.0)\n"); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "Text Field containing an Integer Value\n"); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "Frogs Eaten\n"); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "Single-SELECT\n"); fprintf(cgiOut, "
\n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "
\n"); fprintf(cgiOut, "Multiple-SELECT\n"); fprintf(cgiOut, "
\n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "

Exclusive Radio Button Group: Age of Truck in Years\n"); fprintf(cgiOut, "1\n"); fprintf(cgiOut, "2\n"); fprintf(cgiOut, "3\n"); fprintf(cgiOut, "4\n"); fprintf(cgiOut, "

Nonexclusive Checkbox Group: Voting for Zero through Four Candidates\n"); fprintf(cgiOut, "A\n"); fprintf(cgiOut, "B\n"); fprintf(cgiOut, "C\n"); fprintf(cgiOut, "D\n"); fprintf(cgiOut, "

File Upload:\n"); fprintf(cgiOut, " (Select A Local File)\n"); fprintf(cgiOut, "

\n"); fprintf(cgiOut, "

Set a Cookie

\n"); fprintf(cgiOut, " Cookie Name\n"); fprintf(cgiOut, " Cookie Value

\n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "

Save the CGI Environment

\n"); fprintf(cgiOut, "Pressing this button will submit the form, then save the CGI environment so that it can be replayed later by calling cgiReadEnvironment (in a debugger, for instance).

\n"); fprintf(cgiOut, "\n"); fprintf(cgiOut, "

\n"); } void CookieSet() { char cname[1024]; char cvalue[1024]; /* Must set cookies BEFORE calling cgiHeaderContentType */ cgiFormString("cname", cname, sizeof(cname)); cgiFormString("cvalue", cvalue, sizeof(cvalue)); if (strlen(cname)) { /* Cookie lives for one day (or until browser chooses to get rid of it, which may be immediately), and applies only to this script on this site. */ cgiHeaderCookieSetString(cname, cvalue, 86400, cgiScriptName, SERVER_NAME); } } void LoadEnvironment() { if (cgiReadEnvironment(SAVED_ENVIRONMENT) != cgiEnvironmentSuccess) { cgiHeaderContentType("text/html"); fprintf(cgiOut, "Error\n"); fprintf(cgiOut, "

Error

\n"); fprintf(cgiOut, "cgiReadEnvironment failed. Most " "likely you have not saved an environment " "yet.\n"); exit(0); } /* OK, return now and show the results of the saved environment */ } void SaveEnvironment() { if (cgiWriteEnvironment(SAVED_ENVIRONMENT) != cgiEnvironmentSuccess) { fprintf(cgiOut, "

cgiWriteEnvironment failed. Most " "likely %s is not a valid path or is not " "writable by the user that the CGI program " "is running as.

\n", SAVED_ENVIRONMENT); } else { fprintf(cgiOut, "

Environment saved. Click this button " "to restore it, playing back exactly the same " "scenario: " "

" "

\n"); } } cgic205/license.txt0100644000031000004540000000717110042027753013401 0ustar boutelldevCGIC License Terms ------------------ Basic License ------------- CGIC, copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 by Thomas Boutell and Boutell.Com, Inc.. Permission is granted to use CGIC in any application, commercial or noncommercial, at no cost. HOWEVER, this copyright paragraph must appear on a "credits" page accessible in the public online and offline documentation of the program. Modified versions of the CGIC library should not be distributed without the attachment of a clear statement regarding the author of the modifications, and this notice may in no case be removed. Modifications may also be submitted to the author for inclusion in the main CGIC distribution. IF YOU WOULD PREFER NOT TO ATTACH THE ABOVE NOTICE to the public documentation of your application, consult the information which follows regarding the availability of a nonexclusive commercial license for CGIC. Commercial License ------------------ The price of a nonexclusive commercial license is $200 U.S. To purchase the license: 1. Print and sign two copies of the document below. 2. Send both copies, along with Visa, Mastercard, Discover, or Amex card number, cardholder's name, and expiration date OR a check for $200 in U.S. funds drawn on a U.S. bank, to: Boutell.Com, Inc. PO Box 63767 Philadelphia, PA 19147 USA Credit card orders may alternatively be faxed to: Boutell.Com, Inc. (208) 445-0327 BE SURE TO INCLUDE AN EMAIL ADDRESS, as well as a postal address and phone number. 3. We will return one signed copy to you promptly. You will also receive swift notification of new versions of CGIC, in addition to ongoing email support. * * * CGIC Nonexclusive Commercial License The licensee named below is granted the right to utilize CGIC, major version 1 or 2, any minor version thereof, in CGI applications without the need for a credit notice of any kind. CGI applications developed by the holder of this license may be distributed freely in source code or binary form without additional fees or royalties. This license does not grant the right to use CGIC to create a development tool which passes on substantially all of the capabilities of the CGIC library to the user of the tool, unless that tool is to be used internally by the license holder only in order to develop CGI applications. This license may not be resold, but applications developed in accordance with the terms of the license may be distributed freely subject to the limitations described above. Future minor (2.x) versions of CGIC will be covered by this license free of charge. If significant defects of workmanship are discovered in version 2.x, minor releases to correct them will be made available before or at the same time that those defects are addressed in any future major version. Future "major" (3.x) versions will be available to licensees at an upgrade price of $50. If, for any reason, any portion of this license is found to be invalid, that portion of the license only is invalidated and the remainder of the agreement remains in effect. If this license has not been signed by Thomas Boutell or M. L. Grant on behalf of Boutell. Com, Inc., it is invalid. Licensee's Name: _____________________ Signed for Licensee: _____________________ Complete Mailing Address: _____________________ _____________________ _____________________ Phone Number: _____________________ Email Address: _____________________ Signed for Boutell.Com, Inc.: _____________________ Date: _____________________ cgic205/support.txt0100644000031000004540000000340310042027753013465 0ustar boutelldevSupport for CGIC ---------------- ____ / \ | STOP | \____/ Are you getting a "server error," indicating that your web server "cannot allow POST to this URL," or a similar message? YOU MUST CONFIGURE YOUR WEB SERVER TO ALLOW CGI PROGRAMS, AND YOU MUST INSTALL CGI PROGRAMS IN THE LOCATION (OR WITH THE EXTENSION) THAT YOUR WEB SERVER EXPECTS TO SEE. Please don't send me email about this, unless you are purchasing commercial support. It is strictly between you and your web server's documentation, or between you and your ISP. If you wish to purchase commercial support, we will gladly attempt to help you resolve such problems, but the cooperation of your web server administrator will be necessary in most cases. Thanks! Free Support ------------ Anyone can mail questions about the gd and cgic libraries to boutell@boutell.com. However, I receive a very large volume of email on many subjects, and while I do my best to respond to all queries this can take some time. Sometimes the response must take the form of an eventual new release or an addition to a FAQ or other document, as opposed to an detailed individual response. Hourly Support -------------- Those requiring support in detail may arrange for direct support from the author, Thomas Boutell, at the rate of $50/hr, billed directly by credit card. Purchase orders are also accepted from Fortune 500 corporations and institutions in good standing. To make arrangements, contact boutell@boutell.com. Alternatively, use our free-form secure message page at: https://www.boutell.com/freeform/ To avoid delay and/or confusion, be sure to specifically mention that you wish to purchase CGIC support at the hourly rate above. -Thomas Boutell, boutell@boutell.com cgic205/readme.txt0100644000031000004540000000101610042027753013204 0ustar boutelldevThis is the CGIC CGI development library for C programmers. Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, Thomas Boutell and Boutell.Com, Inc. See the file cgic.html for complete documentation in a single HTML hypertext file to be accessed with your web browser. If you preferer, there is a plaintext version in the file cgic.txt. Or see http://www.boutell.com/cgic/ for the latest copy of the documentation. See the files license.txt and support.txt for terms of use and technical support information.