📄 cgi.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 getTempFileName() function to suit your needs. */
#define cgicTempDir "/tmp"
#if CGICDEBUG
#define CGICDEBUGSTART \
{ \
FILE *dout; \
dout = fopen("/home/boutell/public_html/debug", "a"); \
#define CGICDEBUGEND \
fclose(dout); \
}
#else /* CGICDEBUG */
#define CGICDEBUGSTART
#define CGICDEBUGEND
#endif /* CGICDEBUG */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#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 name for working storage of file uploads. */
char *tfileName;
struct cgiFormEntryStruct *next;
} cgiFormEntry;
/* The first form entry. */
static cgiFormEntry *cgiFormEntryFirst;
static cgiParseResultType cgiParseGetFormInput();
static cgiParseResultType cgiParsePostFormInput();
static cgiParseResultType cgiParsePostMultipartInput();
static cgiParseResultType cgiParseFormInput(char *data, int length);
static void cgiSetupConstants();
static void cgiFreeResources();
static int cgiStrEqNc(char *s1, char *s2);
static int cgiStrBeginsNc(char *s1, char *s2);
int main(int argc, char *argv[]) {
int result;
char *cgiContentLengthString;
char *e;
cgiSetupConstants();
cgiGetenv(&cgiServerSoftware, "SERVER_SOFTWARE");
cgiGetenv(&cgiServerName, "SERVER_NAME");
cgiGetenv(&cgiGatewayInterface, "GATEWAY_INTERFACE");
cgiGetenv(&cgiServerProtocol, "SERVER_PROTOCOL");
cgiGetenv(&cgiServerPort, "SERVER_PORT");
cgiGetenv(&cgiRequestMethod, "REQUEST_METHOD");
cgiGetenv(&cgiPathInfo, "PATH_INFO");
cgiGetenv(&cgiPathTranslated, "PATH_TRANSLATED");
cgiGetenv(&cgiScriptName, "SCRIPT_NAME");
cgiGetenv(&cgiQueryString, "QUERY_STRING");
cgiGetenv(&cgiRemoteHost, "REMOTE_HOST");
cgiGetenv(&cgiRemoteAddr, "REMOTE_ADDR");
cgiGetenv(&cgiAuthType, "AUTH_TYPE");
cgiGetenv(&cgiRemoteUser, "REMOTE_USER");
cgiGetenv(&cgiRemoteIdent, "REMOTE_IDENT");
/* 2.0: the content type string needs to be parsed and modified, so
copy it to a buffer. */
e = getenv("CONTENT_TYPE");
if (e) {
if (strlen(e) < sizeof(cgiContentTypeData)) {
strcpy(cgiContentType, e);
} else {
/* Truncate safely in the event of what is almost certainly
a hack attempt */
strncpy(cgiContentType, e, sizeof(cgiContentTypeData));
cgiContentType[sizeof(cgiContentTypeData) - 1] = '\0';
}
} else {
cgiContentType[0] = '\0';
}
/* Never null */
cgiMultipartBoundary = "";
/* 2.0: parse semicolon-separated additional parameters of the
content type. The one we're interested in is 'boundary'.
We discard the rest to make cgiContentType more useful
to the typical programmer. */
if (strchr(cgiContentType, ';')) {
char *sat = strchr(cgiContentType, ';');
while (sat) {
*sat = '\0';
sat++;
while (isspace(*sat)) {
sat++;
}
if (cgiStrBeginsNc(sat, "boundary=")) {
char *s;
cgiMultipartBoundary = sat + strlen("boundary=");
s = cgiMultipartBoundary;
while ((*s) && (!isspace(*s))) {
s++;
}
*s = '\0';
break;
} else {
sat = strchr(sat, ';');
}
}
}
cgiGetenv(&cgiContentLengthString, "CONTENT_LENGTH");
cgiContentLength = atoi(cgiContentLengthString);
cgiGetenv(&cgiAccept, "HTTP_ACCEPT");
cgiGetenv(&cgiUserAgent, "HTTP_USER_AGENT");
cgiGetenv(&cgiReferrer, "HTTP_REFERER");
cgiGetenv(&cgiCookie, "HTTP_COOKIE");
#ifdef CGICDEBUG
CGICDEBUGSTART
fprintf(dout, "%d\n", cgiContentLength);
fprintf(dout, "%s\n", cgiRequestMethod);
fprintf(dout, "%s\n", cgiContentType);
CGICDEBUGEND
#endif /* CGICDEBUG */
#ifdef WIN32
/* 1.07: Must set stdin and stdout to binary mode */
/* 2.0: this is particularly crucial now and must not be removed */
_setmode( _fileno( stdin ), _O_BINARY );
_setmode( _fileno( stdout ), _O_BINARY );
#endif /* WIN32 */
cgiFormEntryFirst = 0;
cgiIn = stdin;
cgiOut = stdout;
cgiRestored = 0;
/* These five lines keep compilers from
producing warnings that argc and argv
are unused. They have no actual function. */
if (argc) {
if (argv[0]) {
cgiRestored = 0;
}
}
if (cgiStrEqNc(cgiRequestMethod, "post")) {
#ifdef CGICDEBUG
CGICDEBUGSTART
fprintf(dout, "POST recognized\n");
CGICDEBUGEND
#endif /* CGICDEBUG */
if (cgiStrEqNc(cgiContentType, "application/x-www-form-urlencoded")) {
#ifdef CGICDEBUG
CGICDEBUGSTART
fprintf(dout, "Calling PostFormInput\n");
CGICDEBUGEND
#endif /* CGICDEBUG */
if (cgiParsePostFormInput() != cgiParseSuccess) {
#ifdef CGICDEBUG
CGICDEBUGSTART
fprintf(dout, "PostFormInput failed\n");
CGICDEBUGEND
#endif /* CGICDEBUG */
cgiFreeResources();
return -1;
}
#ifdef CGICDEBUG
CGICDEBUGSTART
fprintf(dout, "PostFormInput succeeded\n");
CGICDEBUGEND
#endif /* CGICDEBUG */
} else if (cgiStrEqNc(cgiContentType, "multipart/form-data")) {
#ifdef CGICDEBUG
CGICDEBUGSTART
fprintf(dout, "Calling PostMultipartInput\n");
CGICDEBUGEND
#endif /* CGICDEBUG */
if (cgiParsePostMultipartInput() != cgiParseSuccess) {
#ifdef CGICDEBUG
CGICDEBUGSTART
fprintf(dout, "PostMultipartInput failed\n");
CGICDEBUGEND
#endif /* CGICDEBUG */
cgiFreeResources();
return -1;
}
#ifdef CGICDEBUG
CGICDEBUGSTART
fprintf(dout, "PostMultipartInput succeeded\n");
CGICDEBUGEND
#endif /* CGICDEBUG */
}
} else if (cgiStrEqNc(cgiRequestMethod, "get")) {
/* The spec says this should be taken care of by
the server, but... it isn't */
cgiContentLength = strlen(cgiQueryString);
if (cgiParseGetFormInput() != cgiParseSuccess) {
#ifdef CGICDEBUG
CGICDEBUGSTART
fprintf(dout, "GetFormInput failed\n");
CGICDEBUGEND
#endif /* CGICDEBUG */
cgiFreeResources();
return -1;
} else {
#ifdef CGICDEBUG
CGICDEBUGSTART
fprintf(dout, "GetFormInput succeeded\n");
CGICDEBUGEND
#endif /* CGICDEBUG */
}
}
result = cgiMain();
cgiFreeResources();
return result;
}
static void cgiGetenv(char **s, char *var){
*s = getenv(var);
if (!(*s)) {
*s = "";
}
}
static cgiParseResultType cgiParsePostFormInput() {
char *input;
cgiParseResultType result;
if (!cgiContentLength) {
return cgiParseSuccess;
}
input = (char *) malloc(cgiContentLength);
if (!input) {
return cgiParseMemory;
}
if (((int) fread(input, 1, cgiContentLength, cgiIn))
!= cgiContentLength)
{
return cgiParseIO;
}
result = cgiParseFormInput(input, cgiContentLength);
free(input);
return result;
}
/* 2.0: A virtual datastream supporting putback of
enough characters to handle multipart boundaries easily.
A simple memset(&mp, 0, sizeof(mp)) is suitable initialization. */
typedef struct {
/* Buffer for putting characters back */
char putback[1024];
/* Position in putback from which next character will be read.
If readPos == writePos, then next character should
come from cgiIn. */
int readPos;
/* Position in putback to which next character will be put back.
If writePos catches up to readPos, as opposed to the other
way around, the stream no longer functions properly.
Calling code must guarantee that no more than
sizeof(putback) bytes are put back at any given time. */
int writePos;
/* Offset in the virtual datastream; can be compared
to cgiContentLength */
int offset;
} mpStream, *mpStreamPtr;
int mpRead(mpStreamPtr mpp, char *buffer, int len)
{
int ilen = len;
int got = 0;
while (len) {
if (mpp->readPos != mpp->writePos) {
*buffer++ = mpp->putback[mpp->readPos++];
mpp->readPos %= sizeof(mpp->putback);
got++;
len--;
} else {
break;
}
}
/* Refuse to read past the declared length in order to
avoid deadlock */
if (len > (cgiContentLength - mpp->offset)) {
len = cgiContentLength - mpp->offset;
}
if (len) {
int fgot = fread(buffer, 1, len, cgiIn);
if (fgot >= 0) {
mpp->offset += (got + fgot);
return got + fgot;
} else if (got > 0) {
mpp->offset += got;
return got;
} else {
/* EOF or error */
return fgot;
}
} else if (got) {
return got;
} else if (ilen) {
return EOF;
} else {
/* 2.01 */
return 0;
}
}
void mpPutBack(mpStreamPtr mpp, char *data, int len)
{
mpp->offset -= len;
while (len) {
mpp->putback[mpp->writePos++] = *data++;
mpp->writePos %= sizeof(mpp->putback);
len--;
}
}
/* This function copies the body to outf if it is not null, otherwise to
a newly allocated character buffer at *outP, which will be null
terminated; if both outf and outP are null the body is not stored.
If bodyLengthP is not null, the size of the body in bytes is stored
to *bodyLengthP, not including any terminating null added to *outP.
If 'first' is nonzero, a preceding newline is not expected before
the boundary. If 'first' is zero, a preceding newline is expected.
Upon return mpp is positioned after the boundary and its trailing
newline, if any; if the boundary is followed by -- the next two
characters read after this function returns will be --. Upon error,
if outP is not null, *outP is a null pointer; *bodyLengthP
is set to zero. Returns cgiParseSuccess, cgiParseMemory
or cgiParseIO. */
static cgiParseResultType afterNextBoundary(mpStreamPtr mpp,
FILE *outf,
char **outP,
int *bodyLengthP,
int first
);
static int readHeaderLine(
mpStreamPtr mpp,
char *attr,
int attrSpace,
char *value,
int valueSpace);
static void decomposeValue(char *value,
char *mvalue, int mvalueSpace,
char **argNames,
char **argValues,
int argValueSpace);
/* tfileName must be 1024 bytes to ensure adequacy on
win32 (1024 exceeds the maximum path length and
certainly exceeds observed behavior of _tmpnam).
May as well also be 1024 bytes on Unix, although actual
length is strlen(cgiTempDir) + a short unique pattern. */
static cgiParseResultType getTempFileName(char *tfileName);
static cgiParseResultType cgiParsePostMultipartInput() {
cgiParseResultType result;
cgiFormEntry *n = 0, *l = 0;
int got;
FILE *outf = 0;
char *out = 0;
char tfileName[1024];
mpStream mp;
mpStreamPtr mpp = ∓
memset(&mp, 0, sizeof(mp));
if (!cgiContentLength) {
return cgiParseSuccess;
}
/* Read first boundary, including trailing newline */
result = afterNextBoundary(mpp, 0, 0, 0, 1);
if (result == cgiParseIO) {
/* An empty submission is not necessarily an error */
return cgiParseSuccess;
} else if (result != cgiParseSuccess) {
return result;
}
while (1) {
char d[1024];
char fvalue[1024];
char fname[1024];
int bodyLength = 0;
char ffileName[1024];
char fcontentType[1024];
char attr[1024];
char value[1024];
fvalue[0] = 0;
fname[0] = 0;
ffileName[0] = 0;
fcontentType[0] = 0;
out = 0;
outf = 0;
/* Check for EOF */
got = mpRead(mpp, d, 2);
if (got < 2) {
/* Crude EOF */
break;
}
if ((d[0] == '-') && (d[1] == '-')) {
/* Graceful EOF */
break;
}
mpPutBack(mpp, d, 2);
/* Read header lines until end of header */
while (readHeaderLine(
mpp, attr, sizeof(attr), value, sizeof(value)))
{
char *argNames[3];
char *argValues[2];
/* Content-Disposition: form-data;
name="test"; filename="googley.gif" */
if (cgiStrEqNc(attr, "Content-Disposition")) {
argNames[0] = "name";
argNames[1] = "filename";
argNames[2] = 0;
argValues[0] = fname;
argValues[1] = ffileName;
decomposeValue(value,
fvalue, sizeof(fvalue),
argNames,
argValues,
1024);
} else if (cgiStrEqNc(attr, "Content-Type")) {
argNames[0] = 0;
decomposeValue(value,
fcontentType, sizeof(fcontentType),
argNames,
0,
0);
}
}
if (!cgiStrEqNc(fvalue, "form-data")) {
/* Not form data */
continue;
}
/* Body is everything from here until the next
boundary. So, set it aside and move past boundary.
If a filename was submitted as part of the
disposition header, store to a temporary file.
Otherwise, store to a memory buffer (it is
presumably a regular form field). */
if (strlen(ffileName)) {
if (getTempFileName(tfileName) != cgiParseSuccess) {
return cgiParseIO;
}
outf = fopen(tfileName, "w+b");
} else {
outf = 0;
tfileName[0] = '\0';
}
result = afterNextBoundary(mpp, outf, &out, &bodyLength, 0);
if (result != cgiParseSuccess) {
/* Lack of a boundary here is an error. */
if (outf) {
fclose(outf);
unlink(tfileName);
}
if (out) {
free(out);
}
return result;
}
/* OK, we have a new pair, add it to the list. */
n = (cgiFormEntry *) malloc(sizeof(cgiFormEntry));
if (!n) {
goto outOfMemory;
}
memset(n, 0, sizeof(cgiFormEntry));
/* 2.01: one of numerous new casts required
to please C++ compilers */
n->attr = (char *) malloc(strlen(fname) + 1);
if (!n->attr) {
goto outOfMemory;
}
strcpy(n->attr, fname);
if (out) {
n->value = out;
out = 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -