/* 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" #include #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; int cgiTreatUrlEncoding; 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 cgiMain_init() {} int main(int argc, char *argv[]) { int result; char *cgiContentLengthString; char *e; while (FCGI_Accept() >= 0) { 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"); if(strcmp(cgiRequestMethod,"")==0 && argc>=1) cgiRequestMethod="GET"; cgiGetenv(&cgiPathInfo, "PATH_INFO"); cgiGetenv(&cgiPathTranslated, "PATH_TRANSLATED"); cgiGetenv(&cgiScriptName, "SCRIPT_NAME"); cgiGetenv(&cgiQueryString, "QUERY_STRING"); if(strcmp(cgiQueryString,"")==0 && argc>=2){ cgiQueryString=argv[1]; } 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); if(cgiContentLength==0 && argc>=2){ cgiContentLength=strlen(argv[1]); } 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( FCGI_fileno( stdin ), _O_BINARY ); _setmode( FCGI_fileno( stdout ), _O_BINARY ); #endif /* WIN32 */ cgiFormEntryFirst = 0; cgiIn = FCGI_stdin; cgiOut = FCGI_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; } } cgiTreatUrlEncoding=1; if (cgiStrEqNc(cgiRequestMethod, "post")) { cgiTreatUrlEncoding=0; #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(); } FCGI_Finish(); 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]; if(cgiTreatUrlEncoding==1) 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; } else s[dstPos++] = ch; 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)); }