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

//#define DISABLE_DEBUGLOG


#include "./httprequest_p.h"

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

#include <ctype.h>


GWEN_TREE2_FUNCTIONS(AQH_HTTP_REQUEST, AQH_HttpRequest);



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

static int _getParsedDbInfo(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg);
static int _inspectReceivedMessage(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg);
static int _inspectMsgCommand(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg);
static void _inspectMsgHeader(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg);
static int _inspectMsgBody(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg);
static GWEN_DB_NODE *_extractCookies(GWEN_DB_NODE *dbHeader);
static void _setCookieValue(GWEN_DB_NODE *dbCookies,
                            const char *nameStart, int nameLen,
                            const char *valueStart, int valueLen,
                            uint32_t dbFlags);
static int _parsePostBody(const char *s, int contentLength, GWEN_DB_NODE *dbBody);
static int _unescapeUrlEncoded(const char *src, unsigned int srclen, char *buffer, unsigned int maxsize);



/* ------------------------------------------------------------------------------------------------
 * implementations
 * ------------------------------------------------------------------------------------------------
 */

AQH_HTTP_REQUEST *AQH_HttpRequest_new(GWEN_MSG_ENDPOINT *endpoint, const GWEN_MSG *receivedMsg)
{
  AQH_HTTP_REQUEST *rq;
  int rv;

  GWEN_NEW_OBJECT(AQH_HTTP_REQUEST, rq);
  GWEN_TREE2_INIT(AQH_HTTP_REQUEST, rq, AQH_HttpRequest);

  rq->endpoint=endpoint;
  rq->receivedMsg=receivedMsg;

  rv=_inspectReceivedMessage(rq, receivedMsg);
  if (rv<0) {
    DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
    AQH_HttpRequest_free(rq);
    return NULL;
  }
  return rq;
}



void AQH_HttpRequest_free(AQH_HTTP_REQUEST *rq)
{
  if (rq) {
    GWEN_TREE2_FINI(AQH_HTTP_REQUEST, rq, AQH_HttpRequest);
    free(rq->sessionId);
    free(rq->moduleName);
    free(rq->responseText);
    GWEN_Msg_free(rq->responseMsg);
    GWEN_StringList_free(rq->urlPathMembers);
    GWEN_Url_free(rq->url);
    GWEN_DB_Group_free(rq->dbPostBody);

    GWEN_FREE_OBJECT(rq);
  }
}



GWEN_MSG_ENDPOINT *AQH_HttpRequest_GetEndpoint(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->endpoint):NULL;
}



const GWEN_MSG *AQH_HttpRequest_GetReceivedMsg(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->receivedMsg):NULL;
}



const char *AQH_HttpRequest_GetCommand(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->command):NULL;
}



const char *AQH_HttpRequest_GetProtocol(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->protocol):NULL;
}



GWEN_URL *AQH_HttpRequest_GetUrl(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->url):NULL;
}



void AQH_HttpRequest_SetUrl(AQH_HTTP_REQUEST *rq, GWEN_URL *url)
{
  if (rq) {
    GWEN_Url_free(rq->url);
    rq->url=url;
  }
}



const char *AQH_HttpRequest_GetUrlPath(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->urlPath):NULL;
}



GWEN_STRINGLIST *AQH_HttpRequest_GetUrlPathMembers(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->urlPathMembers):NULL;
}



void AQH_HttpRequest_SetUrlPathMembers(AQH_HTTP_REQUEST *rq, GWEN_STRINGLIST *sl)
{
  if (rq) {
    GWEN_StringList_free(rq->urlPathMembers);
    rq->urlPathMembers=sl;
  }
}



GWEN_DB_NODE *AQH_HttpRequest_GetDbCommand(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->dbCommand):NULL;
}



GWEN_DB_NODE *AQH_HttpRequest_GetDbHeader(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->dbHeader):NULL;
}



GWEN_DB_NODE *AQH_HttpRequest_GetDbCookies(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->dbCookies):NULL;
}



