/***************************************************************************
 *                                                                         *
 *   ALIASAH.CPP                                                           *
 *                                                                         *
 *   Copyright (c) 1997 Galacticomm, Inc.         All Rights Reserved.     *
 *                                                                         *
 *   Active HTML interface for managing Internet aliases.                  *
 *                                                                         *
 *                                            - J. Alvrus   11/17/97       *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "gme.h"
#include "galacth.h"
#include "dnf.h"
#include "tvb.h"
#include "cyctimer.h"
#include "alias.h"
#include "galalias.h"

#define FILREV "$Revision: 1 $"

/* Identifiers from GALALIAS.C */

                                   /* Btrieve record key definitions       */
#define USRKEY 0                   /*   key 0 (User-ID) for .DAT file      */
#define CNVKEY 1                   /*   key 1 (Converted User-ID) for file */
#define ALAKEY 2                   /*   key 2 (alias) for .DAT file        */

struct alsrec {                    /* Btrieve record layout                */
     CHAR userid[UIDSIZ];          /*   User-ID (key 0)                    */
     CHAR convid[UIDSIZ];          /*   alias form of User-ID (key 1)      */
     CHAR alias[ALSSIZ];           /*   Internet alias (key 2)             */
     GBOOL sntmsg;                 /*   have we sent conflict message?     */
};

extern HMCVFILE alsmb;             /* file handle for GALALIAS.MCV         */

extern "C" GBOOL newAlias(const CHAR *userid,CHAR *alias,struct alsrec *pRec);
extern "C" VOID addals(const CHAR *alias,struct alsrec *alsrecp,
                       const CHAR *userid);

/* Form parameter identifiers */

#define FP_USECUST  "custom"       // use custom alias form parameter
#define FP_ALIAS    "alias"        // custom alias text form parameter

/* Miscellaneous global functions */

CHAR *                             //   returns allocated buffer
entEncodeStr(                      // encode entities in a string
const CHAR *src);                  //   unencoded string

size_t
entSize(                           // size of entity-encoded string (incl '\0')
const CHAR *src);                  //   unencoded string

CHAR *                             //   returns pointer to destination
entEncodeBuf(                      // encode string using HTML entities
CHAR *buf,                         //   buffer to receive encoded string
const CHAR *src,                   //   unencoded string
size_t bufSiz);                    //   size of destination buffer

/* All DNF steps and maps */

dnfStep getFormStep[]={            // get alias config form DNF map
     dnfStep(DNFMAPEND)
};
dnfMap getFormMap("galacth/galalias/alias.htm",
                  "Configure Internet Alias",getFormStep);

dnfStep getDispStep[]={            // display alias DNF map
     dnfStep(DNFMAPEND)
};
dnfMap getDispMap("galacth/galalias/aliasdsp.htm",
                  "Display Internet Alias",getDispStep);

dnfStep setOKStep[]={              // set alias error DNF map
     dnfStep(DNFMAPEND)
};
dnfMap setOKMap("galacth/galalias/aliasok.htm",
                 "Internet Alias Updated",setOKStep);

enum {ERRTEXT};
dnfStep setErrStep[]={             // set alias error DNF map
     dnfStep(DNFWATCH,ERRTEXT,"ERROR_MSG"),
     dnfStep(DNFMAPEND)
};
dnfMap setErrMap("galacth/galalias/aliaserr.htm",
                 "Internet Alias Error",setErrStep);

/* text variable classes */

class txvBase : public tvbDefinition { // generic text variable handler
public:
     txvBase(                      // constructor for dynamic variables
     const CHAR *name) :           //   variable name
          tvbDefinition(name),
          m_pVal(NULL)
     {
          registerDef();
     }

     txvBase(                      // constructor for static variables
     const CHAR *name,             //   variable name
     const CHAR *value) :          //   pointer to buffer containing value
          tvbDefinition(name),     //   (contents must be persistent)
          m_pVal(NULL)
     {
          set(value);
          registerDef();
     }

