2577 lines
56 KiB
C
2577 lines
56 KiB
C
/* 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 = ∓
|
|
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
|