/****************************************************************************
 * This file is part of the project AqHome.
 * AqHome (c) by 2025 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 "./mservice_p.h"

#include <aqcgi/service/module.h>

#include <gwenhywfar/debug.h>
#include <gwenhywfar/text.h>

#include <string.h>
#include <ctype.h>
#include <time.h>

#include <sys/stat.h>
#include <string.h>
#include <errno.h>



/* ------------------------------------------------------------------------------------------------
 * defs and enums
 * ------------------------------------------------------------------------------------------------
 */

#define AQH_MOD_SERVICE_HEADERFILE "header.html"
#define AQH_MOD_SERVICE_FOOTERFILE "footer.html"

#define GBAS GWEN_Buffer_AppendString
#define GBAA GWEN_Buffer_AppendArgs



/* ------------------------------------------------------------------------------------------------
 * global vars
 * ------------------------------------------------------------------------------------------------
 */

GWEN_INHERIT(AQCGI_MODULE, AQH_MOD_SERVICE)



/* ------------------------------------------------------------------------------------------------
 * forward declarations
 * ------------------------------------------------------------------------------------------------
 */

static void GWENHYWFAR_CB _freeData(void *bp, void *p);
static void _calcUserModPerms(AQCGI_MODULE *m, const AQCGI_USER *user);
static uint32_t _calcRolePerms(const AQCGI_MODULE *m, const AQCGI_MODULE_PERMS *modPerms);



/* ------------------------------------------------------------------------------------------------
 * code
 * ------------------------------------------------------------------------------------------------
 */

void AQH_ModService_Extend(AQCGI_MODULE *m, AQCGI_SERVICE *sv, const char *baseFolder)
{
  AQH_MOD_SERVICE *xm;

  GWEN_NEW_OBJECT(AQH_MOD_SERVICE, xm);
  GWEN_INHERIT_SETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m, xm, _freeData);

  xm->service=sv;
  xm->baseFolder=(baseFolder && *baseFolder)?strdup(baseFolder):NULL;
}



void _freeData(GWEN_UNUSED void *bp, void *p)
{
  AQH_MOD_SERVICE *xm;

  xm=(AQH_MOD_SERVICE*) p;
  free(xm->baseFolder);
  GWEN_FREE_OBJECT(xm);
}



AQCGI_SERVICE *AQH_ModService_GetService(const AQCGI_MODULE *m)
{
  if (m) {
    AQH_MOD_SERVICE *xm;

    xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m);
    if (xm) {
      return xm->service;
    }
  }
  return NULL;
}



const char *AQH_ModService_GetBaseFolder(const AQCGI_MODULE *m)
{
  if (m) {
    AQH_MOD_SERVICE *xm;

    xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m);
    if (xm) {
      return xm->baseFolder;
    }
  }
  return NULL;
}



uint32_t AQH_ModService_GetUserPerms(const AQCGI_MODULE *m)
{
  if (m) {
    AQH_MOD_SERVICE *xm;

    xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m);
    if (xm) {
      return xm->userPerms;
    }
  }
  return 0;
}



void AQH_ModService_SetHandleRequestFn(AQCGI_MODULE *m, AQH_MODSERVICE_HANDLEREQUEST_FN fn)
{
  if (m) {
    AQH_MOD_SERVICE *xm;

    xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m);
    if (xm) {
      xm->handleRequestFn=fn;
    }
  }
}



void AQH_ModService_SetLoadSubModuleFn(AQCGI_MODULE *m, AQH_MODSERVICE_LOADSUBMODULE_FN fn)
{
  if (m) {
    AQH_MOD_SERVICE *xm;

    xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m);
    if (xm) {
      xm->loadSubModuleFn=fn;
    }
  }
}



void AQH_ModService_SetAddHeaderFn(AQCGI_MODULE *m, AQH_MODSERVICE_ADDHEADER_FN fn)
{
  if (m) {
    AQH_MOD_SERVICE *xm;

    xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m);
    if (xm) {
      xm->addHeaderFn=fn;
    }
  }
}



void AQH_ModService_SetAddFooterFn(AQCGI_MODULE *m, AQH_MODSERVICE_ADDFOOTER_FN fn)
{
  if (m) {
    AQH_MOD_SERVICE *xm;

    xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m);
    if (xm) {
      xm->addFooterFn=fn;
    }
  }
}