     virtual VOID
     set(                          // set value to display
     const CHAR *newValue)         //   to this string
     {
          m_pVal=newValue;
     }

     virtual const CHAR *
     get() const                   // get current value
     {
          return(m_pVal);
     }

     virtual VOID
     clr()                         // clear current value
     {
          m_pVal=NULL;
     }

     virtual const CHAR *          //   returns text var value
     resolve(                      // resolve a text var name into its value
     const CHAR *nam);             //   name of text var being resolved

protected:                         // protected member variables
     const CHAR *m_pVal;           //   pointer to text to return
};

class txvCheck : public txvBase { // txt var for "CHECKED" items in forms
public:
     txvCheck(
     const CHAR *name) :
          txvBase(name)
     {}

     VOID
     setChecked(                   // set checked status
     bool isChecked);              //   is the option checked?
};

class txvEncoded : public txvBase { // txt var requiring encoding
public:
     txvEncoded(                   // constructor for dynamic variables
     const CHAR *name) :           //   variable name
          txvBase(name)
     {}

     txvEncoded(                   // constructor for static variables
     const CHAR *name,             //   variable name
     const CHAR *value) :          //   pointer to buffer containing value
          txvBase(name,value)      //   (contents must be persistent)
     {}

     ~txvEncoded()                 // destructor
     {
          clr();
     }

     virtual VOID
     set(                          // set value to display
     const CHAR *newValue)=0;      //   to this string

     virtual VOID
     clr();                        // clear current value
};

class txvHTML : public txvEncoded { // txt var for HTML components
public:
     txvHTML(                      // constructor for dynamic variables
     const CHAR *name) :           //   variable name
          txvEncoded(name)
     {}

     txvHTML(                      // constructor for static variables
     const CHAR *name,             //   variable name
     const CHAR *value) :          //   pointer to buffer containing value
          txvEncoded(name,value)   //   (contents must be persistent)
     {}

     virtual VOID
     set(                          // set value to display
     const CHAR *newValue);        //   to this string
};

/* WL Mail system agent */

class alsAgent : public acthAgent { // WL Mail system agent
public:
     alsAgent(                     // constructor
     const CHAR *apnam,            //   description (eg "File Libraries")
     const CHAR *upfx) :           //   URL prefix (eg "library")
          acthAgent(apnam,upfx)
     {
          registerAgent(acthVersion);
     }

     acthSynthesis *               //   session info
     newSynthesis(                 // instantiate Synthesis class
     acthSession *ses);            //   for passing to acthSynthesis's ctor
};

/* Base synthesis class */

class synBase : public acthSynthesis { // virtual base class
public:
     synBase(
     acthSession *ses);

     ~synBase();

     virtual ACTHCODE              //   returns enumerated status value
     proceed()=0;                  // agent's request handler

protected:

     bool
     userOK(                       // does this user have access?
     ACTHCODE &rc);                //   error code if not

     bool
     startProc();                  // start proceed() processing?

     bool
     contProc(                     // continue proceed() processing?
     ACTHCODE rc);                 //   current proceed() return code

     bool
     haveRoom();                   // do we have room for output?

private:
     synBase();                    // no default constructor

     synBase(                      // no copy constructor
     synBase& hs);

     VOID
     operator=(                    // no assignment operator
     synBase& hs);

protected:                         // standard data members
     dnfHandler *m_dnf;            //   DynaFile handler
     cycleTimer m_timer;           //   cycle time slicer
};

/* Request-specific synthesis classes */

class synBadRequest : public acthSynthesis { // bad request handler
public:
     synBadRequest(
     acthSession *ses,
     ACTHCODE rc) :
          acthSynthesis(ses),
          m_rc(rc)
     {}

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler

private:                           // private member functions

     synBadRequest(                // no standard synthesis constructor
     acthSession *ses);

private:                           // private data members
     ACTHCODE m_rc;                //   what's wrong with request
};