GWEN_DB_NODE *AQH_HttpRequest_GetDbPostBody(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->dbPostBody):NULL;
}



const char *AQH_HttpRequest_GetSessionId(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->sessionId):NULL;
}



void AQH_HttpRequest_SetSessionId(AQH_HTTP_REQUEST *rq, const char *s)
{
  if (rq) {
    free(rq->sessionId);
    rq->sessionId=s?strdup(s):NULL;
  }
}



const char *AQH_HttpRequest_GetModuleName(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->moduleName):NULL;
}



void AQH_HttpRequest_SetModuleName(AQH_HTTP_REQUEST *rq, const char *s)
{
  if (rq) {
    free(rq->moduleName);
    rq->moduleName=s?strdup(s):NULL;
  }
}



AQH_SESSION *AQH_HttpRequest_GetSession(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->session):NULL;
}



void AQH_HttpRequest_SetSession(AQH_HTTP_REQUEST *rq, AQH_SESSION *session)
{
  if (rq)
    rq->session=session;
}



AQH_MODULE *AQH_HttpRequest_GetModule(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->module):NULL;
}



void AQH_HttpRequest_SetModule(AQH_HTTP_REQUEST *rq, AQH_MODULE *m)
{
  if (rq)
    rq->module=m;
}



uint32_t AQH_HttpRequest_GetModulePerms(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->modulePerms):0;
}



void AQH_HttpRequest_SetModulePerms(AQH_HTTP_REQUEST *rq, uint32_t i)
{
  if (rq)
    rq->modulePerms=i;
}



int AQH_HttpRequest_GetResponseCode(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->responseCode):0;
}



void AQH_HttpRequest_SetResponseCode(AQH_HTTP_REQUEST *rq, int i)
{
  if (rq)
    rq->responseCode=i;
}



const char *AQH_HttpRequest_GetResponseText(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->responseText):NULL;
}



void AQH_HttpRequest_SetResponseText(AQH_HTTP_REQUEST *rq, const char *s)
{
  if (rq) {
    free(rq->responseText);
    rq->responseText=s?strdup(s):NULL;
  }
}



GWEN_MSG *AQH_HttpRequest_GetResponseMsg(const AQH_HTTP_REQUEST *rq)
{
  return rq?(rq->responseMsg):NULL;
}



GWEN_MSG *AQH_HttpRequest_TakeResponseMsg(AQH_HTTP_REQUEST *rq)
{
  if (rq) {
    GWEN_MSG *msg;

    msg=rq->responseMsg;
    rq->responseMsg=NULL;
    return msg;
  }
  return NULL;
}



void AQH_HttpRequest_SetResponseMsg(AQH_HTTP_REQUEST *rq, GWEN_MSG *msg)
{
  if (rq && msg) {
    GWEN_Msg_free(rq->responseMsg);
    rq->responseMsg=msg;
  }
}



void AQH_HttpRequest_SetupUrlPathMembers(AQH_HTTP_REQUEST *rq)
{
  const char *path;

  path=AQH_HttpRequest_GetUrlPath(rq);
  if (path && *path) {
    GWEN_STRINGLIST *sl;

    if (*path=='/') /* skip leading slash */
      path++;
    sl=GWEN_StringList_fromString(path, "/", 0);
    if (sl==NULL) {
      DBG_ERROR(NULL, "Empty url path member list");
    }
    else
      AQH_HttpRequest_SetUrlPathMembers(rq, sl);
  }
}



int _inspectReceivedMessage(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg)
{
  int rv;

  rv=_getParsedDbInfo(rq, msg);
  if (rv<0) {
    DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
    return rv;
  }

  rv=_inspectMsgCommand(rq, msg);
  if (rv<0) {
    DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
    return rv;
  }

  _inspectMsgHeader(rq, msg);

  rv=_inspectMsgBody(rq, msg);
  if (rv<0) {
    DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
    return rv;
  }

  return 0;
}