void AQH_ModService_AddHeader(AQCGI_MODULE *m, const char *lang, GWEN_BUFFER *dbuf)
{
  if (m && dbuf) {
    AQH_MOD_SERVICE *xm;

    xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m);
    if (xm) {
      if (xm->addHeaderFn)
        xm->addHeaderFn(m, lang, dbuf);
      else {
        AQCGI_MODULE *mParent;

        mParent=AQCGI_Module_Tree2_GetParent(m);
        if (mParent)
          AQH_ModService_AddHeader(mParent, lang, dbuf);
        AQH_ModService_ReadStaticFile(m, lang, AQH_MOD_SERVICE_HEADERFILE, dbuf);
      }
    }
  }
}



void AQH_ModService_AddFooter(AQCGI_MODULE *m, const char *lang, GWEN_BUFFER *dbuf)
{
  if (m && dbuf) {
    AQH_MOD_SERVICE *xm;

    xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m);
    if (xm) {
      if (xm->addFooterFn)
        xm->addFooterFn(m, lang, dbuf);
      else {
        AQCGI_MODULE *mParent;

        AQH_ModService_ReadStaticFile(m, lang, AQH_MOD_SERVICE_FOOTERFILE, dbuf);

        mParent=AQCGI_Module_Tree2_GetParent(m);
        if (mParent)
          AQH_ModService_AddFooter(mParent, lang, dbuf);
      }
    }
  }
}



int AQH_ModService_RespondWithFile(AQCGI_MODULE *m, AQCGI_REQUEST *rq, const char *lang, const char *sFilename, GWEN_BUFFER *dbuf)
{
  int rv;

  rv=AQH_ModService_ReadStaticFile(m, lang, sFilename, dbuf);
  if (rv<0) {
    AQCGI_Request_SetResponseCode(rq, 500);
    AQCGI_Request_SetResponseText(rq, "Internal Error");
    return GWEN_ERROR_INTERNAL;
  }
  AQCGI_Request_SetResponseCode(rq, 200);
  AQCGI_Request_SetResponseText(rq, "Ok");

  return 0;
}



int AQH_ModService_RespondWithVarFile(AQCGI_MODULE *m, AQCGI_REQUEST *rq,
				      const char *lang,
				      const char *sFilename,
				      GWEN_DB_NODE *dbVars,
				      GWEN_BUFFER *dbuf)
{
  int rv;
  GWEN_BUFFER *fbuf;

  fbuf=GWEN_Buffer_new(0, 256, 0, 1);
  rv=AQH_ModService_ReadStaticFile(m, lang, sFilename, fbuf);
  if (rv<0) {
    AQCGI_Request_SetResponseCode(rq, 500);
    AQCGI_Request_SetResponseText(rq, "Internal Error");
    GWEN_Buffer_free(fbuf);
    return GWEN_ERROR_INTERNAL;
  }
  else {
    GWEN_DB_ReplaceVars(dbVars, GWEN_Buffer_GetStart(fbuf), dbuf);
    AQCGI_Request_SetResponseCode(rq, 200);
    AQCGI_Request_SetResponseText(rq, "Ok");
    GWEN_Buffer_free(fbuf);
    return 0;
  }
}



int AQH_ModService_HandleRequest(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, const char *sLastPathElem)
{
  if (m) {
    AQH_MOD_SERVICE *xm;

    xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m);
    if (xm && xm->handleRequestFn)
      return xm->handleRequestFn(m, rq, session, sLastPathElem);
  }
  return GWEN_ERROR_NOT_IMPLEMENTED;
}



AQCGI_MODULE *AQH_ModService_LoadSubModule(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, const char *sModuleName)
{
  if (m) {
    AQH_MOD_SERVICE *xm;

    xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m);
    if (xm && xm->loadSubModuleFn) {
      AQCGI_MODULE *mReturn;

      mReturn=xm->loadSubModuleFn(m, rq, session, sModuleName);
      if (mReturn)
        AQH_ModService_CalcSessionModPerms(mReturn, session);
      return mReturn;
    }
  }
  return NULL;
}