class synSendFile : public synBase { // generic send-file handler
public:
     synSendFile(
     acthSession *ses,
     const CHAR *fileName) :
          synBase(ses),
          m_map(NULL),
          m_step(NULL)
     {
          ::stlcpy(m_fileName,fileName,GCMAXPTH);
     }

     ~synSendFile();

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler

private:                           // private member functions

     synSendFile(                  // no standard synthesis constructor
     acthSession *ses);

private:                           // private data members
     dnfMap *m_map;                //   DynaFile map
     dnfStep *m_step;              //   DynaFile step array
     CHAR m_fileName[GCMAXPTH];    //   name of file to send
};

class synGet : public synBase {    // get alias handler
public:
     synGet(
     acthSession *ses);

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler

private:                           // private data members
     CHAR m_dfltAlias[1024];       //   default alias (or error message)
     CHAR m_custAlias[ALSSIZ];     //   custom alias (if any)
};

class synSet : public synBase {    // set alias handler
public:
     synSet(
     acthSession *ses);

     ~synSet();

     ACTHCODE                      //   returns enumerated status value
     proceed();                    // agent's request handler

private:                           // private data members
     CHAR *m_errmsg;               //   error message if bad alias
     CHAR m_newAlias[ALSSIZ];      //   new custom alias
};

/* Global variables */

alsAgent *TheMan;                  // Secret Agent Man
CHAR *ahKey;                       // key to use ActiveH management module
INT timeSlice;                     // output time slice
GBOOL ahActive=FALSE;              // has management module been init?

txvCheck chkUseDflt("ALIAS_DFLT_CHK");
txvCheck chkUseCust("ALIAS_CUST_CHK");
txvHTML txvDfltAlias("DEFAULT_ALIAS");
txvHTML txvCustAlias("CUSTOM_ALIAS");

/* Interface functions */

extern "C" VOID
ahInit(VOID)                       // initialize ActiveH part of module
{
     if (::ynopt(AHALIAS)) {
          ::ahKey=::stgopt(AHALSKY);
          ::timeSlice=::numopt(TIMSLC,1,1000);
          ::TheMan=new alsAgent("Internet Alias Manager","alias");
          ::ahActive=TRUE;
     }
}

extern "C" VOID
ahClose(VOID)                      // shut down ActiveH module
{
     if (::ahActive) {
          ::free(::ahKey);
          delete ::TheMan;
     }
     ::ahActive=FALSE;
}

/* Agent functions */

acthSynthesis *                    //   new session-specific structure
alsAgent::newSynthesis(            // instantiate Synthesis class
acthSession *ses)                  //   passed to acthSynthesis's constructor
{
     CHAR tmpPath[GCMAXPTH];

     switch (ses->urlargc()) {
     case 0:
          return(new synGet(ses));
     case 1:
          if (::sameas("get",ses->urlargv(0))) {
               return(new synGet(ses));
          }
          if (::sameas("set",ses->urlargv(0))) {
               return(new synSet(ses));
          }
          if (::isfile(::makePath(tmpPath,"files",ses->urlargv(0),GCMAXPTH))) {
               return(new synSendFile(ses,tmpPath));
          }
          break;
     }
     return(new synBadRequest(ses,ACTHNOTFND));
}

/* Base synthesis functions */

synBase::synBase(
acthSession *ses) :
     acthSynthesis(ses),
     m_dnf(NULL),
     m_timer(::timeSlice)
{
}

bool
synBase::userOK(                   // does this user have access?
ACTHCODE &rc)                      //   error code if not
{
     if (!::ahActive) {
          rc=ACTHNOTFND;
          return(false);
     }
     acthUserID *usr=ses->getUser();
     if (usr == NULL) {
          rc=ACTHNOANON;
          return(false);
     }
     if (!usr->hasKey(::ahKey)) {
          rc=ACTHFORBID;
          return(false);
     }
     return(true);
}

bool
synBase::startProc()          // start proceed() processing?
{
     return(haveRoom() && m_timer.start());
}