int _getParsedDbInfo(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg)
{
  GWEN_DB_NODE *dbParsedInfo;
  GWEN_DB_NODE *db;

  dbParsedInfo=GWEN_Msg_GetDbParsedInfo(msg);
  if (dbParsedInfo==NULL) {
    DBG_INFO(AQH_LOGDOMAIN, "No parsed info db, msg probably not generated by HTTP endpoint module");
    return GWEN_ERROR_GENERIC;
  }

  db=GWEN_DB_GetGroup(dbParsedInfo, GWEN_PATH_FLAGS_PATHMUSTEXIST, "command");
  if (db==NULL) {
    DBG_INFO(AQH_LOGDOMAIN, "No parsed command db, msg probably not generated by HTTP endpoint module");
    return GWEN_ERROR_GENERIC;
  }
  rq->dbCommand=db;

  db=GWEN_DB_GetGroup(dbParsedInfo, GWEN_PATH_FLAGS_PATHMUSTEXIST, "header");
  if (db==NULL) {
    DBG_INFO(AQH_LOGDOMAIN, "No parsed header db, msg probably not generated by HTTP endpoint module or no header recvd, ignoring");
  }
  rq->dbHeader=db;

  return 0;
}



int _inspectMsgCommand(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg)
{
  const char *s;

  s=GWEN_DB_GetCharValue(rq->dbCommand, "command", 0, NULL);
  if (!(s && *s)) {
    DBG_ERROR(AQH_LOGDOMAIN, "No command in request");
    return GWEN_ERROR_GENERIC;
  }
  rq->command=s;

  s=GWEN_DB_GetCharValue(rq->dbCommand, "protocol", 0, NULL);
  if (!(s && *s)) {
    DBG_ERROR(AQH_LOGDOMAIN, "No protocol in request");
    return GWEN_ERROR_GENERIC;
  }
  rq->protocol=s;

  s=GWEN_DB_GetCharValue(rq->dbCommand, "url", 0, NULL);
  if (!(s && *s)) {
    DBG_ERROR(AQH_LOGDOMAIN, "No url in request");
    return GWEN_ERROR_GENERIC;
  }

  rq->url=GWEN_Url_fromCommandString(s);
  if (rq->url==NULL) {
    DBG_ERROR(AQH_LOGDOMAIN, "Invalid URL [%s]", s);
    return GWEN_ERROR_GENERIC;
  }

  s=GWEN_Url_GetPath(rq->url);
  if (!(s && *s)) {
    DBG_ERROR(AQH_LOGDOMAIN, "Empty url in request, assuming \"/\"");
    s="/";
  }
  rq->urlPath=s;

  return 0;
}



void _inspectMsgHeader(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg)
{
  if (rq->dbHeader)
    rq->dbCookies=_extractCookies(rq->dbHeader);
  if (rq->dbCookies) {
    const char *s;

    s=GWEN_DB_GetCharValue(rq->dbCookies, AQH_HTTP_REQUEST_SESSIONCOOKIE, 0, NULL);
    if (s && *s) {
      DBG_INFO(AQH_LOGDOMAIN, "Sessionid: %s", s);
      AQH_HttpRequest_SetSessionId(rq, s);
    }
  }

}



int _inspectMsgBody(AQH_HTTP_REQUEST *rq, const GWEN_MSG *msg)
{
  int rv;

  rq->recvdBodySize=GWEN_Msg_GetParsedPayloadSize(msg);
  if (rq->recvdBodySize>0)
    rq->recvdBodyPtr=GWEN_Msg_GetConstBuffer(msg)+GWEN_Msg_GetParsedPayloadOffset(msg);

  if (rq->dbHeader && rq->recvdBodySize>0 && strcasecmp(rq->command, "POST")==0) {
    const char *s;

    /* check whether we need to parse POST body */
    s=GWEN_DB_GetCharValue(rq->dbHeader, "content-type", 0, NULL);
    if (s && strcasecmp(s, "application/x-www-form-urlencoded")==0) {
      rq->dbPostBody=GWEN_DB_Group_new("post");
      rv=_parsePostBody((const char*) (rq->recvdBodyPtr), rq->recvdBodySize, rq->dbPostBody);
      if (rv<0) {
	DBG_INFO(AQH_LOGDOMAIN, "here (%d)", rv);
	return rv;
      }
    }
  }

  return 0;
}