int AQH_ModService_ReadStaticFile(AQCGI_MODULE *m, const char *lang, const char *filename, GWEN_BUFFER *dbuf)
{
  if (m && filename && dbuf) {
    AQH_MOD_SERVICE *xm;

    xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m);
    if (xm) {
      GWEN_BUFFER *fbuf;
      int rv;

      fbuf=GWEN_Buffer_new(0, 256, 0, 1);
      GWEN_Buffer_AppendString(fbuf, xm->baseFolder);
      GWEN_Buffer_AppendString(fbuf, GWEN_DIR_SEPARATOR_S);
      GWEN_Buffer_AppendString(fbuf, (lang && *lang)?lang:"en");
      GWEN_Buffer_AppendString(fbuf, GWEN_DIR_SEPARATOR_S);
      GWEN_Buffer_AppendString(fbuf, filename);
      DBG_ERROR(NULL, "Reading file \"%s\"", GWEN_Buffer_GetStart(fbuf));
      rv=GWEN_SyncIo_Helper_ReadFile(GWEN_Buffer_GetStart(fbuf), dbuf);
      if (rv<0) {
        DBG_ERROR(NULL, "Read(%s): %d", GWEN_Buffer_GetStart(fbuf), rv);
        GWEN_Buffer_free(fbuf);
        return rv;
      }
      GWEN_Buffer_free(fbuf);
      return 0;
    }
  }
  DBG_ERROR(NULL, "Any arg is missing (or is not a AQH_MOD_SERVICE object)");
  return GWEN_ERROR_INTERNAL;
}



AQCGI_SESSION *AQH_ModService_ReadSession(AQCGI_MODULE *m, AQCGI_REQUEST *rq)
{
  AQCGI_SERVICE *sv;
  GWEN_DB_NODE *db;
  const char *s;

  sv=AQH_ModService_GetService(m);
  db=AQCGI_Request_GetDbRequestHeader(rq);
  s=GWEN_DB_GetCharValue(db, "cookies/session", 0, NULL);
  if (s && *s) {
    AQCGI_SESSION *session;

    session=AQCGI_Service_LoadSession(sv, s);
    if (session==NULL) {
      DBG_ERROR(NULL, "Session \"%s\" not found", s);
      return NULL;
    }
    else {
      const char *sUserName;
      GWEN_BUFFER *tbuf;

      sUserName=AQCGI_Session_GetUserAlias(session);
      if (sUserName && *sUserName) {
        AQCGI_USER *user;

        user=AQCGI_Service_LoadUser(sv, sUserName);
        if (user==NULL) {
          DBG_ERROR(NULL, "User \"%s\" not found", sUserName);
          AQCGI_Session_free(session);
          return NULL;
        }
        else {
          DBG_ERROR(NULL, "User is \"%s\"", sUserName);
        }
        AQCGI_Session_SetUser(session, user);
      }
      /* renew session cookie */
      tbuf=GWEN_Buffer_new(0, 256, 0, 1);
      GWEN_Buffer_AppendArgs(tbuf, "Set-Cookie: session=%s; max-age=86400", AQCGI_Session_GetUid(session));
      AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(tbuf));
      DBG_ERROR(NULL, "Renew session cookie");
      GWEN_Buffer_free(tbuf);
      return session;
    }
  }
  else {
    DBG_ERROR(NULL, "No session cookie");
  }
  return NULL;
}



void AQH_ModService_CalcSessionModPerms(AQCGI_MODULE *m, const AQCGI_SESSION *session)
{
  const AQCGI_USER *user;

  user=session?AQCGI_Session_GetUser(session):NULL;
  _calcUserModPerms(m, user);
}