bool
synBase::contProc(            // continue proceed() processing?
ACTHCODE rc)                       //   current proceed() return code
{
     return(rc == ACTHMORE && haveRoom() && m_timer.haveTime());
}

bool
synBase::haveRoom()           // do we have room for output?
{
     // NOTE:  512 is hard-coded bout stream buffer size
     return(::btuoba(::usrnum) > 512);
}

synBase::~synBase()
{
     if (m_dnf != NULL) {
          delete m_dnf;
          m_dnf=NULL;
     }
}

/* General request functions */

ACTHCODE
synBadRequest::proceed()
{
     return(m_rc);
}

synSendFile::~synSendFile()
{
     if (m_map != NULL) {
          delete m_map;
          m_map=NULL;
     }
     if (m_step != NULL) {
          delete m_step;
          m_step=NULL;
     }
}

ACTHCODE
synSendFile::proceed()
{
     ACTHCODE rc=ACTHMORE;

     if (startProc()) {
          if (firsttime()) {
               if (!userOK(rc)) {
                    return(rc);
               }
               m_step=new dnfStep(DNFMAPEND);
               m_map=new dnfMap(m_fileName,"Generic send-file",m_step);
               m_dnf=new dnfHandler(*m_map,bout);
          }
          do {
               if (m_dnf->process() == DNFEND) {
                    rc=ACTHDONE;
               }
          } while (contProc(rc));
     }
     return(rc);
}

/* Get alias form functions */

synGet::synGet(
acthSession *ses) :
     synBase(ses)
{
     *m_dfltAlias='\0';
     *m_custAlias='\0';
}

ACTHCODE
synGet::proceed()
{
     ACTHCODE rc=ACTHMORE;

     if (startProc()) {
          if (firsttime()) {
               if (!userOK(rc)) {
                    return(rc);
               }
               acthUserID *usr=ses->getUser();
               struct alsrec tmpals;
               ::dfaSetBlk(alsbb);
               if (!::dfaAcqEQ(&tmpals,usr->userid(),USRKEY)) {
                    ::addals(NULL,&tmpals,usr->userid());
               }
               ::dfaRstBlk();
               if (*tmpals.convid == '\0') {
                    ::setmbk(::alsmb);
                    ::stlcpy(m_dfltAlias,::stpans(::getmsg(NODFTALS)),
                             sizeof(m_dfltAlias));
                    ::rstmbk();
               }
               else {
                    ::stlcpy(m_dfltAlias,::strrpl(tmpals.convid,'*','.'),UIDSIZ);
               }
               ASSERT(m_dnf == NULL);
               if (::useals) {
                    ::stlcpy(m_custAlias,tmpals.alias,ALSSIZ);
                    m_dnf=new dnfHandler(getFormMap,bout);
               }
               else {
                    m_dnf=new dnfHandler(getDispMap,bout);
               }
          }
          chkUseDflt.setChecked(NULSTR(m_custAlias));
          chkUseCust.setChecked(!NULSTR(m_custAlias));
          txvDfltAlias.set(m_dfltAlias);
          txvCustAlias.set(m_custAlias);
          do {
               if (m_dnf->process() == DNFEND) {
                    rc=ACTHDONE;
               }
          } while (contProc(rc));
          chkUseDflt.clr();
          chkUseCust.clr();
          txvDfltAlias.clr();
          txvCustAlias.clr();
     }
     return(rc);
}

/* Set alias functions */

synSet::synSet(
acthSession *ses) :
     synBase(ses),
     m_errmsg(NULL)
{
     *m_newAlias='\0';
}

