Initial import.
This commit is contained in:
11
src/0BUILD
Normal file
11
src/0BUILD
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml?>
|
||||
|
||||
|
||||
<gwbuild>
|
||||
|
||||
<subdirs>
|
||||
aqcgi
|
||||
</subdirs>
|
||||
|
||||
|
||||
</gwbuild>
|
||||
103
src/aqcgi/0BUILD
Normal file
103
src/aqcgi/0BUILD
Normal file
@@ -0,0 +1,103 @@
|
||||
<?xml?>
|
||||
|
||||
|
||||
<gwbuild>
|
||||
|
||||
|
||||
<target type="InstallLibrary" name="aqcgi"
|
||||
so_current="$(project_so_current)"
|
||||
so_age="$(project_so_age)"
|
||||
so_revision="$(project_so_revision)"
|
||||
install="$(libdir)" >
|
||||
|
||||
<includes type="c" >
|
||||
$(gmp_cflags)
|
||||
$(gwenhywfar_cflags)
|
||||
-I$(topsrcdir)/src
|
||||
-I$(topbuilddir)/src
|
||||
-I$(topbuilddir)
|
||||
-I$(topsrcdir)
|
||||
-I$(srcdir)
|
||||
</includes>
|
||||
|
||||
<includes type="tm2" >
|
||||
--include=$(builddir)
|
||||
--include=$(srcdir)
|
||||
--include=$(builddir)/../types
|
||||
--include=$(topsrcdir)/src/lib/typemaker2/c
|
||||
--include=$(topbuilddir)/src/lib/typemaker2/c
|
||||
</includes>
|
||||
|
||||
<define name="BUILDING_AQCGI" />
|
||||
|
||||
<setVar name="local/cflags">$(visibility_cflags)</setVar>
|
||||
|
||||
|
||||
<setVar name="tm2flags" >
|
||||
--api=AQCGI_API
|
||||
</setVar>
|
||||
|
||||
|
||||
<setVar name="local/typefiles" >
|
||||
</setVar>
|
||||
|
||||
<setVar name="local/built_sources" >
|
||||
</setVar>
|
||||
|
||||
<setVar name="local/built_headers_pub">
|
||||
</setVar>
|
||||
|
||||
|
||||
<setVar name="local/built_headers_priv" >
|
||||
</setVar>
|
||||
|
||||
|
||||
<headers dist="false" >
|
||||
$(local/built_headers_pub)
|
||||
$(local/built_headers_priv)
|
||||
</headers>
|
||||
|
||||
|
||||
<headers dist="true" >
|
||||
request_p.h
|
||||
</headers>
|
||||
|
||||
|
||||
<headers dist="true" >
|
||||
api.h
|
||||
cgi.h
|
||||
request.h
|
||||
</headers>
|
||||
|
||||
|
||||
<sources>
|
||||
$(local/typefiles)
|
||||
cgi.c
|
||||
request.c
|
||||
</sources>
|
||||
|
||||
|
||||
<data DIST="FALSE" generated="TRUE" >
|
||||
</data>
|
||||
|
||||
<extradist>
|
||||
</extradist>
|
||||
|
||||
|
||||
<useTargets>
|
||||
</useTargets>
|
||||
|
||||
|
||||
<libraries>
|
||||
$(gwenhywfar_libs)
|
||||
</libraries>
|
||||
|
||||
|
||||
<subdirs>
|
||||
</subdirs>
|
||||
|
||||
|
||||
</target>
|
||||
|
||||
</gwbuild>
|
||||
|
||||
57
src/aqcgi/api.h
Normal file
57
src/aqcgi/api.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/****************************************************************************
|
||||
* This file is part of the project AqCGI.
|
||||
* AqCGI (c) by 2024 Martin Preuss, all rights reserved.
|
||||
*
|
||||
* The license for this file can be found in the file COPYING which you
|
||||
* should have received along with this file.
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef AQCGI_API_H
|
||||
#define AQCGI_API_H
|
||||
|
||||
|
||||
#ifdef BUILDING_AQCGI
|
||||
# /* building AqGmControl */
|
||||
# if AQCGI_SYS_IS_WINDOWS
|
||||
# /* for windows */
|
||||
# ifdef __declspec
|
||||
# define AQCGI_API __declspec (dllexport)
|
||||
# else /* if __declspec */
|
||||
# define AQCGI_API
|
||||
# endif /* if NOT __declspec */
|
||||
# else
|
||||
# /* for non-win32 */
|
||||
# ifdef GCC_WITH_VISIBILITY_ATTRIBUTE
|
||||
# define AQCGI_API __attribute__((visibility("default")))
|
||||
# else
|
||||
# define AQCGI_API
|
||||
# endif
|
||||
# endif
|
||||
#else
|
||||
# /* not building AqFinance */
|
||||
# if AQCGI_SYS_IS_WINDOWS
|
||||
# /* for windows */
|
||||
# ifdef __declspec
|
||||
# define AQCGI_API __declspec (dllimport)
|
||||
# else /* if __declspec */
|
||||
# define AQCGI_API
|
||||
# endif /* if NOT __declspec */
|
||||
# else
|
||||
# /* for non-win32 */
|
||||
# define AQCGI_API
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef GCC_WITH_VISIBILITY_ATTRIBUTE
|
||||
# define AQCGI_EXPORT __attribute__((visibility("default")))
|
||||
# define AQCGI_NOEXPORT __attribute__((visibility("hidden")))
|
||||
#else
|
||||
# define AQCGI_EXPORT
|
||||
# define AQCGI_NOEXPORT
|
||||
#endif
|
||||
|
||||
|
||||
#define AQCGI_LOGDOMAIN "aqcgi"
|
||||
|
||||
#endif
|
||||
|
||||
622
src/aqcgi/cgi.c
Normal file
622
src/aqcgi/cgi.c
Normal file
@@ -0,0 +1,622 @@
|
||||
/****************************************************************************
|
||||
* This file is part of the project AqCGI.
|
||||
* AqCGI (c) by 2024 Martin Preuss, all rights reserved.
|
||||
*
|
||||
* The license for this file can be found in the file COPYING which you
|
||||
* should have received along with this file.
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include "./cgi.h"
|
||||
|
||||
#include <gwenhywfar/db.h>
|
||||
#include <gwenhywfar/syncio.h>
|
||||
#include <gwenhywfar/text.h>
|
||||
#include <gwenhywfar/debug.h>
|
||||
#include <gwenhywfar/cryptkeysym.h>
|
||||
#include <gwenhywfar/mdigest.h>
|
||||
#include <gwenhywfar/syncio_file.h>
|
||||
#include <gwenhywfar/error.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
|
||||
#define AQCGI_VARNAME_TEXT "text"
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* external declarations
|
||||
* ------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
extern char **environ;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* forward declarations
|
||||
* ------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static int _handleHeaders(AQCGI_REQUEST *rq);
|
||||
static int _handleBody(AQCGI_REQUEST *rq);
|
||||
static int _finishAndSendHeader(AQCGI_REQUEST *rq, GWEN_SYNCIO *sio);
|
||||
static int _sendBody(AQCGI_REQUEST *rq, GWEN_SYNCIO *sio);
|
||||
static int _unescapeUrlEncoded(const char *src, unsigned int srclen, char *buffer, unsigned int maxsize);
|
||||
static GWEN_DB_NODE *_parseEnv(void);
|
||||
static int _parseEnvVarIntoDb(const char *var, GWEN_DB_NODE *db);
|
||||
static void _parseCookieHeaderIntoDb(const char *s, GWEN_DB_NODE *db);
|
||||
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* implementations
|
||||
* ------------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
int AQCGI_ReadForcedStdInToBuffer(int len, GWEN_BUFFER *buf)
|
||||
{
|
||||
GWEN_SYNCIO *sio;
|
||||
int rv;
|
||||
|
||||
sio=GWEN_SyncIo_File_fromStdin();
|
||||
GWEN_Buffer_AllocRoom(buf, len);
|
||||
rv=GWEN_SyncIo_ReadForced(sio, (uint8_t *) GWEN_Buffer_GetPosPointer(buf), len);
|
||||
if (rv<0) {
|
||||
DBG_INFO(AQCGI_LOGDOMAIN, "here (%d)", rv);
|
||||
GWEN_SyncIo_free(sio);
|
||||
return rv;
|
||||
}
|
||||
GWEN_Buffer_IncrementPos(buf, rv);
|
||||
GWEN_Buffer_AdjustUsedBytes(buf);
|
||||
GWEN_SyncIo_free(sio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int AQCGI_GenerateSessionId(GWEN_BUFFER *buf)
|
||||
{
|
||||
GWEN_CRYPT_KEY *sk;
|
||||
uint8_t *ptr;
|
||||
uint32_t len;
|
||||
int rv;
|
||||
|
||||
sk=GWEN_Crypt_KeyDes3K_Generate(GWEN_Crypt_CryptMode_Cbc, 24, 2);
|
||||
if (sk==NULL) {
|
||||
DBG_INFO(AQCGI_LOGDOMAIN, "Could not generate DES key");
|
||||
return GWEN_ERROR_IO;
|
||||
}
|
||||
ptr=GWEN_Crypt_KeyDes3K_GetKeyDataPtr(sk);
|
||||
len=GWEN_Crypt_KeyDes3K_GetKeyDataLen(sk);
|
||||
|
||||
rv=GWEN_Text_ToHexBuffer((const char*) ptr, len, buf,0, 0, 0);
|
||||
if (rv<0) {
|
||||
DBG_INFO(AQCGI_LOGDOMAIN, "here (%d)", rv);
|
||||
GWEN_Crypt_Key_free(sk);
|
||||
return rv;
|
||||
}
|
||||
GWEN_Crypt_Key_free(sk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int AQCGI_HashMd256ToBuffer(const char *input, GWEN_BUFFER *buf)
|
||||
{
|
||||
GWEN_MDIGEST *md;
|
||||
int rv;
|
||||
int digestSize;
|
||||
uint8_t tmpBuf[32];
|
||||
|
||||
md=GWEN_MDigest_Sha256_new();
|
||||
digestSize=GWEN_MDigest_GetDigestSize(md);
|
||||
assert(digestSize<=sizeof(tmpBuf));
|
||||
rv=GWEN_MDigest_Digest(md, (const uint8_t*) input, strlen(input), tmpBuf, sizeof(tmpBuf));
|
||||
GWEN_MDigest_free(md);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Error hashing input (%d)", rv);
|
||||
return rv;
|
||||
}
|
||||
rv=GWEN_Text_ToHexBuffer((const char*) tmpBuf, digestSize, buf, 0, 0, 0);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Error converting hash to hex (%d)", rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int AQCGI_ParseUrlEncoded(const char *s, int contentLength, GWEN_DB_NODE *dbDecoded)
|
||||
{
|
||||
while(contentLength>0) {
|
||||
const char *sNameStart;
|
||||
int nameLen;
|
||||
|
||||
while((contentLength>0) && (*s<33)) {
|
||||
contentLength--;
|
||||
s++;
|
||||
}
|
||||
sNameStart=s;
|
||||
while((contentLength>0) && (*s!='=') && (*s!='&')) {
|
||||
contentLength--;
|
||||
s++;
|
||||
}
|
||||
nameLen=s-sNameStart;
|
||||
|
||||
if ((contentLength>0) && (*s=='=')) {
|
||||
const char *sValueStart;
|
||||
int valueLen;
|
||||
|
||||
s++;
|
||||
contentLength--;
|
||||
while((contentLength>0) && (*s<33)) {
|
||||
s++;
|
||||
contentLength--;
|
||||
}
|
||||
sValueStart=s;
|
||||
while((contentLength>0) && (*s!='&')) {
|
||||
contentLength--;
|
||||
s++;
|
||||
}
|
||||
valueLen=s-sValueStart;
|
||||
|
||||
if (nameLen && valueLen) {
|
||||
char sNameBuf[32];
|
||||
char sValueBuf[64];
|
||||
|
||||
if (_unescapeUrlEncoded(sNameStart, nameLen, sNameBuf, sizeof(sNameBuf))>=0 &&
|
||||
_unescapeUrlEncoded(sValueStart, valueLen, sValueBuf, sizeof(sValueBuf))>=0)
|
||||
GWEN_DB_SetCharValue(dbDecoded, 0, sNameBuf, sValueBuf);
|
||||
else {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Either name or value invalid in body, aborting");
|
||||
return GWEN_ERROR_BAD_DATA;
|
||||
}
|
||||
}
|
||||
if ((contentLength>0) && (*s=='&')) {
|
||||
contentLength--;
|
||||
s++;
|
||||
}
|
||||
}
|
||||
} /* while */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
AQCGI_REQUEST *AQCGI_ReadRequest(void)
|
||||
{
|
||||
AQCGI_REQUEST *rq;
|
||||
int rv;
|
||||
|
||||
rq=AQCGI_Request_new();
|
||||
|
||||
rv=_handleHeaders(rq);
|
||||
if (rv<0) {
|
||||
DBG_INFO(AQCGI_LOGDOMAIN, "here (%d)", rv);
|
||||
AQCGI_SendResponseWithStatus(rq, 500, "Internal error");
|
||||
AQCGI_Request_free(rq);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rv=_handleBody(rq);
|
||||
if (rv<0) {
|
||||
DBG_INFO(AQCGI_LOGDOMAIN, "here (%d)", rv);
|
||||
AQCGI_SendResponseWithStatus(rq, 500, "Internal error");
|
||||
AQCGI_Request_free(rq);
|
||||
return NULL;
|
||||
}
|
||||
return rq;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int AQCGI_SendResponseWithStatus(AQCGI_REQUEST *rq, int code, const char *text)
|
||||
{
|
||||
AQCGI_Request_SetResponseCode(rq, code);
|
||||
AQCGI_Request_SetResponseText(rq, text);
|
||||
return AQCGI_SendResponse(rq);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int AQCGI_SendResponse(AQCGI_REQUEST *rq)
|
||||
{
|
||||
GWEN_SYNCIO *sio;
|
||||
int rv;
|
||||
|
||||
sio=GWEN_SyncIo_File_fromStdout();
|
||||
assert(sio);
|
||||
|
||||
rv=_finishAndSendHeader(rq, sio);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "here (%d)", rv);
|
||||
GWEN_SyncIo_free(sio);
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv=_sendBody(rq, sio);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "here (%d)", rv);
|
||||
GWEN_SyncIo_free(sio);
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv=GWEN_SyncIo_Flush(sio);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "here (%d)", rv);
|
||||
GWEN_SyncIo_free(sio);
|
||||
return rv;
|
||||
}
|
||||
GWEN_SyncIo_free(sio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int _finishAndSendHeader(AQCGI_REQUEST *rq, GWEN_SYNCIO *sio)
|
||||
{
|
||||
int rv;
|
||||
GWEN_BUFFER *buf;
|
||||
int responseBodyLength=0;
|
||||
|
||||
buf=AQCGI_Request_GetBufferResponseBody(rq);
|
||||
if (buf)
|
||||
responseBodyLength=GWEN_Buffer_GetUsedBytes(buf);
|
||||
|
||||
AQCGI_Request_AddResponseStatusHeader(rq);
|
||||
buf=AQCGI_Request_GetBufferResponseHeader(rq);
|
||||
GWEN_Buffer_AppendArgs(buf, "Content-length: %d\n", responseBodyLength);
|
||||
if (buf && GWEN_Buffer_GetUsedBytes(buf)) {
|
||||
rv=GWEN_SyncIo_WriteForced(sio, (const uint8_t*)GWEN_Buffer_GetStart(buf), GWEN_Buffer_GetUsedBytes(buf));
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "here (%d)", rv);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
rv=GWEN_SyncIo_WriteForced(sio, (const uint8_t*)"\n", 1);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "here (%d)", rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int _sendBody(AQCGI_REQUEST *rq, GWEN_SYNCIO *sio)
|
||||
{
|
||||
int rv;
|
||||
GWEN_BUFFER *buf;
|
||||
|
||||
buf=AQCGI_Request_GetBufferResponseBody(rq);
|
||||
if (buf && GWEN_Buffer_GetUsedBytes(buf)) {
|
||||
rv=GWEN_SyncIo_WriteForced(sio, (const uint8_t*)GWEN_Buffer_GetStart(buf), GWEN_Buffer_GetUsedBytes(buf));
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "here (%d)", rv);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int _unescapeUrlEncoded(const char *src, unsigned int srclen, char *buffer, unsigned int maxsize)
|
||||
{
|
||||
unsigned int size;
|
||||
|
||||
size=0;
|
||||
|
||||
while (srclen>0 && *src) {
|
||||
unsigned char x;
|
||||
|
||||
x=(unsigned char)*src;
|
||||
if (
|
||||
(x>='A' && x<='Z') ||
|
||||
(x>='a' && x<='z') ||
|
||||
(x>='0' && x<='9') ||
|
||||
x==' ' ||
|
||||
x=='.' ||
|
||||
x==',' ||
|
||||
x=='.' ||
|
||||
x=='*' ||
|
||||
x=='?' ||
|
||||
x=='+' ||
|
||||
x=='-' ||
|
||||
x=='_'
|
||||
) {
|
||||
if (size<(maxsize-1)) {
|
||||
buffer[size++]=(x=='+')?' ':x;
|
||||
}
|
||||
else {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Buffer too small");
|
||||
return GWEN_ERROR_BUFFER_OVERFLOW;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (*src=='%') {
|
||||
unsigned char d1, d2;
|
||||
unsigned char c;
|
||||
|
||||
if (srclen<3) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Incomplete escape sequence (EOLN met)");
|
||||
return GWEN_ERROR_BAD_DATA;
|
||||
}
|
||||
/* skip '%' */
|
||||
src++;
|
||||
if (!(*src) || !isxdigit((int)*src)) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Incomplete escape sequence (no digits)");
|
||||
return GWEN_ERROR_BAD_DATA;
|
||||
}
|
||||
/* read first digit */
|
||||
d1=(unsigned char)(toupper(*src));
|
||||
|
||||
/* get second digit */
|
||||
src++;
|
||||
if (!(*src) || !isxdigit((int)*src)) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Incomplete escape sequence (only 1 digit)");
|
||||
return GWEN_ERROR_BAD_DATA;
|
||||
}
|
||||
d2=(unsigned char)(toupper(*src));
|
||||
/* compute character */
|
||||
d1-='0';
|
||||
if (d1>9)
|
||||
d1-=7;
|
||||
c=(d1<<4)&0xf0;
|
||||
d2-='0';
|
||||
if (d2>9)
|
||||
d2-=7;
|
||||
c+=(d2&0xf);
|
||||
/* store character */
|
||||
if (size<(maxsize-1))
|
||||
buffer[size++]=(char)c;
|
||||
else {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Buffer too small");
|
||||
return GWEN_ERROR_BUFFER_OVERFLOW;
|
||||
}
|
||||
srclen-=2;
|
||||
}
|
||||
else {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Found non-alphanum characters in escaped string (\"%s\")", src);
|
||||
return GWEN_ERROR_BAD_DATA;
|
||||
}
|
||||
}
|
||||
srclen--;
|
||||
src++;
|
||||
} /* while */
|
||||
|
||||
buffer[size]=0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
GWEN_DB_NODE *_parseEnv(void)
|
||||
{
|
||||
GWEN_DB_NODE *db;
|
||||
GWEN_DB_NODE *dbCookies;
|
||||
int i=0;
|
||||
const char *s;
|
||||
|
||||
db=GWEN_DB_Group_new("env");
|
||||
while( (s=environ[i++]) )
|
||||
_parseEnvVarIntoDb(s, db);
|
||||
|
||||
dbCookies=GWEN_DB_GetGroup(db, 0, "cookies");
|
||||
i=0;
|
||||
while( (s=GWEN_DB_GetCharValue(db, "HTTP_COOKIE", i++, NULL)) )
|
||||
_parseCookieHeaderIntoDb(s, dbCookies);
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int _parseEnvVarIntoDb(const char *var, GWEN_DB_NODE *db)
|
||||
{
|
||||
const char *s;
|
||||
|
||||
while(isblank(*var))
|
||||
var++;
|
||||
s=strchr(var, '=');
|
||||
if (s) {
|
||||
int nameLen;
|
||||
|
||||
nameLen=s-var;
|
||||
if (nameLen) {
|
||||
char *name;
|
||||
|
||||
name=GWEN_Text_strndup(var, nameLen);
|
||||
s++;
|
||||
if (*s) {
|
||||
GWEN_DB_SetCharValue(db, 0, name, s);
|
||||
free(name);
|
||||
return 0;
|
||||
}
|
||||
free(name);
|
||||
}
|
||||
}
|
||||
return GWEN_ERROR_BAD_DATA;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void _parseCookieHeaderIntoDb(const char *s, GWEN_DB_NODE *db)
|
||||
{
|
||||
while(*s) {
|
||||
const char *nameStart;
|
||||
char *t;
|
||||
|
||||
while(isblank(*s))
|
||||
s++;
|
||||
if (!*s)
|
||||
break;
|
||||
nameStart=s;
|
||||
t=strchr(s, '=');
|
||||
if (t) {
|
||||
int nameLen;
|
||||
|
||||
nameLen=t-nameStart;
|
||||
s=t+1;
|
||||
if (nameLen) {
|
||||
char *name;
|
||||
|
||||
name=GWEN_Text_strndup(nameStart, nameLen);
|
||||
if (*s) {
|
||||
const char *dataBegin;
|
||||
int dataLen;
|
||||
|
||||
while(isblank(*s))
|
||||
s++;
|
||||
dataBegin=s;
|
||||
while(*s && *s!=';')
|
||||
s++;
|
||||
dataLen=s-dataBegin;
|
||||
if (dataLen) {
|
||||
char *data;
|
||||
|
||||
data=GWEN_Text_strndup(dataBegin, dataLen);
|
||||
GWEN_DB_SetCharValue(db, 0, name, data);
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
free(name);
|
||||
}
|
||||
}
|
||||
s=strchr(s, ';');
|
||||
if (s==NULL)
|
||||
break;
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int _handleHeaders(AQCGI_REQUEST *rq)
|
||||
{
|
||||
GWEN_DB_NODE *db;
|
||||
const char *s;
|
||||
|
||||
db=_parseEnv();
|
||||
if (db==NULL) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Error parsing environment variables");
|
||||
return GWEN_ERROR_GENERIC;
|
||||
}
|
||||
AQCGI_Request_SetDbRequestHeader(rq, db);
|
||||
|
||||
s=GWEN_DB_GetCharValue(db, "REQUEST_METHOD", 0, NULL);
|
||||
if (s && *s) {
|
||||
if (strcasecmp(s, "GET")==0)
|
||||
AQCGI_Request_SetRequestMethod(rq, AQCGI_REQUEST_METHOD_GET);
|
||||
else if (strcasecmp(s, "POST")==0)
|
||||
AQCGI_Request_SetRequestMethod(rq, AQCGI_REQUEST_METHOD_POST);
|
||||
else {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Unhandled request method \"%s\"", s);
|
||||
return GWEN_ERROR_GENERIC;
|
||||
}
|
||||
}
|
||||
else {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Missing request method");
|
||||
return GWEN_ERROR_GENERIC;
|
||||
}
|
||||
|
||||
s=GWEN_DB_GetCharValue(db, "PATH_INFO", 0, NULL);
|
||||
if (s && *s) {
|
||||
GWEN_STRINGLIST *sl;
|
||||
|
||||
if (*s=='/')
|
||||
s++;
|
||||
sl=GWEN_StringList_fromString2(s, "/", 0,
|
||||
GWEN_TEXT_FLAGS_DEL_QUOTES |
|
||||
GWEN_TEXT_FLAGS_DEL_LEADING_BLANKS |
|
||||
GWEN_TEXT_FLAGS_DEL_TRAILING_BLANKS);
|
||||
AQCGI_Request_SetStringlistPath(rq, sl);
|
||||
}
|
||||
|
||||
s=GWEN_DB_GetCharValue(db, "QUERY_STRING", 0, NULL);
|
||||
if (s && *s) {
|
||||
GWEN_DB_NODE *dbQuery;
|
||||
int rv;
|
||||
|
||||
dbQuery=GWEN_DB_Group_new("query");
|
||||
rv=AQCGI_ParseUrlEncoded(s, strlen(s), dbQuery);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Error parsing query string \"%s\" (%d)", s, rv);
|
||||
GWEN_DB_Group_free(dbQuery);
|
||||
}
|
||||
else
|
||||
AQCGI_Request_SetDbQuery(rq, dbQuery);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int _handleBody(AQCGI_REQUEST *rq)
|
||||
{
|
||||
GWEN_DB_NODE *db;
|
||||
const char *s;
|
||||
int bodyLength=0;
|
||||
|
||||
db=AQCGI_Request_GetDbRequestHeader(rq);
|
||||
|
||||
s=GWEN_DB_GetCharValue(db, "CONTENT_LENGTH", 0, NULL);
|
||||
if (s && *s) {
|
||||
int i;
|
||||
|
||||
if (1==sscanf(s, "%d", &i)) {
|
||||
bodyLength=i;
|
||||
AQCGI_Request_SetRequestBodyLength(rq, i);
|
||||
}
|
||||
}
|
||||
|
||||
s=GWEN_DB_GetCharValue(db, "CONTENT_TYPE", 0, NULL);
|
||||
if (bodyLength>0 && s && *s && strcasecmp(s, "application/x-www-form-urlencoded")==0) {
|
||||
GWEN_BUFFER *buf;
|
||||
GWEN_DB_NODE *dbDecoded;
|
||||
int rv;
|
||||
|
||||
buf=GWEN_Buffer_new(0, bodyLength, 0, 1);
|
||||
rv=AQCGI_ReadForcedStdInToBuffer(bodyLength, buf);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Error reading body from stdin (%d)", rv);
|
||||
GWEN_Buffer_free(buf);
|
||||
return rv;
|
||||
}
|
||||
|
||||
dbDecoded=GWEN_DB_Group_new("body");
|
||||
rv=AQCGI_ParseUrlEncoded(GWEN_Buffer_GetStart(buf), GWEN_Buffer_GetUsedBytes(buf), dbDecoded);
|
||||
if (rv<0) {
|
||||
DBG_ERROR(AQCGI_LOGDOMAIN, "Error reading body from stdin (%d)", rv);
|
||||
GWEN_DB_Group_free(dbDecoded);
|
||||
GWEN_Buffer_free(buf);
|
||||
return rv;
|
||||
}
|
||||
AQCGI_Request_SetDbPostBody(rq, dbDecoded);
|
||||
GWEN_Buffer_free(buf);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
33
src/aqcgi/cgi.h
Normal file
33
src/aqcgi/cgi.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/****************************************************************************
|
||||
* This file is part of the project AqCGI.
|
||||
* AqCGI (c) by 2024 Martin Preuss, all rights reserved.
|
||||
*
|
||||
* The license for this file can be found in the file COPYING which you
|
||||
* should have received along with this file.
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef AQCGI_CGI_H
|
||||
#define AQCGI_CGI_H
|
||||
|
||||
#include <aqcgi/api.h>
|
||||
#include <aqcgi/request.h>
|
||||
|
||||
#include <gwenhywfar/db.h>
|
||||
#include <gwenhywfar/buffer.h>
|
||||
|
||||
|
||||
|
||||
AQCGI_API AQCGI_REQUEST *AQCGI_ReadRequest(void);
|
||||
AQCGI_API int AQCGI_SendResponseWithStatus(AQCGI_REQUEST *rq, int code, const char *text);
|
||||
AQCGI_API int AQCGI_SendResponse(AQCGI_REQUEST *rq);
|
||||
|
||||
AQCGI_API int AQCGI_ReadForcedStdInToBuffer(int len, GWEN_BUFFER *buf);
|
||||
AQCGI_API int AQCGI_GenerateSessionId(GWEN_BUFFER *buf);
|
||||
AQCGI_API int AQCGI_HashMd256ToBuffer(const char *input, GWEN_BUFFER *buf);
|
||||
AQCGI_API int AQCGI_ParseUrlEncoded(const char *s, int contentLength, GWEN_DB_NODE *dbDecoded);
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
265
src/aqcgi/request.c
Normal file
265
src/aqcgi/request.c
Normal file
@@ -0,0 +1,265 @@
|
||||
/****************************************************************************
|
||||
* This file is part of the project AqCGI.
|
||||
* AqCGI (c) by 2024 Martin Preuss, all rights reserved.
|
||||
*
|
||||
* The license for this file can be found in the file COPYING which you
|
||||
* should have received along with this file.
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
|
||||
#include "./request_p.h"
|
||||
|
||||
#include <gwenhywfar/misc.h>
|
||||
#include <gwenhywfar/debug.h>
|
||||
|
||||
|
||||
|
||||
GWEN_INHERIT_FUNCTIONS(AQCGI_REQUEST);
|
||||
|
||||
|
||||
|
||||
|
||||
AQCGI_REQUEST *AQCGI_Request_new(void)
|
||||
{
|
||||
AQCGI_REQUEST *rq;
|
||||
|
||||
GWEN_NEW_OBJECT(AQCGI_REQUEST, rq);
|
||||
GWEN_INHERIT_INIT(AQCGI_REQUEST, rq);
|
||||
rq->bufferResponseHeader=GWEN_Buffer_new(0, 256, 0, 1);
|
||||
rq->bufferResponseBody=GWEN_Buffer_new(0, 256, 0, 1);
|
||||
|
||||
return rq;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_free(AQCGI_REQUEST *rq)
|
||||
{
|
||||
if (rq) {
|
||||
GWEN_INHERIT_FINI(AQCGI_REQUEST, rq);
|
||||
GWEN_Buffer_free(rq->bufferResponseHeader);
|
||||
GWEN_Buffer_free(rq->bufferResponseBody);
|
||||
GWEN_StringList_free(rq->stringlistPath);
|
||||
GWEN_DB_Group_free(rq->dbRequestHeader);
|
||||
GWEN_DB_Group_free(rq->dbQuery);
|
||||
GWEN_DB_Group_free(rq->dbPostBody);
|
||||
free(rq->responseText);
|
||||
|
||||
GWEN_FREE_OBJECT(rq);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
GWEN_BUFFER *AQCGI_Request_GetBufferResponseHeader(const AQCGI_REQUEST *rq)
|
||||
{
|
||||
return rq?rq->bufferResponseHeader:NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_AddResponseHeaderData(AQCGI_REQUEST *rq, const char *s)
|
||||
{
|
||||
if (rq && s && *s) {
|
||||
GWEN_Buffer_AppendString(rq->bufferResponseHeader, s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_AddResponseStatusHeader(AQCGI_REQUEST *rq)
|
||||
{
|
||||
if (rq) {
|
||||
GWEN_Buffer_AppendArgs(rq->bufferResponseHeader, "Status: %d %s\n", rq->responseCode, rq->responseText?rq->responseText:"");
|
||||
if (rq->responseRedirect)
|
||||
GWEN_Buffer_AppendArgs(rq->bufferResponseHeader, "Location: %s\n", rq->responseRedirect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
GWEN_BUFFER *AQCGI_Request_GetBufferResponseBody(const AQCGI_REQUEST *rq)
|
||||
{
|
||||
return rq?rq->bufferResponseBody:NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_SetBufferResponseBody(AQCGI_REQUEST *rq, GWEN_BUFFER *buf)
|
||||
{
|
||||
if (rq) {
|
||||
GWEN_Buffer_free(rq->bufferResponseBody);
|
||||
rq->bufferResponseBody=buf;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_AddResponseBodyData(AQCGI_REQUEST *rq, const char *s)
|
||||
{
|
||||
if (rq && s && *s) {
|
||||
GWEN_Buffer_AppendString(rq->bufferResponseBody, s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int AQCGI_Request_GetRequestMethod(const AQCGI_REQUEST *rq)
|
||||
{
|
||||
return rq?rq->requestMethod:AQCGI_REQUEST_METHOD_GET;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_SetRequestMethod(AQCGI_REQUEST *rq, int m)
|
||||
{
|
||||
if (rq)
|
||||
rq->requestMethod=m;
|
||||
}
|
||||
|
||||
|
||||
|
||||
GWEN_STRINGLIST *AQCGI_Request_GetStringlistPath(const AQCGI_REQUEST *rq)
|
||||
{
|
||||
return rq?rq->stringlistPath:NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
GWEN_STRINGLISTENTRY *AQCGI_Request_GetFirstPathEntry(const AQCGI_REQUEST *rq)
|
||||
{
|
||||
return (rq && rq->stringlistPath)?GWEN_StringList_FirstEntry(rq->stringlistPath):NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_SetStringlistPath(AQCGI_REQUEST *rq, GWEN_STRINGLIST *sl)
|
||||
{
|
||||
if (rq) {
|
||||
GWEN_StringList_free(rq->stringlistPath);
|
||||
rq->stringlistPath=sl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int AQCGI_Request_GetRequestBodyLength(const AQCGI_REQUEST *rq)
|
||||
{
|
||||
return rq?rq->requestBodyLength:0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_SetRequestBodyLength(AQCGI_REQUEST *rq, int i)
|
||||
{
|
||||
if (rq)
|
||||
rq->requestBodyLength=i;
|
||||
}
|
||||
|
||||
|
||||
|
||||
GWEN_DB_NODE *AQCGI_Request_GetDbRequestHeader(const AQCGI_REQUEST *rq)
|
||||
{
|
||||
return rq?rq->dbRequestHeader:NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_SetDbRequestHeader(AQCGI_REQUEST *rq, GWEN_DB_NODE *db)
|
||||
{
|
||||
if (rq) {
|
||||
GWEN_DB_Group_free(rq->dbRequestHeader);
|
||||
rq->dbRequestHeader=db;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
GWEN_DB_NODE *AQCGI_Request_GetDbQuery(const AQCGI_REQUEST *rq)
|
||||
{
|
||||
return rq?rq->dbQuery:NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_SetDbQuery(AQCGI_REQUEST *rq, GWEN_DB_NODE *db)
|
||||
{
|
||||
if (rq) {
|
||||
GWEN_DB_Group_free(rq->dbQuery);
|
||||
rq->dbQuery=db;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
GWEN_DB_NODE *AQCGI_Request_GetDbPostBody(const AQCGI_REQUEST *rq)
|
||||
{
|
||||
return rq?rq->dbPostBody:NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_SetDbPostBody(AQCGI_REQUEST *rq, GWEN_DB_NODE *db)
|
||||
{
|
||||
if (rq) {
|
||||
GWEN_DB_Group_free(rq->dbPostBody);
|
||||
rq->dbPostBody=db;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int AQCGI_Request_GetResponseCode(const AQCGI_REQUEST *rq)
|
||||
{
|
||||
return rq?rq->responseCode:200;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_SetResponseCode(AQCGI_REQUEST *rq, int i)
|
||||
{
|
||||
if (rq)
|
||||
rq->responseCode=i;
|
||||
}
|
||||
|
||||
|
||||
const char *AQCGI_Request_GetResponseText(const AQCGI_REQUEST *rq)
|
||||
{
|
||||
return rq?rq->responseText:"Ok.";
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_SetResponseText(AQCGI_REQUEST *rq, const char *s)
|
||||
{
|
||||
if (rq) {
|
||||
free(rq->responseText);
|
||||
rq->responseText=s?strdup(s):NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const char *AQCGI_Request_GetResponseRedirect(const AQCGI_REQUEST *rq)
|
||||
{
|
||||
return rq?rq->responseRedirect:NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AQCGI_Request_SetResponseRedirect(AQCGI_REQUEST *rq, const char *s)
|
||||
{
|
||||
if (rq) {
|
||||
free(rq->responseRedirect);
|
||||
rq->responseRedirect=s?strdup(s):NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
72
src/aqcgi/request.h
Normal file
72
src/aqcgi/request.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/****************************************************************************
|
||||
* This file is part of the project AqCGI.
|
||||
* AqCGI (c) by 2024 Martin Preuss, all rights reserved.
|
||||
*
|
||||
* The license for this file can be found in the file COPYING which you
|
||||
* should have received along with this file.
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef AQCGI_REQUEST_H
|
||||
#define AQCGI_REQUEST_H
|
||||
|
||||
|
||||
#include <aqcgi/api.h>
|
||||
|
||||
#include <gwenhywfar/stringlist.h>
|
||||
#include <gwenhywfar/inherit.h>
|
||||
#include <gwenhywfar/db.h>
|
||||
|
||||
|
||||
enum {
|
||||
AQCGI_REQUEST_METHOD_GET=0,
|
||||
AQCGI_REQUEST_METHOD_POST
|
||||
};
|
||||
|
||||
|
||||
|
||||
typedef struct AQCGI_REQUEST AQCGI_REQUEST;
|
||||
GWEN_INHERIT_FUNCTION_LIB_DEFS(AQCGI_REQUEST, AQCGI_API);
|
||||
|
||||
|
||||
|
||||
AQCGI_API AQCGI_REQUEST *AQCGI_Request_new(void);
|
||||
AQCGI_API void AQCGI_Request_free(AQCGI_REQUEST *rq);
|
||||
|
||||
AQCGI_API GWEN_BUFFER *AQCGI_Request_GetBufferResponseHeader(const AQCGI_REQUEST *rq);
|
||||
AQCGI_API void AQCGI_Request_AddResponseHeaderData(AQCGI_REQUEST *rq, const char *s);
|
||||
AQCGI_API void AQCGI_Request_AddResponseStatusHeader(AQCGI_REQUEST *rq);
|
||||
|
||||
AQCGI_API GWEN_BUFFER *AQCGI_Request_GetBufferResponseBody(const AQCGI_REQUEST *rq);
|
||||
AQCGI_API void AQCGI_Request_SetBufferResponseBody(AQCGI_REQUEST *rq, GWEN_BUFFER *buf);
|
||||
AQCGI_API void AQCGI_Request_AddResponseBodyData(AQCGI_REQUEST *rq, const char *s);
|
||||
|
||||
AQCGI_API int AQCGI_Request_GetRequestMethod(const AQCGI_REQUEST *rq);
|
||||
AQCGI_API void AQCGI_Request_SetRequestMethod(AQCGI_REQUEST *rq, int m);
|
||||
|
||||
AQCGI_API GWEN_STRINGLIST *AQCGI_Request_GetStringlistPath(const AQCGI_REQUEST *rq);
|
||||
AQCGI_API void AQCGI_Request_SetStringlistPath(AQCGI_REQUEST *rq, GWEN_STRINGLIST *sl);
|
||||
AQCGI_API GWEN_STRINGLISTENTRY *AQCGI_Request_GetFirstPathEntry(const AQCGI_REQUEST *rq);
|
||||
|
||||
AQCGI_API int AQCGI_Request_GetRequestBodyLength(const AQCGI_REQUEST *rq);
|
||||
AQCGI_API void AQCGI_Request_SetRequestBodyLength(AQCGI_REQUEST *rq, int i);
|
||||
|
||||
AQCGI_API GWEN_DB_NODE *AQCGI_Request_GetDbRequestHeader(const AQCGI_REQUEST *rq);
|
||||
AQCGI_API void AQCGI_Request_SetDbRequestHeader(AQCGI_REQUEST *rq, GWEN_DB_NODE *db);
|
||||
|
||||
AQCGI_API GWEN_DB_NODE *AQCGI_Request_GetDbQuery(const AQCGI_REQUEST *rq);
|
||||
AQCGI_API void AQCGI_Request_SetDbQuery(AQCGI_REQUEST *rq, GWEN_DB_NODE *db);
|
||||
|
||||
AQCGI_API GWEN_DB_NODE *AQCGI_Request_GetDbPostBody(const AQCGI_REQUEST *rq);
|
||||
AQCGI_API void AQCGI_Request_SetDbPostBody(AQCGI_REQUEST *rq, GWEN_DB_NODE *db);
|
||||
|
||||
AQCGI_API int AQCGI_Request_GetResponseCode(const AQCGI_REQUEST *rq);
|
||||
AQCGI_API void AQCGI_Request_SetResponseCode(AQCGI_REQUEST *rq, int i);
|
||||
|
||||
AQCGI_API const char *AQCGI_Request_GetResponseText(const AQCGI_REQUEST *rq);
|
||||
AQCGI_API void AQCGI_Request_SetResponseText(AQCGI_REQUEST *rq, const char *s);
|
||||
|
||||
AQCGI_API const char *AQCGI_Request_GetResponseRedirect(const AQCGI_REQUEST *rq);
|
||||
AQCGI_API void AQCGI_Request_SetResponseRedirect(AQCGI_REQUEST *rq, const char *s);
|
||||
|
||||
|
||||
#endif
|
||||
37
src/aqcgi/request_p.h
Normal file
37
src/aqcgi/request_p.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/****************************************************************************
|
||||
* This file is part of the project AqCGI.
|
||||
* AqCGI (c) by 2024 Martin Preuss, all rights reserved.
|
||||
*
|
||||
* The license for this file can be found in the file COPYING which you
|
||||
* should have received along with this file.
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef AQCGI_REQUEST_P_H
|
||||
#define AQCGI_REQUEST_P_H
|
||||
|
||||
#include "./request.h"
|
||||
|
||||
|
||||
|
||||
struct AQCGI_REQUEST {
|
||||
GWEN_INHERIT_ELEMENT(AQCGI_REQUEST);
|
||||
GWEN_BUFFER *bufferResponseHeader;
|
||||
GWEN_BUFFER *bufferResponseBody;
|
||||
|
||||
int requestMethod;
|
||||
GWEN_STRINGLIST *stringlistPath;
|
||||
int requestBodyLength;
|
||||
|
||||
GWEN_DB_NODE *dbRequestHeader;
|
||||
GWEN_DB_NODE *dbQuery;
|
||||
GWEN_DB_NODE *dbPostBody;
|
||||
|
||||
int responseCode;
|
||||
char *responseText;
|
||||
|
||||
char *responseRedirect;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user