void _calcUserModPerms(AQCGI_MODULE *m, const AQCGI_USER *user)
{
  if (m) {
    AQH_MOD_SERVICE *xm;

    xm=GWEN_INHERIT_GETDATA(AQCGI_MODULE, AQH_MOD_SERVICE, m);
    if (xm) {
      uint32_t perms=0;

      if (user) {
        if (AQCGI_User_GetFlags(user) & AQCGI_USER_FLAGS_ADMIN)
          perms=0xffffffff;
        else {
          const char *sModName;
          const AQCGI_MODULE_PERMS_LIST *modPermsList;
          AQCGI_MODULE_PERMS *modPerms;

          sModName=AQCGI_Module_GetName(m);
          modPermsList=AQCGI_User_GetModulePermList(user);
          modPerms=(sModName && modPermsList)?AQCGI_ModulePerms_List_GetByModuleId(modPermsList, sModName):NULL;
          if (modPerms)
            perms=_calcRolePerms(m, modPerms);
          else
            perms=AQCGI_Module_GetGuestPerms(m);
        }
      } /* if (user) */
      else
        perms=AQCGI_Module_GetGuestPerms(m);
      xm->userPerms=perms;
    }
  } /* if (m) */
}



uint32_t _calcRolePerms(const AQCGI_MODULE *m, const AQCGI_MODULE_PERMS *modPerms)
{
  uint32_t perms=0;
  const AQCGI_ROLE_LIST *roleList;

  roleList=AQCGI_Module_GetRoleList(m);
  if (roleList) {
    int roleArraySize;
    int i;
    uint32_t explAddPerms=0;
    uint32_t explDelPerms=0;

    roleArraySize=AQCGI_ModulePerms_GetRoleArrayArraySize();
    for (i=0; i<roleArraySize; i++) {
      int roleId;

      roleId=AQCGI_ModulePerms_GetRoleArrayAt(modPerms, i);
      if (roleId) {
        const AQCGI_ROLE *role;

        role=AQCGI_Role_List_GetById(roleList, roleId);
        if (role) {
          perms|=AQCGI_Role_GetPerms(role);
          explAddPerms|=AQCGI_Role_GetExplAddPerms(role);
          explAddPerms|=AQCGI_Role_GetExplDelPerms(role);
        }
      }
    } /* for */
    /* collate permissions */
    perms|=explAddPerms;
    perms|=AQCGI_ModulePerms_GetExplAddPerms(modPerms);
    perms&=~explDelPerms;
    perms&=~AQCGI_ModulePerms_GetExplDelPerms(modPerms);
  }
  return perms;
}




void AQH_ModService_AddPermDef(AQCGI_PERMDEF_LIST *permDefList, const char *id, uint32_t mask, const char *descr)
{
  AQCGI_PERMDEF *permDef;

  permDef=AQCGI_PermDef_new();
  AQCGI_PermDef_SetId(permDef, id);
  AQCGI_PermDef_SetMask(permDef, mask);
  AQCGI_PermDef_SetDescr(permDef, descr);
  AQCGI_PermDef_List_Add(permDef, permDefList);
}



void AQH_ModService_AddRole(AQCGI_ROLE_LIST *roleList, int id, const char *name, uint32_t perms, const char *descr)
{
  AQCGI_ROLE *role;

  role=AQCGI_Role_new();
  AQCGI_Role_SetId(role, id);
  AQCGI_Role_SetName(role, name);
  AQCGI_Role_SetPerms(role, perms);
  AQCGI_Role_SetDescr(role, descr);
  AQCGI_Role_List_Add(role, roleList);
}



void AQH_ModService_HandleRequestWithTable(AQCGI_MODULE *m,
                                           AQCGI_REQUEST *rq,
                                           AQCGI_SESSION *session,
                                           const char *page,
                                           const AQH_MODSERVICE_HANDLER_ENTRY *e)
{
  uint32_t perms;
  GWEN_BUFFER *dbuf;
  int i;

  perms=AQH_ModService_GetUserPerms(m);
  DBG_ERROR(NULL, "Perms=%08x", perms);

  dbuf=GWEN_Buffer_new(0, 256, 0, 1);
  AQH_ModService_AddHeader(m, "en", dbuf);

  for (i=0; ; i++) {
    if (e[i].page==NULL) {
      AQCGI_Request_SetResponseCode(rq, 404);
      AQCGI_Request_SetResponseText(rq, "Not Found");
      break;
    }
    if ((AQCGI_Request_GetRequestMethod(rq)==e[i].httpMethod) &&
        (strcasecmp(page, e[i].page)==0)) {
      /* preset result */
      AQCGI_Request_SetResponseCode(rq, 200);
      AQCGI_Request_SetResponseText(rq, "Ok");
      if ((perms & e[i].perms)==e[i].perms)
        (e[i].handlerFn)(m, rq, session, dbuf);
      else
	AQH_ModService_DenyRequest(m, rq, session, dbuf);
      break;
    }
  }

  if (!(AQCGI_Request_GetFlags(rq) & AQH_MODSERVICE_RQFLAGS_RAWFILE)) {
    DBG_ERROR(NULL, "Not adding footer");
    AQH_ModService_AddFooter(m, "en", dbuf);
  }
  AQCGI_Request_SetBufferResponseBody(rq, dbuf);
  if (AQCGI_Request_GetFlags(rq) & AQCGI_FLAGS_HAS_CONTENT_HEADER)
    AQCGI_Request_AddResponseHeaderData(rq, "Content-type: text/html");
}