GWEN_DB_NODE *_extractCookies(GWEN_DB_NODE *dbHeader)
{
  int i;
  GWEN_DB_NODE *dbCookies;

  dbCookies=GWEN_DB_GetGroup(dbHeader, 0, "cookies");
  for (i=0; ; i++) {
    const char *sCookie;

    sCookie=GWEN_DB_GetCharValue(dbHeader, "Cookie", i, NULL);
    if (sCookie && *sCookie) {
      const char *s;

      s=sCookie;
      while(*s) {
        while(*s && *s<33)
          s++;
  
        if (*s) {
          const char *nameStart;
          int nameLen;
  
          nameStart=s++;
          while(*s && *s!='=')
            s++;
          if (*s=='=') {
            nameLen=s-nameStart;
            s++;
            while(*s && *s<33)
              s++;
            if (*s) {
              const char *valueStart;
              int valueLen;
  
              valueStart=s;
              while(*s && *s!=';')
                s++;
              valueLen=s-valueStart;

              _setCookieValue(dbCookies, nameStart, nameLen, valueStart, valueLen, 0);

              if (*s)
                s++;
            }
          }
        }
      } /* while(*s) */
    } /* if (sCookie && *sCookie) */
    else
      break;
  } /* for */
  return dbCookies;
}



void _setCookieValue(GWEN_DB_NODE *dbCookies, const char *nameStart, int nameLen, const char *valueStart, int valueLen, uint32_t dbFlags)
{
  if (nameLen && valueLen) {
    int i;
    char *sName;
    char *sValue;
  
    for (i=nameLen-1; i>0; i--) {
      if (nameStart[i]>32)
        break;
    }
    sName=GWEN_Text_strndup(nameStart, i+1);
  
    for (i=valueLen-1; i>0; i--) {
      if (valueStart[i]>32)
        break;
    }
    sValue=GWEN_Text_strndup(valueStart, i+1);

    DBG_INFO(AQH_LOGDOMAIN, "Received Cookie: [%s]=[%s]", sName, sValue);
    GWEN_DB_SetCharValue(dbCookies, dbFlags, sName, sValue);
    free(sValue);
    free(sName);
  }
}



int _parsePostBody(const char *s, int contentLength, GWEN_DB_NODE *dbBody)
{
  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(dbBody, 0, sNameBuf, sValueBuf);
        else {
          DBG_ERROR(NULL, "Either name or value invalid in body, aborting");
          return GWEN_ERROR_BAD_DATA;
        }
      }
      if ((contentLength>0) && (*s=='&')) {
        contentLength--;
        s++;
      }
    }
  } /* while */
  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(GWEN_LOGDOMAIN, "Buffer too small");
	return GWEN_ERROR_BUFFER_OVERFLOW;
      }
    }
    else {
      if (*src=='%') {
        unsigned char d1, d2;
        unsigned char c;

        if (srclen<3) {
          DBG_ERROR(GWEN_LOGDOMAIN, "Incomplete escape sequence (EOLN met)");
	  return GWEN_ERROR_BAD_DATA;
        }
        /* skip '%' */
        src++;
        if (!(*src) || !isxdigit((int)*src)) {
          DBG_ERROR(GWEN_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(GWEN_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(GWEN_LOGDOMAIN, "Buffer too small");
	  return GWEN_ERROR_BUFFER_OVERFLOW;
        }
        srclen-=2;
      }
      else {
        DBG_ERROR(GWEN_LOGDOMAIN, "Found non-alphanum characters in escaped string (\"%s\")", src);
	return GWEN_ERROR_BAD_DATA;
      }
    }
    srclen--;
    src++;
  } /* while */

  buffer[size]=0;
  return 0;
}




