emsApplication/3rdPartner/cgic-master/cgic.c

2577 lines
56 KiB
C
Raw Normal View History

2024-05-24 12:19:45 +08:00
/* 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 getTempFile() function to suit your needs. */
#define cgicTempDir "/tmp"
#define cgicMaxTempSize 1073741824
#if CGICDEBUG
#define CGICDEBUGSTART \
{ \
FILE *dout; \
dout = fopen("/home/boutell/public_html/debug", "a"); \
#define CGICDEBUGEND \
fclose(dout); \
}
#else /* CGICDEBUG */
#define CGICDEBUGSTART
#define CGICDEBUGEND
#endif /* CGICDEBUG */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef WIN32
#include <io.h>
/* cgic 2.01 */
#include <fcntl.h>
#else
#include <unistd.h>
#endif /* WIN32 */
#include "cgic.h"
#define cgiStrEq(a, b) (!strcmp((a), (b)))
char *cgiServerSoftware;
char *cgiServerName;
char *cgiGatewayInterface;
char *cgiServerProtocol;
char *cgiServerPort;
char *cgiRequestMethod;
char *cgiPathInfo;
char *cgiPathTranslated;
char *cgiScriptName;
char *cgiQueryString;
char *cgiRemoteHost;
char *cgiRemoteAddr;
char *cgiAuthType;
char *cgiRemoteUser;
char *cgiRemoteIdent;
char cgiContentTypeData[1024];
char *cgiContentType = cgiContentTypeData;
char *cgiMultipartBoundary;
char *cgiCookie;
int cgiContentLength;
char *cgiAccept;
char *cgiUserAgent;
char *cgiReferrer;
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 descriptor for working storage of file uploads. */
FILE *tFile;
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);
#ifdef UNIT_TEST
static int unitTest();
#endif
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 */
cgiHeaderStatus(500, "Error reading form data");
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 */
cgiHeaderStatus(500, "Error reading form data");
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 */
cgiHeaderStatus(500, "Error reading form data");
cgiFreeResources();
return -1;
} else {
#ifdef CGICDEBUG
CGICDEBUGSTART
fprintf(dout, "GetFormInput succeeded\n");
CGICDEBUGEND
#endif /* CGICDEBUG */
}
}
#ifdef UNIT_TEST
unitTest();
cgiFreeResources();
return 0;
#else
result = cgiMain();
return result;
#endif
}
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;
/* Refuse to read past the declared length in order to
avoid deadlock */
if (len > (cgiContentLength - mpp->offset)) {
len = cgiContentLength - mpp->offset;
}
while (len) {
if (mpp->readPos != mpp->writePos) {
*buffer++ = mpp->putback[mpp->readPos++];
mpp->readPos %= sizeof(mpp->putback);
got++;
len--;
} else {
break;
}
}
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) {
mpp->offset += 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);
static cgiParseResultType getTempFile(FILE **tFile);
static cgiParseResultType cgiParsePostMultipartInput() {
cgiParseResultType result;
cgiFormEntry *n = 0, *l = 0;
int got;
FILE *outf = 0;
char *out = 0;
mpStream mp;
mpStreamPtr mpp = &mp;
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 */
result = afterNextBoundary(mpp, 0, 0, 0, 0);
if (result != cgiParseSuccess) {
/* Lack of a boundary here is an error. */
return result;
}
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 (getTempFile(&outf) != cgiParseSuccess) {
return cgiParseIO;
}
} else {
outf = 0;
}
result = afterNextBoundary(mpp, outf, &out, &bodyLength, 0);
if (result != cgiParseSuccess) {
/* Lack of a boundary here is an error. */
if (outf) {
fclose(outf);
}
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';
}
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);
if(outf)
{
n->tFile = fdopen (dup (fileno (outf)), "w+b");
fclose(outf);
}
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->tFile) {
fclose(n->tFile);
}
if (n->contentType) {
free(n->contentType);
}
free(n);
}
if (out) {
free(out);
}
if (outf) {
fclose(outf);
}
return cgiParseMemory;
}
static cgiParseResultType getTempFile(FILE **tFile)
{
/* 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. */
char tfileName[1024];
#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
*tFile = fopen(tfileName, "w+b");
unlink(tfileName);
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]);
}
if(outLen > cgicMaxTempSize) {
goto outOfMemory;
}
}
/* 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) {
if (argValue) {
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 foundAmp = 0;
int start = pos;
int len = 0;
char *attr;
char *value;
while (pos != length) {
if (data[pos] == '&') {
/* Tolerate attr name without a value. This will fall through
and give us an empty value */
break;
}
if (data[pos] == '=') {
pos++;
break;
}
pos++;
len++;
}
if (!len) {
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->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 (c->tFile) {
fclose(c->tFile);
}
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 (!e->tFile) {
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 (!e->tFile) {
*cfpp = 0;
return cgiFormNotAFile;
}
cfp = (cgiFilePtr) malloc(sizeof(cgiFile));
if (!cfp) {
*cfpp = 0;
return cgiFormMemory;
}
cfp->in = fdopen(dup(fileno(e->tFile)), "rb");
rewind(cfp->in);
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 */
/* Actually, it can, so this is real. */
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);
cgiHeaderCookieSet(name, svalue, secondsToLive, path, domain, 0);
}
static char *days[] = {
"Sun",
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat"
};
static char *months[] = {
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
};
void cgiHeaderCookieSet(char *name, char *value, int secondsToLive,
char *path, char *domain, int options)
{
/* 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%s%s%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,
((options & cgiCookieSecure) ? "; Secure" : ""),
((options & cgiCookieHttpOnly) ? "; HttpOnly" : ""),
((options & cgiCookieSameSiteStrict) ? "; SameSite=Strict" : ""));
}
void cgiHeaderCookieSetString(char *name, char *value, int secondsToLive,
char *path, char *domain)
{
cgiHeaderCookieSet(name, value, secondsToLive, path, domain, 0);
}
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 = NULL;
int got;
int len = e->valueLength;
if (getTempFile(&out)
!= cgiParseSuccess || !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);
goto error;
}
if (((int) fwrite(buffer, 1, got, out)) != got) {
result = cgiEnvironmentIO;
fclose(out);
goto error;
}
len -= got;
}
/* cgic 2.05: should be fclose not rewind */
e->tFile = out;
} else {
e->tFile = NULL;
}
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->tFile) {
fclose(e->tFile);
}
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) {
size_t 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 = 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(const 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(const 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(const char *data, int len)
{
while (len--) {
if (*data == '\"') {
TRYPUTC('&');
TRYPUTC('#');
TRYPUTC('3');
TRYPUTC('4');
TRYPUTC(';');
} else {
TRYPUTC(*data);
}
data++;
}
return cgiFormSuccess;
}
cgiFormResultType cgiValueEscape(const char *s)
{
return cgiValueEscapeData(s, (int) strlen(s));
}
#ifdef UNIT_TEST
static void unitTestAssert(const int value, const char *message);
static int unitTest() {
char *input = "one=1&two=2&empty1&four=4&empty2";
cgiFormEntry *e;
cgiParseResultType result = cgiParseFormInput(input, strlen(input));
unitTestAssert(result == cgiParseSuccess, "cgiParseFormInput did not return cgiParseSuccess");
e = cgiFormEntryFirst;
unitTestAssert(!!e, "first entry missing");
unitTestAssert(!strcmp(e->attr, "one"), "first entry name is not one");
unitTestAssert(!strcmp(e->value, "1"), "first entry value is not 1");
e = e->next;
unitTestAssert(!!e, "Test failed: second entry missing");
unitTestAssert(!strcmp(e->attr, "two"), "second entry name is not two");
unitTestAssert(!strcmp(e->value, "2"), "second entry value is not 2");
e = e->next;
unitTestAssert(!!e, "Test failed: third entry missing");
unitTestAssert(!strcmp(e->attr, "empty1"), "third entry name is not empty1");
unitTestAssert(!strcmp(e->value, ""), "third entry value is not empty string");
e = e->next;
unitTestAssert(!!e, "Test failed: fourth entry missing");
unitTestAssert(!strcmp(e->attr, "four"), "fourth entry name is not four");
unitTestAssert(!strcmp(e->value, "4"), "fourth entry value is not 4");
e = e->next;
unitTestAssert(!!e, "Test failed: fifth entry missing");
unitTestAssert(!strcmp(e->attr, "empty2"), "fifth entry name is not empty2");
unitTestAssert(!strcmp(e->value, ""), "fifth entry value is not empty string");
unitTestAssert(!e->next, "unexpected entry at end of list");
return 0;
}
static void unitTestAssert(const int value, const char *message)
{
if (value) {
return;
}
fprintf(stderr, "Test failed: %s\n", message);
exit(1);
}
#endif