void AQH_ModService_WritePermsToForm(uint32_t perms, const AQCGI_PERMDEF_LIST *permDefList, const char *sPrefix, GWEN_BUFFER *dbuf)
{
  if (permDefList) {
    const AQCGI_PERMDEF *permDef;
    GWEN_BUFFER *tbuf;
    uint32_t pos;

    tbuf=GWEN_Buffer_new(0, 256, 0, 1);
    if (sPrefix && *sPrefix)
      GBAA(tbuf, "%s:", sPrefix);
    pos=GWEN_Buffer_GetPos(tbuf);

    permDef=AQCGI_PermDef_List_First(permDefList);
    while(permDef) {
      const char *s;

      s=AQCGI_PermDef_GetId(permDef);
      if (s && *s) {
        uint32_t mask;

        GBAS(tbuf, s);
        mask=AQCGI_PermDef_GetMask(permDef);
        if (perms & mask)
          GBAA(dbuf, "<input type=\"checkbox\" name=\"%s\" checked>", GWEN_Buffer_GetStart(tbuf));
        else
          GBAA(dbuf, "<input type=\"checkbox\" name=\"%s\">", s?s:"");
        GBAA(dbuf, "<label for=\"%s\">%s</label>", GWEN_Buffer_GetStart(tbuf), s?s:"");
        GWEN_Buffer_Crop(tbuf, 0, pos);
      }
      permDef=AQCGI_PermDef_List_Next(permDef);
    }
    GWEN_Buffer_free(tbuf);
  }
}



uint32_t AQH_ModService_ReadPermsFromForm(GWEN_DB_NODE *dbPost, const AQCGI_PERMDEF_LIST *permDefList, const char *sPrefix)
{
  uint32_t result=0;

  if (permDefList) {
    const AQCGI_PERMDEF *permDef;
    GWEN_BUFFER *tbuf;
    uint32_t pos;

    tbuf=GWEN_Buffer_new(0, 256, 0, 1);
    if (sPrefix && *sPrefix)
      GBAA(tbuf, "%s:", sPrefix);
    pos=GWEN_Buffer_GetPos(tbuf);

    permDef=AQCGI_PermDef_List_First(permDefList);
    while(permDef) {
      const char *id;

      id=AQCGI_PermDef_GetId(permDef);
      if (id && *id) {
        uint32_t mask;
        const char *s;

        GBAS(tbuf, id);
        mask=AQCGI_PermDef_GetMask(permDef);
        s=GWEN_DB_GetCharValue(dbPost, GWEN_Buffer_GetStart(tbuf), 0, NULL);
        if (s && *s)
          result|=mask;
        GWEN_Buffer_Crop(tbuf, 0, pos);
      }
      permDef=AQCGI_PermDef_List_Next(permDef);
    }
    GWEN_Buffer_free(tbuf);
  }

  return result;
}