ACTHCODE
synSet::proceed()
{
     ACTHCODE rc=ACTHMORE;

     if (startProc()) {
          if (firsttime()) {
               if (!userOK(rc)) {
                    return(rc);
               }
               ::setmbk(::alsmb);
               if (::useals) {
                    CHAR numBuf[sizeof("-1234567890")];
                    if (ses->param(FP_USECUST,numBuf,sizeof(numBuf))
                     && atoi(numBuf)) {
                         ses->param(FP_ALIAS,m_newAlias,ALSSIZ);
                    }
                    acthUserID *usr=ses->getUser();
                    struct alsrec tmpals;
                    if (::newAlias(usr->userid(),m_newAlias,&tmpals)) {
                         m_dnf=new dnfHandler(setOKMap,bout);
                    }
               }
               else {
                    ::prfmsg(ALSNEN2);
               }
               ::rstmbk();
               if (m_dnf == NULL) {
                    m_errmsg=::entEncodeStr(::stpans(::prfbuf));
                    m_dnf=new dnfHandler(setErrMap,bout);
               }
          }
          do {
               switch (m_dnf->process()) {
               case ERRTEXT:
                    if (m_errmsg != NULL) {
                         bout << m_errmsg;
                    }
                    break;
               case DNFEND:
                    rc=ACTHDONE;
                    break;
               }
          } while (contProc(rc));
     }
     return(rc);
}

synSet::~synSet()
{
     if (m_errmsg != NULL) {
          delete m_errmsg;
          m_errmsg=NULL;
     }
}

/* Text variable handler functions */

const CHAR *                       //   returns text var value
txvBase::resolve(                  // resolve a text var name into its value
const CHAR *nam)                   //   name of text var being resolved
{
     (VOID)nam;
     if (m_pVal == NULL) {
          return("");
     }
     return(m_pVal);
}

VOID
txvCheck::setChecked(              // set checked status
bool isChecked)                    //   is the option checked?
{
     if (isChecked) {
          set("CHECKED");
     }
     else {
          clr();
     }
}

VOID
txvEncoded::clr()                  // clear current value
{
     if (m_pVal != NULL) {
          delete[] m_pVal;
          m_pVal=NULL;
     }
}

VOID
txvHTML::set(                      // set value to display
const CHAR *newValue)              //   to this string
{
     clr();
     m_pVal=entEncodeStr(newValue);
}

/* Miscellaneous global functions */

CHAR *                             //   returns allocated buffer
entEncodeStr(                      // encode entities in a string
const CHAR *src)                   //   unencoded string
{
     ASSERT(src != NULL);
     size_t len=entSize(src);
     CHAR *buf=new CHAR[len];
     return(entEncodeBuf(buf,src,len));
}

size_t
entSize(                           // size of entity-encoded string (incl '\0')
const CHAR *src)                   //   unencoded string
{
     CHAR c;
     size_t len=0;
     ASSERT(src != NULL);
     while ((c=*src++) != '\0') {
          switch (c) {
          case '<':
               len+=CSTRLEN("&lt;");
               break;
          case '>':
               len+=CSTRLEN("&gt;");
               break;
          case '"':
               len+=CSTRLEN("&quot;");
               break;
          case '&':
               len+=CSTRLEN("&amp;");
               break;
          default:
               ++len;
               break;
          }
     }
     return(++len);
}

CHAR *                             //   returns pointer to destination
entEncodeBuf(                      // encode string using HTML entities
CHAR *buf,                         //   buffer to receive encoded string
const CHAR *src,                   //   unencoded string
size_t bufSiz)                     //   size of destination buffer
{
     CHAR c,*pOut=buf;
     ASSERT(buf != NULL);
     ASSERT(src != NULL);
     ASSERT(bufSiz != 0);
     while ((c=*src++) != '\0') {
          const CHAR *pAdd=NULL;
          switch (c) {
          case '<':
               pAdd="&lt;";
               break;
          case '>':
               pAdd="&gt;";
               break;
          case '"':
               pAdd="&quot;";
               break;
          case '&':
               pAdd="&amp;";
               break;
          }
          size_t addLen=pAdd == NULL ? 1 : strlen(pAdd);
          if ((size_t)(pOut-buf)+addLen >= bufSiz) {
               break;
          }
          if (pAdd != NULL) {
               pOut=stpcpy(pOut,pAdd);
          }
          else {
               *pOut++=c;
          }
     }
     *pOut='\0';
     return(buf);
}
