diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0187b3d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+build/
+*~
diff --git a/0BUILD b/0BUILD
new file mode 100644
index 0000000..17a8e9e
--- /dev/null
+++ b/0BUILD
@@ -0,0 +1,260 @@
+
+
+
+
+
+
+
+
+ $(project_name)
+
+
+
+ $(project_vmajor).$(project_vminor).$(project_vpatchlevel)
+
+ $(project_vmajor).$(project_vminor).$(project_vpatchlevel).$(project_vbuild)$(project_vtag)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(option_prefix)
+ $(option_prefix)/etc
+ $(option_prefix)/bin
+ $(option_prefix)/lib/cgi-bin
+ $(option_prefix)/lib
+ $(option_prefix)/include
+ $(option_prefix)/share
+ $(option_prefix)/share/locale
+
+ $(libdir)/$(package)
+ $(includedir)/$(package)
+ $(datadir)/$(package)
+
+
+
+
+
+ -ggdb -Wall -O0
+ -ggdb -Wall -O0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(pkglibdir)/plugins/$(project_so_effective)
+
+
+
+ 1
+ etc
+ share/locale
+ share/aqfinance"
+
+
+
+ 0
+
+
+ etc
+ share/locale
+ share/aqfinance
+
+
+ $(sysconfdir)
+ $(datadir)/locale
+ $(datadir)/aqfinance
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -fvisibility=hidden
+
+
+
+ -fvisibility=hidden
+
+
+
+
+
+ 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
+
+
+
+
+ setlocale
+ memmove
+ memset
+ strcasecmp
+ strdup
+ strerror
+ snprintf
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -C -c -ki18n -ktr2i18n -kI18N -kI18S -kI18N_NOOP -ktranslate -kaliasLocale -ktr -ktrUtf8
+ --msgid-bugs-address=aqbanking-user@lists.aqbanking.de
+ -o $(OUTPUT[0]) $(INPUT[])
+
+
+
+ Extracting I18N strings into $(OUTPUT[0])
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ --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[])
+
+
+
+ Formatting source files in-place.
+
+
+
+
+
+
+ AUTHORS
+ COPYING
+ README
+
+
+
+
+ src
+
+
+
+
+
+
+
diff --git a/src/0BUILD b/src/0BUILD
new file mode 100644
index 0000000..1fd25c9
--- /dev/null
+++ b/src/0BUILD
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+ aqcgi
+
+
+
+
diff --git a/src/aqcgi/0BUILD b/src/aqcgi/0BUILD
new file mode 100644
index 0000000..8b78e27
--- /dev/null
+++ b/src/aqcgi/0BUILD
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+ $(gmp_cflags)
+ $(gwenhywfar_cflags)
+ -I$(topsrcdir)/src
+ -I$(topbuilddir)/src
+ -I$(topbuilddir)
+ -I$(topsrcdir)
+ -I$(srcdir)
+
+
+
+ --include=$(builddir)
+ --include=$(srcdir)
+ --include=$(builddir)/../types
+ --include=$(topsrcdir)/src/lib/typemaker2/c
+ --include=$(topbuilddir)/src/lib/typemaker2/c
+
+
+
+
+ $(visibility_cflags)
+
+
+
+ --api=AQCGI_API
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(local/built_headers_pub)
+ $(local/built_headers_priv)
+
+
+
+
+ request_p.h
+
+
+
+
+ api.h
+ cgi.h
+ request.h
+
+
+
+
+ $(local/typefiles)
+ cgi.c
+ request.c
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(gwenhywfar_libs)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/aqcgi/api.h b/src/aqcgi/api.h
new file mode 100644
index 0000000..5792725
--- /dev/null
+++ b/src/aqcgi/api.h
@@ -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
+
diff --git a/src/aqcgi/cgi.c b/src/aqcgi/cgi.c
new file mode 100644
index 0000000..c105825
--- /dev/null
+++ b/src/aqcgi/cgi.c
@@ -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
+#endif
+
+#include "./cgi.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+
+#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;
+}
+
+
+
diff --git a/src/aqcgi/cgi.h b/src/aqcgi/cgi.h
new file mode 100644
index 0000000..8e889ae
--- /dev/null
+++ b/src/aqcgi/cgi.h
@@ -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
+#include
+
+#include
+#include
+
+
+
+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
+
diff --git a/src/aqcgi/request.c b/src/aqcgi/request.c
new file mode 100644
index 0000000..77af71f
--- /dev/null
+++ b/src/aqcgi/request.c
@@ -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
+#endif
+
+
+#include "./request_p.h"
+
+#include
+#include
+
+
+
+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;
+ }
+}
+
+
+
+
+
diff --git a/src/aqcgi/request.h b/src/aqcgi/request.h
new file mode 100644
index 0000000..dd49da5
--- /dev/null
+++ b/src/aqcgi/request.h
@@ -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
+
+#include
+#include
+#include
+
+
+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
diff --git a/src/aqcgi/request_p.h b/src/aqcgi/request_p.h
new file mode 100644
index 0000000..63441a2
--- /dev/null
+++ b/src/aqcgi/request_p.h
@@ -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