AQCGI_MODULE_LIST *AQH_ModService_LoadRawModules(AQCGI_MODULE *m)
{
  AQCGI_SERVICE *sv;
  GWEN_STRINGLIST *slModuleNames;

  sv=AQH_ModService_GetService(m);
  slModuleNames=AQCGI_Service_ListModules(sv);
  if (slModuleNames) {
    AQCGI_MODULE_LIST *modList;
    GWEN_STRINGLISTENTRY *se;

    modList=AQCGI_Module_List_new();
    se=GWEN_StringList_FirstEntry(slModuleNames);
    while(se) {
      const char *sModName;

      sModName=GWEN_StringListEntry_Data(se);
      if (sModName && *sModName) {
        AQCGI_MODULE *currentMod;

        currentMod=AQCGI_Service_LoadModule(sv, sModName);
        if (currentMod)
          AQCGI_Module_List_Add(currentMod, modList);
      }
      se=GWEN_StringListEntry_Next(se);
    }
    GWEN_StringList_free(slModuleNames);
    if (AQCGI_Module_List_GetCount(modList))
      return modList;
    AQCGI_Module_List_free(modList);
  }

  return NULL;
}



AQCGI_USER_LIST *AQH_ModService_LoadRawUsers(AQCGI_MODULE *m)
{
  AQCGI_SERVICE *sv;
  GWEN_STRINGLIST *slUserNames;

  sv=AQH_ModService_GetService(m);
  slUserNames=AQCGI_Service_ListUsers(sv);
  if (slUserNames) {
    AQCGI_USER_LIST *userList;
    GWEN_STRINGLISTENTRY *se;

    userList=AQCGI_User_List_new();
    se=GWEN_StringList_FirstEntry(slUserNames);
    while(se) {
      const char *sModName;

      sModName=GWEN_StringListEntry_Data(se);
      if (sModName && *sModName) {
        AQCGI_USER *u;

        u=AQCGI_Service_LoadUser(sv, sModName);
        if (u)
          AQCGI_User_List_Add(u, userList);
      }
      se=GWEN_StringListEntry_Next(se);
    }
    GWEN_StringList_free(slUserNames);
    if (AQCGI_User_List_GetCount(userList))
      return userList;
    AQCGI_User_List_free(userList);
  }

  return NULL;
}



void AQH_ModService_EscapeToBuffer(const char *src, GWEN_BUFFER *buf)
{
  while (*src) {
    unsigned char x;

    x=(unsigned char)*src;
    if (!(
          (x>='A' && x<='Z') ||
          (x>='a' && x<='z') ||
          (x>='0' && x<='9') ||
          NULL!=strchr("-_", x)
         )) {
      unsigned char c;

      GWEN_Buffer_AppendByte(buf, '%');
      c=(((unsigned char)(*src))>>4)&0xf;
      if (c>9)
        c+=7;
      c+='0';
      GWEN_Buffer_AppendByte(buf, c);
      c=((unsigned char)(*src))&0xf;
      if (c>9)
        c+=7;
      c+='0';
      GWEN_Buffer_AppendByte(buf, c);
    }
    else
      GWEN_Buffer_AppendByte(buf, *src);

    src++;
  } /* while */
}



void AQH_ModService_UnescapeToBuffer(const char *src, GWEN_BUFFER *buf)
{
  while (*src) {
    int charHandled=0;

    if (*src=='%') {
      if (strlen(src)>2) {
        unsigned char d1, d2;
        unsigned char c;

        if (isxdigit((int)src[1]) && isxdigit((int)src[2])) {
          /* skip '%' */
          src++;
          /* read first digit */
          d1=(unsigned char)(toupper(*src));

          /* get second digit */
          src++;
          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 */
          GWEN_Buffer_AppendByte(buf, (char)c);
          charHandled=1;
        }
      }
    }
    if (!charHandled)
      GWEN_Buffer_AppendByte(buf, *src);
    src++;
  } /* while */
}



int AQH_ModService_FileIsCurrent(const char *sPath, int seconds)
{
  struct stat sb;
  time_t t1;

  if (lstat(sPath, &sb)==-1) {
    DBG_ERROR(NULL, "Error on lstat(%s): %s (%d)", sPath, strerror(errno), errno);
    return 0;
  }
  t1=time(0);
  if ((t1-sb.st_mtime)<(time_t) seconds) {
    DBG_DEBUG(NULL, "File %s is current", sPath);
    return 1;
  }

  return 0;
}



