Initial import.

This commit is contained in:
Martin Preuss
2024-07-09 23:10:08 +02:00
parent 82c6a21d4c
commit f75e752127
10 changed files with 1462 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
build/
*~

260
0BUILD Normal file
View File

@@ -0,0 +1,260 @@
<?xml?>
<!--
Build ing this project using gwenbuild (part of library "gwenhywfar"):
1) create a build folder (e.g. "build")
2) change into that folder
3) run "gwbuild PATH_TO_SRC"
4) run "gwbuild -p"
5) run "gwbuild -Btm2builder"
6) run "gwbuild -j14"
-->
<gwbuild requiredVersion="5.6.0" >
<project name="aqcgi"
vmajor="0" vminor="0" vpatchlevel="1" vbuild="0" vtag="beta"
so_current="0" so_age="0" so_revision="0"
write_config_h="TRUE"
>
<setVar name="package">$(project_name)</setVar>
<define name="PACKAGE" value="$(package)" quoted="TRUE" />
<!-- version -->
<setVar name="AQCGI_VERSION_STRING">$(project_vmajor).$(project_vminor).$(project_vpatchlevel)</setVar>
<setVar name="AQCGI_VERSION_FULL_STRING">
$(project_vmajor).$(project_vminor).$(project_vpatchlevel).$(project_vbuild)$(project_vtag)
</setVar>
<define name="AQCGI_VERSION_MAJOR" value="$(project_vmajor)" />
<define name="AQCGI_VERSION_MINOR" value="$(project_vminor)" />
<define name="AQCGI_VERSION_PATCHLEVEL" value="$(project_vpatchlevel)" />
<define name="AQCGI_VERSION_BUILD" value="$(project_vbuild)" />
<define name="AQCGI_VERSION_TAG" value="$(project_vtag)" />
<define name="AQCGI_VERSION_STRING" value="$(AQCGI_VERSION_STRING)" quoted="TRUE" />
<define name="AQCGI_VERSION_FULL_STRING" value="$(AQCGI_VERSION_FULL_STRING)" quoted="TRUE" />
<define name="AQCGI_VERSION_FINTS_STRING" value="$(AQCGI_VERSION_STRING)" quoted="TRUE" />
<!-- prefix handling -->
<option id="prefix" type="string">
<default>/usr/local</default>
</option>
<setVar name="prefix">$(option_prefix)</setVar>
<setVar name="sysconfdir">$(option_prefix)/etc</setVar>
<setVar name="bindir">$(option_prefix)/bin</setVar>
<setVar name="cgibindir">$(option_prefix)/lib/cgi-bin</setVar>
<setVar name="libdir">$(option_prefix)/lib</setVar>
<setVar name="includedir">$(option_prefix)/include</setVar>
<setVar name="datadir">$(option_prefix)/share</setVar>
<setVar name="localedir">$(option_prefix)/share/locale</setVar>
<setVar name="pkglibdir">$(libdir)/$(package)</setVar>
<setVar name="pkgincludedir">$(includedir)/$(package)</setVar>
<setVar name="pkgdatadir">$(datadir)/$(package)</setVar>
<!-- options -->
<option id="debug" type="string">
<default>TRUE</default>
<choices>TRUE FALSE</choices>
</option>
<ifVarMatches name="option_debug" value="TRUE" >
<setVar name="CFLAGS">-ggdb -Wall -O0</setVar>
<setVar name="CXXFLAGS">-ggdb -Wall -O0</setVar>
</ifVarMatches>
<option id="enable_testcode" type="string">
<default>TRUE</default>
<choices>TRUE FALSE</choices>
</option>
<ifVarMatches name="option_enable_testcode" value="TRUE" >
<define name="AQFINANCE_ENABLE_TESTCODE" value="1" />
</ifVarMatches>
<option id="local_install" type="string">
<default>FALSE</default>
<choices>TRUE FALSE</choices>
</option>
<ifVarMatches name="option_local_install" value="TRUE" >
<define name="ENABLE_LOCAL_INSTALL" value="1" />
</ifVarMatches>
<!-- enable test code -->
<option id="enable_testcode" type="string">
<default>TRUE</default>
<choices>TRUE FALSE</choices>
</option>
<ifVarMatches name="option_enable_testcode" value="TRUE" >
<define name="AQCGI_ENABLE_TESTCODE" value="1" />
</ifVarMatches>
<option id="packver" type="string">
<default>$(AQCGI_VERSION_FULL_STRING)</default>
</option>
<!-- paths -->
<setVar name="aqcgi_plugin_installdir">$(pkglibdir)/plugins/$(project_so_effective)</setVar>
<ifVarMatches name="GWBUILD_SYSTEM" value="windows" > <!-- long version of IF statement with THEN and ELSE -->
<then>
<define name="OS_WIN32" value="1" />
<setVar name="aqcgi_sys_is_windows">1</setVar>
<setVar name="aqcgi_cfg_searchdir">etc</setVar>
<setVar name="aqcgi_locale_searchdir">share/locale</setVar>
<setVar name="aqcgi_data_searchdir">share/aqfinance"</setVar>
</then>
<else>
<define name="OS_POSIX" value="1" />
<setVar name="aqcgi_sys_is_windows">0</setVar>
<ifVarMatches name="option_local_install" value="TRUE" >
<then>
<setVar name="aqcgi_cfg_searchdir">etc</setVar>
<setVar name="aqcgi_locale_searchdir">share/locale</setVar>
<setVar name="aqcgi_data_searchdir">share/aqfinance</setVar>
</then>
<else>
<setVar name="aqcgi_cfg_searchdir">$(sysconfdir)</setVar>
<setVar name="aqcgi_locale_searchdir">$(datadir)/locale</setVar>
<setVar name="aqcgi_data_searchdir">$(datadir)/aqfinance</setVar>
</else>
</ifVarMatches>
</else>
</ifVarMatches>
<define name="AQCGI_SYS_IS_WINDOWS" value="$(aqcgi_sys_is_windows)" />
<!-- dependencies ( pkg-config and libs) -->
<dependencies>
<dep id="gwenhywfar" name="gwenhywfar" minversion="5.5.1.1" required="TRUE" />
</dependencies>
<checklibs>
</checklibs>
<!-- symbol visibility -->
<checkCompiler>
<arg name="has_symbol_visibility">-fvisibility=hidden</arg>
</checkCompiler>
<ifVarMatches name="has_symbol_visibility" value="TRUE" >
<setVar name="visibility_cflags">-fvisibility=hidden</setVar>
<define name="GCC_WITH_VISIBILITY_ATTRIBUTE" />
</ifVarMatches>
<checkheaders>
locale.h libintl.h iconv.h
fcntl.h stdlib.h string.h unistd.h
assert.h ctype.h errno.h fcntl.h stdio.h stdlib.h string.h strings.h locale.h
netinet/in.h signal.h
</checkheaders>
<checkfunctions type="c" >
setlocale
memmove
memset
strcasecmp
strdup
strerror
snprintf
</checkfunctions>
<checkProgs>
<prog cmd="xgettext" id="xgettext" />
<prog cmd="astyle" id="astyle" />
</checkProgs>
<define name="LOCALEDIR" value="$(datadir)/locale" quoted="TRUE" />
<define name="OS_TYPE" value="$(GWBUILD_SYSTEM)" quoted="TRUE" />
<define name="OS_SHORTNAME" value="$(GWBUILD_SYSTEM)" quoted="TRUE" />
<!-- Build with "gwbuild -B gettext-src" -->
<ifVarMatches name="xgettext_EXISTS" value="TRUE" >
<buildFiles name="gettext-src" auto="FALSE" >
<input>
<files match="*.c" />
<files match="*.cpp" />
</input>
<output>
aqfinance.pot
</output>
<cmd tool="$(xgettext)" checkDates="TRUE" deleteOutFileFirst="TRUE" >
-C -c -ki18n -ktr2i18n -kI18N -kI18S -kI18N_NOOP -ktranslate -kaliasLocale -ktr -ktrUtf8
--msgid-bugs-address=aqbanking-user@lists.aqbanking.de
-o $(OUTPUT[0]) $(INPUT[])
</cmd>
<buildMessage>
Extracting I18N strings into $(OUTPUT[0])
</buildMessage>
</buildFiles>
</ifVarMatches>
<!-- Build with "gwbuild -B format-src" -->
<ifVarMatches name="astyle_EXISTS" value="TRUE" >
<buildFiles name="format-src" auto="FALSE" >
<input>
<files match="*.c" />
<files match="*.cpp" />
<files match="*.h" />
</input>
<output>
</output>
<cmd tool="$(astyle)" checkDates="FALSE" >
--style=stroustrup
-s2
--min-conditional-indent=0
--indent-labels
--max-continuation-indent=80
--pad-comma
--pad-header
--unpad-paren
--align-pointer=name
--break-closing-braces
--break-one-line-headers
--attach-return-type
--convert-tabs
--max-code-length=120
--break-after-logical
--preserve-date
--suffix=none
$(INPUT[])
</cmd>
<buildMessage>
Formatting source files in-place.
</buildMessage>
</buildFiles>
</ifVarMatches>
<extradist>
AUTHORS
COPYING
README
</extradist>
<subdirs>
src
</subdirs>
</project>
</gwbuild>

11
src/0BUILD Normal file
View File

@@ -0,0 +1,11 @@
<?xml?>
<gwbuild>
<subdirs>
aqcgi
</subdirs>
</gwbuild>

103
src/aqcgi/0BUILD Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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