int AQH_ModService_RespondWithMimeFile(AQCGI_REQUEST *rq, const char *sFilename, const char *sMimeType, GWEN_BUFFER *dbuf)
{
  GWEN_BUFFER *ibuf;
  int rv;

  ibuf=GWEN_Buffer_new(0, 1024, 0, 1);
  /* read file */
  rv=GWEN_SyncIo_Helper_ReadFile(sFilename, ibuf);
  if (rv<0) {
    DBG_ERROR(NULL, "Error reading \"%s\" (%d)", sFilename, rv);
    return rv;
  }
  else {
    GWEN_Buffer_Reset(dbuf);
    GWEN_Buffer_AppendBytes(dbuf, GWEN_Buffer_GetStart(ibuf), GWEN_Buffer_GetUsedBytes(ibuf));

    if (sMimeType && *sMimeType) {
      GWEN_BUFFER *tbuf;

      tbuf=GWEN_Buffer_new(0, 256, 0, 1);
      GBAA(tbuf, "Content-type: %s", sMimeType);
      AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(tbuf));
      GWEN_Buffer_free(tbuf);
    }
    AQCGI_Request_AddFlags(rq, AQH_MODSERVICE_RQFLAGS_RAWFILE);
  }
  GWEN_Buffer_free(ibuf);
  return 0;
}




int AQH_ModService_AddRequestUrl(const AQCGI_REQUEST *rq, GWEN_BUFFER *dbuf)
{
  GWEN_DB_NODE *dbRequest;

  dbRequest=AQCGI_Request_GetDbRequestHeader(rq);
  if (dbRequest) {
    const char *s;

    GWEN_Buffer_AppendArgs(dbuf,
			   "%s%s",
			   GWEN_DB_GetCharValue(dbRequest, "SCRIPT_NAME", 0, ""),
			   GWEN_DB_GetCharValue(dbRequest, "PATH_INFO", 0, ""));
    s=GWEN_DB_GetCharValue(dbRequest, "QUERY_STRING", 0, NULL);
    if (s && *s)
      GWEN_Buffer_AppendArgs(dbuf, "?%s", s);
    return 1;
  }
  return 0;
}



void AQH_ModService_Redirect(AQCGI_REQUEST *rq, const char *destLocation)
{
  GWEN_BUFFER *dbuf;

  dbuf=GWEN_Buffer_new(0, 128, 0, 1);
  AQCGI_Request_SetResponseCode(rq, 303);
  AQCGI_Request_SetResponseText(rq, "See other");
  GWEN_Buffer_AppendArgs(dbuf, "Location: %s", destLocation);
  AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(dbuf));
  GWEN_Buffer_free(dbuf);
}



void AQH_ModService_RedirectWithReferer(AQCGI_REQUEST *rq, const char *destLocation)
{
  GWEN_BUFFER *dbuf;
  GWEN_BUFFER *ubuf;

  dbuf=GWEN_Buffer_new(0, 128, 0, 1);
  GWEN_Buffer_AppendArgs(dbuf, "Location: %s", destLocation);

  ubuf=GWEN_Buffer_new(0, 128, 0, 1);
  if (AQH_ModService_AddRequestUrl(rq, ubuf)) {
    DBG_ERROR(NULL, "Adding header: [%s]", GWEN_Buffer_GetStart(ubuf));
    GWEN_Buffer_AppendString(dbuf, "?src=");
    GWEN_Text_EscapeToBuffer(GWEN_Buffer_GetStart(ubuf), dbuf);
  }
  GWEN_Buffer_free(ubuf);

  AQCGI_Request_SetResponseCode(rq, 303);
  AQCGI_Request_SetResponseText(rq, "See other");
  AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(dbuf));
  GWEN_Buffer_free(dbuf);
}



void AQH_ModService_DenyRequest(AQCGI_MODULE *m, AQCGI_REQUEST *rq, const AQCGI_SESSION *session, GWEN_BUFFER *dbuf)
{
  if (session && AQCGI_Session_GetUser(session)) {
    /* no permissions */
    AQCGI_Request_SetResponseCode(rq, 403);
    AQCGI_Request_SetResponseText(rq, "Forbidden");
    if (dbuf)
      GWEN_Buffer_AppendString(dbuf, "<h1>Error</h1><p>No permissions for this request.</p>");
  }
  else {
    /* need login */
    AQH_ModService_RedirectWithReferer(rq, AQCGI_Service_GetLoginUrl(AQH_ModService_GetService(m)));
  }
}




