/***************************************************************************
 *                                                                         *
 *   GALFMAH.CPP                                                           *
 *                                                                         *
 *   Copyright (c) 1997 Galacticomm, Inc.         All Rights Reserved.     *
 *                                                                         *
 *   Active HTML Forum Manager.                                            *
 *                                                                         *
 *                                            - J. Alvrus   8/14/97        *
 *                                                                         *
 ***************************************************************************/

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

#define FILREV "$Revision: 8 $"

/* Miscellaneous global constants */

#define GRPSTKSZ    20             // group stack size
#define MAXGRPSTR   (GRPSTKSZ*FORNSZ) // max group path size

                                   // internal error codes
#define ERR_NAME         10000     //   invalid group/forum name
#define ERR_KEY          10001     //   invalid key name
#define ERR_MSGLIF       10002     //   invalid message lifetime
#define ERR_CRDRATE      10003     //   invalid credit consumption rate
#define ERR_CHGWRTMSG    10004     //   invalid write message charge
#define ERR_CHGRDMSG     10005     //   invalid read message charge
#define ERR_CHGWRTATT    10006     //   invalid write attachment charge
#define ERR_CHGRDATT     10007     //   invalid read attachment charge
#define ERR_CHGWRTATTK   10008     //   invalid write att per-k charge
#define ERR_CHGRDATTK    10009     //   invalid read att per-k charge
#define ERR_PFNLVL       10010     //   invalid profanity level
#define ERR_ACCLVL       10011     //   invalid access level
#define ERR_ECHO         10012     //   invalid echo address
#define ERR_2DEEP        10013     //   groups nested too deeply

/* Form parameter identifiers */

#define FP_NAME     "Name"         // forum/group name
#define FP_TOPIC    "Topic"        // forum/group topic
#define FP_KEY      "Key"          // group key
#define FP_FORUMOP  "ForumOp"      // Forum-Op
#define FP_MSGLIFE  "MessageLife"  // forum message lifetime
#define FP_CCR      "CreditRate"   // forum credit consumption rate
#define FP_CHGWRMSG "ChargeWriteMsg" // forum charge per message written
#define FP_CHGRDMSG "ChargeReadMsg" // forum charge per message read
#define FP_CHGWRATT "ChargeWriteAtt" // forum charge per attachment upload
#define FP_CHGRDATT "ChargeReadAtt" // forum charge per attachment download
#define FP_CHGWRATK "ChargeWriteAttK" // forum charge per-kbyte for uploads
#define FP_CHGRDATK "ChargeReadAttK" // forum charge per-kbyte for downloads
#define FP_PFNLVL   "ProfanLevel"  // forum charge per-kbyte for downloads
#define FP_DFTNON   "DftNonPriv"   // default non-privileged access level
#define FP_DFTPRV   "DftPriv"      // default privileged access level
#define FP_MAXNON   "MaxNonPriv"   // maximum non-privileged access level
#define FP_DESC     "Description"  // forum description
#define FP_ECHOES   "Echoes"       // echo address string
#define FP_DATFIL   "DataFile"     // forum data path and file name
#define FP_ATTPATH  "AttPath"      // forum attachment path

#define FP_ADDFOR   "add"          // add a forum to a group
#define FP_REMFOR   "remove"       // remove a forum from a group

/* Miscellaneous global functions */

CHAR *                             //   returns pointer to destination
catGroup(                          // concatenate a group onto a group path
CHAR *dst,                         //   buffer to concatenate onto
const CHAR *src,                   //   group name to add
size_t dstSiz);                    //   size of destination buffer

CHAR *                             //   returns allocated buffer
urlEncodeStr(                      // encode special characters in a URL
const CHAR *src);                  //   unencoded URL

size_t
urlEncodeSize(                     // size of encoded URL (incl '\0')
const CHAR *src);                  //   unencoded URL

CHAR *                             //   returns pointer to destination
urlEncodeBuf(                      // encode special characters in URL
CHAR *buf,                         //   buffer to receive encoded string
const CHAR *src,                   //   unencoded URL
size_t bufSiz);                    //   size of destination buffer

CHAR *                             //   returns pointer to destination
urlDecodeBuf(                      // decode special characters in URL
CHAR *buf,                         //   buffer to receive decoded string
const CHAR *src,                   //   unencoded URL
size_t bufSiz);                    //   size of destination buffer

bool
urlNeedEncode(                     // does this character need to be encoded?
CHAR c);                           //   character to check

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

VOID
setForDskTxv(                      // set forum text vars
const struct fordsk *fdsk);        //   from on-disk definition format

VOID
setForDefTxv(                      // set forum text vars
const struct fordef *fdef);        //   from in-memory definition format

VOID
clrForumTxv();                     // clear forum-related text variables

bool
valfdfnam(                         /* is this a valid forum data file name?*/
const CHAR *nam);                  /*   file name to change                */

CHAR *                             /*   returns copy of pointer to buffer  */
fixfdfnam(                         /* fix up forum data file name          */
CHAR *nambuf);                     /*   file name buffer (must be GCMAXPTH)*/

/* Miscellaneous global classes */

class treePictures {               // tree picture management class
public:
     treePictures();
     ~treePictures();

     VOID
     empty(                        // empty group picture
     ostream &ost);                //   output stream to write to

     VOID
     withfor(                      // group with forums picture
     ostream &ost);                //   output stream to write to

     VOID
     blank(                        // tree blank space picture
     ostream &ost);                //   output stream to write to

     VOID
     trunk(                        // tree trunk picture
     ostream &ost);                //   output stream to write to

     VOID
     branch(                       // tree branch picture
     ostream &ost);                //   output stream to write to

     VOID
     leaf(                         // tree leaf picture
     ostream &ost);                //   output stream to write to

private:                           // private member functions

     VOID
     outputPic(                    // output HTML code for a picture
     ostream &ost,                 //   output stream to write to
     const CHAR *picPath,          //   path & file name of picture file
     const CHAR *altStr);          //   alternate string

     const CHAR *
     urlPath(                      // generate path of file in URL style
     const CHAR *dirName,          //   directory name
     const CHAR *fileName);        //   file name

private:                           // private data members
     CHAR *m_rootdir;              //   directory where picture files kept
     CHAR *m_empty;                //   empty group picture
     CHAR *m_withfor;              //   group with forums picture
     CHAR *m_blank;                //   tree trunk picture
     CHAR *m_trunk;                //   tree trunk picture
     CHAR *m_branch;               //   tree branch picture
     CHAR *m_leaf;                 //   tree leaf picture
     CHAR *m_altEmpty;             //   empty group alternate string
     CHAR *m_altFor;               //   group with forums alternate string
     CHAR *m_altBlank;             //   tree trunk alternate string
     CHAR *m_altTrunk;             //   tree trunk alternate string
     CHAR *m_altBranch;            //   tree branch alternate string
     CHAR *m_altLeaf;              //   tree leaf alternate string
     CHAR *m_preCode;              //   HTML code preceeding picture path
     CHAR *m_midCode;              //   HTML code between picture and alternate
     CHAR *m_postCode;             //   HTML code following alternate string
     CHAR m_pathBuf[GCMAXPTH];     //   buffer for returning paths
};

class forumLock {                  // class for conflict checking
public:
     forumLock(                    // required constructor
     VOID *workBuf,                //   work buffer to be used
     USHORT forum);                //   forum ID to lock

     ~forumLock();

private:                           // private data members
     VOID *m_work;                 //   GME work buffer being used
     USHORT m_forum;               //   forum ID that is locked
};

GBOOL                              /*   returns TRUE if there is a conflict*/
forumLockCfl(                      /* check-for-conflict function          */
VOID *pWork,                       /*   work area being used for operation */
USHORT forum,                      /*   forum ID with conflict             */
LONG msgid);                       /*   message ID with conflict           */

/* Text variable classes */

class fmTimeStampTxv : public tvbDefinition { // time stamp text variable
public:
     fmTimeStampTxv() :
          tvbDefinition("FM_TIME_STAMP")
     {
          registerDef();
     }

     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
} txvTimeStamp;                    // the one instance of this variable

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

     fmTextVar(                    // constructor for static variables
     const CHAR *name,             //   variable name
     const CHAR *value) :          //   pointer to buffer containing value
          tvbDefinition(name)      //   (contents must be persistent)
     {
          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 fmIntTxv : public fmTextVar { // txt var for integer quantities
public:
     fmIntTxv(                     // constructor for dynamic variables
     const CHAR *name) :           //   variable name
          fmTextVar(name)
     {}

     VOID
     set(                          // set value to display
     LONG newValue);               //   to this

     VOID
     set(                          // set value to display
     ULONG newValue);              //   to this

     VOID
     set(                          // set value to display
     INT newValue);                //   to this

     VOID
     set(                          // set value to display
     UINT newValue);               //   to this

     VOID
     set(                          // set value to display
     SHORT newValue);              //   to this

     VOID
     set(                          // set value to display
     USHORT newValue);             //   to this

private:                           // private member functions

     VOID
     set(                          // this version is not used
     const CHAR *newValue)
     {}

private:                           // private data members
     CHAR m_numBuf[sizeof("-1234567890")]; // buffer for number
};

class fmDateTxv : public fmTextVar { // txt var for integer quantities
public:
     fmDateTxv(                    // constructor for dynamic variables
     const CHAR *name) :           //   variable name
          fmTextVar(name)
     {}

     VOID
     set(                          // set value to display
     USHORT dosDate);              //   DOS date format

private:                           // private member functions

     VOID
     set(                          // this version is not used
     const CHAR *newValue)
     {}

private:                           // private data members
     CHAR m_dateBuf[sizeof("12/31/1999")]; // buffer for date string
};

class fmTimeTxv : public fmTextVar { // txt var for integer quantities
public:
     fmTimeTxv(                    // constructor for dynamic variables
     const CHAR *name) :           //   variable name
          fmTextVar(name)
     {}

     VOID
     set(                          // set value to display
     USHORT dosTime);              //   DOS time format

private:                           // private member functions

     VOID
     set(                          // this version is not used
     const CHAR *newValue)
     {}

private:                           // private data members
     CHAR m_timeBuf[sizeof("12:00:00")]; // buffer for time string
};

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

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

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

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

class fmURLTxv : public fmEncodedTxv { // txt var for URL components
public:
     fmURLTxv(                     // constructor for dynamic variables
     const CHAR *name) :           //   variable name
          fmEncodedTxv(name)
     {}

     fmURLTxv(                     // constructor for static variables
     const CHAR *name,             //   variable name
     const CHAR *value) :          //   pointer to buffer containing value
          fmEncodedTxv(name)
     {
          set(value);
     }

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

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

     fmHTMLTxv(                    // constructor for static variables
     const CHAR *name,             //   variable name
     const CHAR *value) :          //   pointer to buffer containing value
          fmEncodedTxv(name)
     {
          set(value);
     }

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

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

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

class fmSelectTxv : public fmTextVar { // txt var for "SELECTED" items in forms
public:
     fmSelectTxv(
     const CHAR *name) :
          fmTextVar(name)
     {}

     VOID
     setSelected(                  // set checked status
     bool isSelected);             //   is the option checked?
};

class fmAccessTxv {                // handler for access list text variables
public:
     fmAccessTxv(                  // required constructor
     const CHAR *idStr);           //   access type ID

     VOID
     set(                          // set contents
     INT acc);                     //   access level to present

     VOID
     clr();                        // clear contents

private:                           // private data members
     fmSelectTxv *pVar[7];         //   individual text variable handlers
};

class fmProfanTxv {                // handler for profanity list text variables
public:
     fmProfanTxv(                  // required constructor
     const CHAR *prefix);          //   name prefix

     VOID
     set(                          // set contents
     INT pfnlvl);                  //   profanity level to present

     VOID
     clr();                        // clear contents

private:                           // private data members
     fmSelectTxv *pVar[5];         //   individual text variable handlers
};

/* All DNF steps and maps */

enum {GRPTREE};
dnfStep grpListStep[]={            // group list DNF map
     dnfStep(DNFTABLE),
          dnfStep(DNFCOLUMN,GRPTREE,"GROUP_LIST_TREE"),
     dnfStep(DNFTABLEEND),
     dnfStep(DNFMAPEND)
};
dnfMap grpListMap("galacth/galfmah/fmgrplst.htm",
                  "List of Groups",grpListStep);

dnfStep grpGetCrtStep[]={          // create group DNF map
     dnfStep(DNFMAPEND)
};
dnfMap grpGetCrtMap("galacth/galfmah/fmcrtgrp.htm",
                    "Create Group Form",grpGetCrtStep);

dnfStep grpSetCrtStep[]={          // create group response DNF map
     dnfStep(DNFMAPEND)
};
dnfMap grpSetCrtMap("galacth/galfmah/fmcrtgok.htm",
                    "Create Group Response",grpSetCrtStep);

enum {ERRTEXT};
dnfStep grpSetCrtErrStep[]={
     dnfStep(DNFWATCH,ERRTEXT,"FM_ERROR_MSG"),
     dnfStep(DNFMAPEND)
};
dnfMap grpSetCrtErrMap("galacth/galfmah/fmcrtger.htm",
                       "Create Group Error Message",grpSetCrtErrStep);

dnfStep grpGetModStep[]={          // modify group DNF map
     dnfStep(DNFMAPEND)
};
dnfMap grpGetModMap("galacth/galfmah/fmmodgrp.htm",
                    "Modify Group Form",grpGetModStep);

dnfStep grpSetModStep[]={          // modify group response DNF map
     dnfStep(DNFMAPEND)
};
dnfMap grpSetModMap("galacth/galfmah/fmmodgok.htm",
                    "Modify Group Response",grpSetModStep);

//enum {ERRTEXT};                  // defined above
dnfStep grpSetModErrStep[]={
     dnfStep(DNFWATCH,ERRTEXT,"FM_ERROR_MSG"),
     dnfStep(DNFMAPEND)
};
dnfMap grpSetModErrMap("galacth/galfmah/fmmodger.htm",
                       "Modify Group Error Message",grpSetModErrStep);

dnfStep grpGetDelStep[]={          // delete group DNF map
     dnfStep(DNFMAPEND)
};
dnfMap grpGetDelMap("galacth/galfmah/fmdelgrp.htm",
                    "Delete Group Form",grpGetDelStep);

dnfStep grpSetDelStep[]={          // delete group response DNF map
     dnfStep(DNFMAPEND)
};
dnfMap grpSetDelMap("galacth/galfmah/fmdelgok.htm",
                    "Delete Group Response",grpSetDelStep);

//enum {ERRTEXT};                  // defined above
dnfStep grpSetDelErrStep[]={
     dnfStep(DNFWATCH,ERRTEXT,"FM_ERROR_MSG"),
     dnfStep(DNFMAPEND)
};
dnfMap grpSetDelErrMap("galacth/galfmah/fmdelger.htm",
                       "Delete Group Error Message",grpSetDelErrStep);

dnfStep grpForFrameStep[]={          // group forums frame DNF map
     dnfStep(DNFMAPEND)
};
dnfMap grpForFrameMap("galacth/galfmah/fmselfrm.htm",
                      "Group Forums Frame",grpForFrameStep);

dnfStep grpForHeadStep[]={           // group forums header DNF map
     dnfStep(DNFMAPEND)
};
dnfMap grpForHeadMap("galacth/galfmah/fmselhed.htm",
                      "Group Forums Head",grpForHeadStep);

enum {FORLINE};
dnfStep grpForAllStep[]={            // group-related all-forums list DNF map
     dnfStep(DNFTABLE),
          dnfStep(DNFCOLUMN,FORLINE,"FORUM_LIST_LINE"),
     dnfStep(DNFTABLEEND),
     dnfStep(DNFMAPEND)
};
dnfMap grpForAllMap("galacth/galfmah/fmselfor.htm",
                  "List of Forums to Select",grpForAllStep);

//enum {FORLINE};                  // defined above
dnfStep grpForSelStep[]={          // forums in a group list DNF map
     dnfStep(DNFTABLE),
          dnfStep(DNFCOLUMN,FORLINE,"FORUM_LIST_LINE"),
     dnfStep(DNFTABLEEND),
     dnfStep(DNFMAPEND)
};
dnfMap grpForSelMap("galacth/galfmah/fmselgrp.htm",
                    "List of Forums in a Group",grpForSelStep);

//enum {ERRTEXT};                  // defined above
dnfStep grpForSelErrStep[]={       // add forum to group error DNF map
     dnfStep(DNFWATCH,ERRTEXT,"FM_ERROR_MSG"),
     dnfStep(DNFMAPEND)
};
dnfMap grpForSelErrMap("galacth/galfmah/fmselger.htm",
                       "Add Forum to Group Error Message",grpForSelErrStep);

//enum {FORLINE};                  // defined above
dnfStep forListStep[]={            // forum list DNF map
     dnfStep(DNFTABLE),
          dnfStep(DNFCOLUMN,FORLINE,"FORUM_LIST_LINE"),
     dnfStep(DNFTABLEEND),
     dnfStep(DNFMAPEND)
};
dnfMap forListMap("galacth/galfmah/fmforlst.htm",
                  "List of Forums",forListStep);

dnfStep forGetCrtStep[]={          // get create forum form DNF map
     dnfStep(DNFMAPEND)
};
dnfMap forGetCrtMap("galacth/galfmah/fmcrtfor.htm",
                    "Create Forum Form",forGetCrtStep);

dnfStep forSetCrtStep[]={          // create forum OK response DNF map
     dnfStep(DNFMAPEND)
};
dnfMap forSetCrtMap("galacth/galfmah/fmcrtfok.htm",
                    "Create Forum Response",forSetCrtStep);

//enum {ERRTEXT};                  // defined above
dnfStep forSetCrtErrStep[]={       // create forum error response DNF map
     dnfStep(DNFWATCH,ERRTEXT,"FM_ERROR_MSG"),
     dnfStep(DNFMAPEND)
};
dnfMap forSetCrtErrMap("galacth/galfmah/fmcrtfer.htm",
                       "Create Forum Error Message",forSetCrtErrStep);

enum {FORDESC};
dnfStep forGetModStep[]={       // get modify forum form DNF map
     dnfStep(DNFWATCH,FORDESC,"FM_FORUM_DESC"),
     dnfStep(DNFMAPEND)
};
dnfMap forGetModMap("galacth/galfmah/fmmodfor.htm",
                    "Modify Forum Form",forGetModStep);

dnfStep forSetModStep[]={          // modify forum OK response DNF map
     dnfStep(DNFMAPEND)
};
dnfMap forSetModMap("galacth/galfmah/fmmodfok.htm",
                    "Modify Forum Response",forSetModStep);

//enum {ERRTEXT};                  // defined above
dnfStep forSetModErrStep[]={       // modify forum error response DNF map
     dnfStep(DNFWATCH,ERRTEXT,"FM_ERROR_MSG"),
     dnfStep(DNFMAPEND)
};
dnfMap forSetModErrMap("galacth/galfmah/fmmodfer.htm",
                       "Modify Forum Error Message",forSetModErrStep);

dnfStep forGetDelStep[]={          // delete forum DNF map
     dnfStep(DNFMAPEND)
};
dnfMap forGetDelMap("galacth/galfmah/fmdelfor.htm",
                    "Delete Forum Form",forGetDelStep);

dnfStep forSetDelStep[]={          // delete forum response DNF map
     dnfStep(DNFMAPEND)
};
dnfMap forSetDelMap("galacth/galfmah/fmdelfok.htm",
                    "Delete Forum Response",forSetDelStep);

//enum {ERRTEXT};                  // defined above
dnfStep forSetDelErrStep[]={
     dnfStep(DNFWATCH,ERRTEXT,"FM_ERROR_MSG"),
     dnfStep(DNFMAPEND)
};
dnfMap forSetDelErrMap("galacth/galfmah/fmdelfer.htm",
                       "Delete Forum Error Message",forSetDelErrStep);

/* Forum Manager agent */

class fmAgent : public acthAgent { // Forum Manager agent
public:
     fmAgent(                      // constructor/module initializor
     const CHAR *apnam,            //   description (eg "File Libraries")
     const CHAR *upfx);            //   URL prefix (eg "library")

     ~fmAgent();

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

/* Base synthesis classes */

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

     ~fmSynthesis();

     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:
     fmSynthesis();                // no default constructor

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

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

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

class synGroupURL : public fmSynthesis { // for requests w/ group in URL
public:
     synGroupURL(
     acthSession *ses) :
          fmSynthesis(ses)
     {}

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

protected:                         // protected member functions

     bool                          //   returns FALSE if group doesn't exist
     initGrpInfo();                // initialize group path & name (from URL)

     VOID
     getGrpParms(                  // get group header form parameters
     struct forgrp &grpBuf);       //   buffer to fill in

     VOID
     setGroupTxv();                // set up group-related text vars

     VOID
     clrGroupTxv();                // clear group-related text vars

protected:                         // protected data members
     USHORT m_grpID;               //   current group ID
     CHAR m_grpPath[MAXGRPSTR];    //   current group path
     CHAR m_grpName[FORNSZ];       //   current group topic
     CHAR m_grpTopic[TPCSIZ];      //   current group topic
     CHAR m_grpKey[KEYSIZ];        //   current group key
};

/* Utility 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 fmSynthesis { // generic send-file handler
public:
     synSendFile(
     acthSession *ses,
     const CHAR *fileName) :
          fmSynthesis(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
};

/* Group management syntheses */

#define GL_HASSIB   1              // group has siblings
#define GL_HASFOR   2              // group has forums

class synGrpList : public fmSynthesis { // group list handler
public:
     synGrpList(
     acthSession *ses);

     ~synGrpList();

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

private:                           // private member functions

     bool
     nextGroup();                  // find the next group

     bool
     pushGroup();                  // push group context onto stack

     VOID
     popGroup();                   // pop group context from stack

     VOID
     formTree();                   // form HTML code for tree

     CHAR *                        //   returns pointer to destination
     groupPath(                    // generate current group path
     CHAR *dst,                    //   buffer to form in
     size_t dstSiz);               //   size of destination buffer

private:                           // private data members
     ostrstream *m_psout;          //   string in which to form tree HTML
     INT m_sp;                     //   group stack pointer
     INT m_treeDone;               //   number of tree HTML bytes output
     enum {START,OUTDNF,OUTTREE} m_state; // processing state
     bool m_firstGroup;            //   is this the very first group output?
     struct {                      //   group stack structure
          USHORT flags;            //     boolean properties
          USHORT id;               //     group ID
          CHAR name[FORNSZ];       //     group name
     } m_grpstk[GRPSTKSZ+1];       //   group stack
     CHAR m_grpPath[MAXGRPSTR];    //   current group path
     CHAR m_grpTopic[TPCSIZ];      //   current group topic
     CHAR m_grpKey[KEYSIZ];        //   current group key
};

class synGrpGetMgr : public synGroupURL { // get group management form
public:
     synGrpGetMgr(
     acthSession *ses,
     dnfMap &map);

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

private:                           // private member functions

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

private:                           // private data members
     dnfMap &m_map;                //   DynaFile map to use
};

class synGrpSetMgr : public synGroupURL { // group management response handler
public:
     synGrpSetMgr(
     acthSession *ses);

     ~synGrpSetMgr();

protected:                         // implementation-specific member functions

     virtual VOID
     prep()=0;                     // do request-specific preparation

     virtual VOID
     process()=0;                  // do request-specific processing

     virtual dnfMap &              //   returns reference to error map
     getErrMap()=0;                // get DNF map for error response

protected:                         // protected member functions

     VOID
     startResp(                    // processing done, start response
     dnfMap &map);                 //   DNF map for response

     VOID
     setError(                     // report error
     INT err);                     //   code describing error

private:                           // private member functions

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

     const CHAR *                  //   returns pointer to temporary buffer
     errText();                    // generate text of error message

protected:                         // protected data members
     struct forgrp m_grpBuf;       //   buffer for creating group
     CHAR m_work[GMEWRKSZ];        //   GME work buffer

private:                           // private data members
     INT m_err;                    //   process error code
     enum {START,PROCESS,GENERR,GENRESP} m_state; // processing state
};

class synGrpSetCrt : public synGrpSetMgr { // create group response handler
public:
     synGrpSetCrt(
     acthSession *ses) :
          synGrpSetMgr(ses)
     {}

private:

     VOID
     prep();                       // do request-specific preparation

     VOID
     process();                    // do request-specific processing

     dnfMap &                      //   returns reference to error map
     getErrMap();                  // get DNF map for error response
};

class synGrpSetMod : public synGrpSetMgr { // modify group response handler
public:
     synGrpSetMod(
     acthSession *ses) :
          synGrpSetMgr(ses)
     {}

private:

     VOID
     prep();                       // do request-specific preparation

     VOID
     process();                    // do request-specific processing

     dnfMap &                      //   returns reference to error map
     getErrMap();                  // get DNF map for error response
};

class synGrpSetDel : public synGroupURL { // delete group response handler
public:
     synGrpSetDel(
     acthSession *ses);

     ~synGrpSetDel();

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

     const CHAR *                  //   returns pointer to temporary buffer
     errText();                    // generate text of error message

private:                           // private data members
     INT m_err;                    //   process error code
     enum {START,PROCESS,GENERR,GENRESP} m_state; // processing state
     CHAR m_work[GMEWRKSZ];        //   GME work buffer
};

class synGrpForList : public synGroupURL { // group-related forum list handler
public:
     synGrpForList(
     acthSession *ses,
     dnfMap &map);

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

protected:                         // protected member functions

     virtual const struct fordef * //   returns ptr to forum (NULL when done)
     nextForum()=0;                // get next forum to output

     bool                          //   returns false if error
     prep(                         // prepare to process request
     ACTHCODE &rc);                //   updated return code if error

     bool                          //   returns false when done
     outputList();                 // output forum list

private:                           // private member functions

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

     bool                          //   returns FALSE if no more
     prepNextForum();              // prepare next forum for text vars

private:                           // private data members
     dnfMap &m_map;                //   DynaFile map to use
     struct fordef m_fdef;         //   current forum definition
};

class synGrpForAll : public synGrpForList { // group-related all-forums list
public:
     synGrpForAll(
     acthSession *ses);

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

protected:                         // protected member functions

     const struct fordef *         //   returns ptr to forum (NULL when done)
     nextForum();                  // get next forum to output

private:                           // private data members
     CHAR m_curForName[FORNSZ];    //   current forum name
};

class synGrpForSel : public synGrpForList { // group-related all-forums list
public:
     synGrpForSel(
     acthSession *ses);

     ~synGrpForSel();

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

protected:                         // protected member functions

     const struct fordef *         //   returns ptr to forum (NULL when done)
     nextForum();                  // get next forum to output

private:                           // private member functions

     VOID
     getForInfo(                   // get forum info from parameter
     const CHAR *paramName);       //   name of parameter to use

     const CHAR *                  //   returns pointer to temporary buffer
     errText();                    // generate text of error message

private:                           // private data members
     INT m_err;                    //   GME error code in processing
     enum {START,ADDFOR,REMFOR,GENERR,GENRESP} m_state; // processing state
     USHORT m_forum;               //   forum ID being added/removed
     CHAR m_curForName[FORNSZ];    //   current forum name
     CHAR m_work[GMEWRKSZ];        //   GME work buffer
};

/* Forum management syntheses */

class synForList : public fmSynthesis { // forum list handler
public:
     synForList(
     acthSession *ses,
     dnfMap &map);

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

private:                           // private member functions

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

private:                           // private data members
     dnfMap &m_map;                //   DynaFile map to use
     struct fordef m_fdef;         //   current forum definition
};

class synForSetMgr : public fmSynthesis { // base class for forum create/modify
public:
     synForSetMgr(
     acthSession *ses);

     ~synForSetMgr();

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

protected:                         // protected member functions

     virtual dnfMap &              //   returns reference to error map
     getOKMap()=0;                 // get DNF map for success response

     virtual dnfMap &              //   returns reference to error map
     getErrMap()=0;                // get DNF map for error response

     bool                          //   returns false if error
     getShort(                     // get short integer from form response
     const CHAR *paramName,        //   form paramter name
     SHORT &buf);                  //   buffer to receive value

     bool                          //   returns false if error in data
     getName(                      // get forum name from form response
     CHAR *name);                  //   buffer to receive name

     bool                          //   returns false if error in data
     getForumOp(                   // get Forum-Op from form response
     CHAR *forop);                 //   buffer to receive name

     bool                          //   returns false if error in data
     getMsgLife(                   // get message lifetime from form response
     SHORT &msglif);               //   buffer to receive lifetime

     bool                          //   returns false if error in data
     getCharges(                   // get forum charges from form response
     SHORT &ccr,                   //   buffers to receive credit charges
     SHORT &chgmsg,                //   ...
     SHORT &chgrdm,
     SHORT &chgatt,
     SHORT &chgadl,
     SHORT &chgupk,
     SHORT &chgdpk);

     bool                          //   returns false if error in data
     getPfnLvl(                    // get profanity level from form response
     SHORT &pfnlvl);               //   buffer to receive profanity level

     bool                          //   returns false if error in data
     getAccess(                    // get access settings from form response
     SHORT &dfnpv,                 //   buffers to receive access settings
     SHORT &dfprv,                 //   ...
     SHORT &mxnpv);

     bool                          //   returns false if error in data
     getKey(                       // get key from form response
     CHAR *forlok);                //   buffer to receive key

     VOID
     getDesc();                    // get description from form response

     bool                          //   returns false if error in data
     getEchoes(                    // get echoes from form response
     SHORT &necho);                //   buffer to receive number of echoes

     VOID
     hdlProcess(                   // handle return code from GME processing
     INT gmeRC);                   //   GME status code

     ACTHCODE
     prcError();                   // process error file output

     ACTHCODE
     prcResp();                    // process response file output

     VOID
     setError(                     // report error
     INT err);                     //   code describing error

     const CHAR *                  //   returns pointer to temporary buffer
     errText();                    // generate text of error message

protected:                         // protected data members
     CHAR *m_echoes;               //   echo array
     INT m_err;                    //   process error code
     enum {START,PROCESS,GENERR,GENRESP} m_state; // processing state
     CHAR m_work[GMEWRKSZ];        //   GME work buffer
     CHAR m_desc[MAXFDESC];        //   forum description
};

class synForGetCrt : public fmSynthesis {
public:
     synForGetCrt(
     acthSession *ses);

     ~synForGetCrt();

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

private:                           // private data members
     struct fordsk m_fdsk;         //   buffer for forum info
     enum {START,RECFIL,OUTPUT} m_state; // processing state
     CHAR m_work[GMEWRKSZ];        //   GME work buffer
};

class synForSetCrt : public synForSetMgr { // create forum response handler
public:
     synForSetCrt(
     acthSession *ses);

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

private:                           // private member functions

     dnfMap &                      //   returns reference to error map
     getOKMap();                   // get DNF map for success response

     dnfMap &                      //   returns reference to error map
     getErrMap();                  // get DNF map for error response

private:                           // private data members
     struct fordsk m_fdsk;         //   buffer for forum info
};

class synForGetMod : public fmSynthesis {
public:
     synForGetMod(
     acthSession *ses);

     ~synForGetMod();

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

private:                           // private data members
     CHAR *m_echoList;             //   ';'-delimited echo list
     CHAR *m_dptr;                 //   output pointer for description
     CHAR *m_desc;                 //   forum description
     enum {START,OUTHEAD,OUTDESC} m_state; // processing state
     struct fordsk m_fdsk;         //   buffer for forum info
};

class synForSetMod : public synForSetMgr { // modify forum response handler
public:
     synForSetMod(
     acthSession *ses);

     ~synForSetMod();

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

private:                           // private member functions

     dnfMap &                      //   returns reference to error map
     getOKMap();                   // get DNF map for success response

     dnfMap &                      //   returns reference to error map
     getErrMap();                  // get DNF map for error response

private:                           // private data members
     forumLock *m_lock;            //   lock for conflict checking
     struct fordef m_fdef;         //   buffer for forum info
};

class synForGetDel : public fmSynthesis {
public:
     synForGetDel(
     acthSession *ses);

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

private:                           // private data members
     struct fordef m_fdef;         //   buffer for forum info
};

class synForSetDel : public fmSynthesis { // modify forum response handler
public:
     synForSetDel(
     acthSession *ses);

     ~synForSetDel();

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

     const CHAR *                  //   returns pointer to temporary buffer
     errText();                    // generate text of error message

private:                           // private data members
     forumLock *m_lock;            //   lock for conflict checking
     INT m_err;                    //   process error code
     enum {START,PROCESS,GENERR,GENRESP} m_state; // processing state
     struct fordef m_fdef;         //   buffer for forum info
     CHAR m_work[GMEWRKSZ];        //   GME work buffer
};

/* Global variables */

fmAgent TheMan("Forum Manager","forum-manager"); // Secret Agent Man

HMCVFILE fmmb;                     // forum manager MCV file handle

CHAR rootGroupName[FORNSZ];        // name used to describe root group
INT timeSlice;                     // time slice for request processing
treePictures *pTreePix;            // global instance for general use
USHORT *forumLockID;               // array of locked forum IDs
VOID **forumLockWork;              // array of unlocked work area pointers
INT forumLockNum=0;                // number of locks in use

fmHTMLTxv *txvDftForKey;

// forum-related text variables
fmIntTxv txvForumID("FM_FORUM_ID");
fmURLTxv txvForNameURL("FM_FORNAME_URL");
fmHTMLTxv txvForNameDisp("FM_FORNAME_DISP");
fmHTMLTxv txvForTopic("FM_FORUM_TOPIC");
fmHTMLTxv txvForumOp("FM_FORUM_OP");
fmHTMLTxv txvDataFile("FM_DATA_FILE");
fmHTMLTxv txvAttPath("FM_ATT_PATH");
fmIntTxv txvNumThr("FM_NUM_THREADS");
fmIntTxv txvNumMsg("FM_NUM_MSGS");
fmIntTxv txvNumFiles("FM_NUM_FILES");
fmIntTxv txvNumWaitApp("FM_NUM_W4APP");
fmHTMLTxv txvForKey("FM_FORUM_KEY");
fmAccessTxv accDftNon("DFTNON");
fmAccessTxv accDftPrv("DFTPRV");
fmAccessTxv accMaxNon("MAXNON");
fmIntTxv txvMsgLife("FM_MSG_LIFE");
fmIntTxv txvChgWrtMsg("FM_CHG_WRTMSG");
fmIntTxv txvChgRdMsg("FM_CHG_RDMSG");
fmIntTxv txvChgWrtAtt("FM_CHG_WRTATT");
fmIntTxv txvChgRdAtt("FM_CHG_RDATT");
fmIntTxv txvChgWrtAttK("FM_CHG_WRTATTK");
fmIntTxv txvChgRdAttK("FM_CHG_RDATTK");
fmIntTxv txvCrdRate("FM_CREDIT_RATE");
fmProfanTxv pfnForum("FM_PFNLVL");
fmDateTxv datCrtForum("FM_FOR_CRT_DATE");
fmTimeTxv timCrtForum("FM_FOR_CRT_TIME");
fmHTMLTxv txvEchoList("FM_ECHO_LIST");

// group-related text variables
fmURLTxv txvGrpPathURL("FM_GRPPATH_URL");
fmHTMLTxv txvGrpName("FM_GROUP_NAME");
fmHTMLTxv txvGrpPathDisp("FM_GRPPATH_DISP");
fmHTMLTxv txvGrpTopic("FM_GROUP_TOPIC");
fmHTMLTxv txvGrpKey("FM_GROUP_KEY");

/* Agent functions */

fmAgent::fmAgent(                  // constructor/module initializor
const CHAR *apnam,                 //   description (eg "File Libraries")
const CHAR *upfx) :                //   URL prefix (eg "library")
     acthAgent(apnam,upfx)
{
     ::init__galme();
     ::txvDftForKey=new fmHTMLTxv("FM_DFT_FOR_KEY",::forprv);
     ::fmmb=::opnmsg("galfmah.mcv");
     ::stlcpy(::rootGroupName,rawmsg(ROOTNAME),FORNSZ);
     ::timeSlice=(USHORT)numopt(TIMSLC,1,1000);
     ::pTreePix=new treePictures;
     ::forumLockID=new USHORT[::nterms];
     ::forumLockWork=new VOID *[::nterms];
     ::setcfl(forumLockCfl);
     registerAgent(acthVersion);
}

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

     if (ses->urlargc() == 0) {
          return(new synSendFile(ses,"galacth/galfmah/fmopen.htm"));
     }
     if (::sameas("group",ses->urlargv(0))) {
          if (ses->urlargc() == 1) {
               return(new synGrpList(ses));
          }
          if (ses->urlargc() == 2) {
               if (::sameas("list",ses->urlargv(1))) {
                    return(new synGrpList(ses));
               }
               if (::sameas("get-modify",ses->urlargv(1))) {
                    return(new synSendFile(ses,"galacth/galfmah/fmrootg.htm"));
               }
          }
          if (ses->urlargc() >= 2) {
               if (::sameas("get-create",ses->urlargv(1))) {
                    return(new synGrpGetMgr(ses,grpGetCrtMap));
               }
               if (::sameas("set-create",ses->urlargv(1))) {
                    return(new synGrpSetCrt(ses));
               }
               if (::sameas("forums",ses->urlargv(1))) {
                    return(new synGrpGetMgr(ses,grpForFrameMap));
               }
               if (::sameas("forums-head",ses->urlargv(1))) {
                    return(new synGrpGetMgr(ses,grpForHeadMap));
               }
               if (::sameas("forums-all",ses->urlargv(1))) {
                    return(new synGrpForAll(ses));
               }
               if (::sameas("forums-selected",ses->urlargv(1))) {
                    return(new synGrpForSel(ses));
               }
          }
          if (ses->urlargc() > 2) {
               if (::sameas("get-modify",ses->urlargv(1))) {
                    return(new synGrpGetMgr(ses,grpGetModMap));
               }
               if (::sameas("set-modify",ses->urlargv(1))) {
                    return(new synGrpSetMod(ses));
               }
               if (::sameas("get-delete",ses->urlargv(1))) {
                    return(new synGrpGetMgr(ses,grpGetDelMap));
               }
               if (::sameas("set-delete",ses->urlargv(1))) {
                    return(new synGrpSetDel(ses));
               }
          }
     }
     else if (::sameas("forum",ses->urlargv(0))) {
          if (ses->urlargc() == 1) {
               return(new synForList(ses,forListMap));
          }
          if (ses->urlargc() == 2) {
               if (::sameas("list",ses->urlargv(1))) {
                    return(new synForList(ses,forListMap));
               }
               if (::sameas("get-create",ses->urlargv(1))) {
                    return(new synForGetCrt(ses));
               }
               if (::sameas("set-create",ses->urlargv(1))) {
                    return(new synForSetCrt(ses));
               }
          }
          if (ses->urlargc() == 3) {
               if (::sameas("get-modify",ses->urlargv(1))) {
                    return(new synForGetMod(ses));
               }
               if (::sameas("set-modify",ses->urlargv(1))) {
                    return(new synForSetMod(ses));
               }
               if (::sameas("get-delete",ses->urlargv(1))) {
                    return(new synForGetDel(ses));
               }
               if (::sameas("set-delete",ses->urlargv(1))) {
                    return(new synForSetDel(ses));
               }
          }
     }
     else if (ses->urlargc() == 1
           && ::isfile(::makePath(tmpPath,"files",ses->urlargv(0),GCMAXPTH))) {
          return(new synSendFile(ses,tmpPath));
     }
     return(new synBadRequest(ses,ACTHNOTFND));
}

fmAgent::~fmAgent()                // destructor
{
     delete ::txvDftForKey;
     delete ::pTreePix;
     delete[] ::forumLockID;
     delete[] ::forumLockWork;
}

/* Base synthesis functions */

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

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

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

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

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

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

bool                               //   returns FALSE if group doesn't exist
synGroupURL::initGrpInfo()         // initialize group path & name (from URL)
{

     *m_grpPath='\0';
     *m_grpName='\0';
     *m_grpTopic='\0';
     *m_grpKey='\0';
     CHAR *tmpBuf=NULL;
     size_t bufSiz;
     if (ses->urlargc() > 2) {
          bufSiz=FORNSZ*ses->urlargc()-2;
          tmpBuf=new CHAR[bufSiz];
          *tmpBuf='\0';
          for (INT i=2 ; i < ses->urlargc() ; ++i) {
               CHAR tmpName[FORNSZ];
               ::urlDecodeBuf(tmpName,ses->urlargv(i),FORNSZ);
               ::catGroup(tmpBuf,tmpName,bufSiz);
          }
          ::stlcpy(m_grpPath,tmpBuf,MAXGRPSTR);
     }
     if (*m_grpPath == '\0') {
          m_grpID=ROOTGRP;
     }
     else if ((m_grpID=::gmeGetGrpID(tmpBuf)) == ROOTGRP) {
          delete[] tmpBuf;
          return(false);
     }
     else {
          const struct forgrp *grpptr=::gmeGetGrpP(m_grpID);
          ::stlcpy(m_grpName,grpptr->name,FORNSZ);
          ::stlcpy(m_grpTopic,grpptr->topic,TPCSIZ);
          ::stlcpy(m_grpKey,grpptr->key,KEYSIZ);
     }
     delete[] tmpBuf;
     return(true);
}

VOID
synGroupURL::getGrpParms(          // get group header form parameters
struct forgrp &grpBuf)             //   buffer to fill in
{
     ses->param(FP_NAME,grpBuf.name,FORNSZ);
     ses->param(FP_TOPIC,grpBuf.topic,TPCSIZ);
     ses->param(FP_KEY,grpBuf.key,KEYSIZ);
}

VOID
synGroupURL::setGroupTxv()         // set up group-related text vars
{
     ::txvGrpPathURL.set(m_grpPath);
     if (m_grpID == ROOTGRP) {
          ::txvGrpPathDisp.set(::rootGroupName);
          ::txvGrpName.set(::rootGroupName);
          ::txvGrpTopic.clr();
          ::txvGrpKey.clr();
     }
     else {
          ::txvGrpPathDisp.set(m_grpPath);
          ::txvGrpName.set(m_grpName);
          ::txvGrpTopic.set(m_grpTopic);
          ::txvGrpKey.set(m_grpKey);
     }
}

VOID
synGroupURL::clrGroupTxv()         // clear group-related text vars
{
     ::txvGrpPathDisp.clr();
     ::txvGrpPathURL.clr();
     ::txvGrpName.clr();
     ::txvGrpTopic.clr();
     ::txvGrpKey.clr();
}

/* General request functions */

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

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

     if (startProc()) {
          do {
               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);
               }
               switch (m_dnf->process()) {
               case DNFEND:
                    rc=ACTHDONE;
                    break;
               }
          } while (contProc(rc));
     }
     return(rc);
}

synSendFile::~synSendFile()
{
     // have to delete DNF here because it MUST be deleted before map
     if (m_dnf != NULL) {
          delete m_dnf;
          m_dnf=NULL;
     }
     if (m_map != NULL) {
          delete m_map;
          m_map=NULL;
     }
     if (m_step != NULL) {
          delete m_step;
          m_step=NULL;
     }
}

/* Group list functions */

synGrpList::synGrpList(
acthSession *ses) :
     fmSynthesis(ses),
     m_psout(NULL),
     m_sp(0),
     m_state(START),
     m_firstGroup(true)
{
     ::memset(m_grpstk,0,sizeof(m_grpstk));
     *m_grpPath='\0';
}

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

     if (startProc()) {
          do {
               switch (m_state) {
               case START:
                    if (!userOK(rc)) {
                         return(rc);
                    }
                    ::stlcpy(m_grpstk[0].name,::rootGroupName,FORNSZ);
                    if (::gmeNextGrpFDefP(ROOTGRP,"") != NULL) {
                         m_grpstk[0].flags|=GL_HASFOR;
                    }
                    formTree();
                    m_dnf=new dnfHandler(grpListMap,bout);
                    m_state=OUTDNF;
                    break;
               case OUTDNF:
                    ::txvGrpName.set(m_grpstk[m_sp].name);
                    ::txvGrpPathDisp.set(m_grpPath);
                    ::txvGrpPathURL.set(m_grpPath);
                    ::txvGrpTopic.set(m_grpTopic);
                    ::txvGrpKey.set(m_grpKey);
                    switch (m_dnf->process()) {
                    case DNFROWBEGIN:
                         if (m_firstGroup) {
                              m_firstGroup=false;
                         }
                         else if (nextGroup()) {
                              groupPath(m_grpPath,MAXGRPSTR);
                              const struct forgrp *grpptr
                                   =::gmeGetGrpP(m_grpstk[m_sp].id);
                              ::stlcpy(m_grpTopic,grpptr->topic,TPCSIZ);
                              ::stlcpy(m_grpKey,grpptr->key,KEYSIZ);
                              formTree();
                         }
                         else {
                              m_dnf->tableDone();
                         }
                         break;
                    case GRPTREE:
                         m_treeDone=0;
                         m_state=OUTTREE;
                         break;
                    case DNFEND:
                         rc=ACTHDONE;
                         break;
                    }
                    ::txvGrpName.clr();
                    ::txvGrpPathDisp.clr();
                    ::txvGrpPathURL.clr();
                    ::txvGrpTopic.clr();
                    ::txvGrpKey.clr();
                    break;
               case OUTTREE:
                    bout.flush();
                    m_treeDone+=ses->sndrsp(m_psout->str()+m_treeDone,
                                            m_psout->pcount()-m_treeDone);
                    if (m_psout->pcount() <= m_treeDone) {
                         m_psout->rdbuf()->freeze(0);
                         delete m_psout;
                         m_psout=NULL;
                         m_state=OUTDNF;
                    }
                    break;
               }
          } while (contProc(rc));
     }
     return(rc);
}

bool
synGrpList::nextGroup()            // find the next group
{
     if (!pushGroup()) {
          return(false);
     }
     while (true) {
          ASSERT(m_sp > 0);
          USHORT parid=m_grpstk[m_sp-1].id;
          const struct forgrp *grpptr=::gmeNextGrpP(parid,m_grpstk[m_sp].name);
          if (grpptr == NULL) {
               if (parid == ROOTGRP) {
                    return(false);
               }
               popGroup();
          }
          else {
               m_grpstk[m_sp].id=grpptr->grpid;
               ::stlcpy(m_grpstk[m_sp].name,grpptr->name,FORNSZ);
               m_grpstk[m_sp].flags=0;
               if (::gmeNextGrpP(parid,m_grpstk[m_sp].name) != NULL) {
                    m_grpstk[m_sp].flags|=GL_HASSIB;
               }
               if (::gmeNextGrpFDefP(m_grpstk[m_sp].id,"") != NULL) {
                    m_grpstk[m_sp].flags|=GL_HASFOR;
               }
               return(true);
          }
     }
}

bool
synGrpList::pushGroup()            // push group context onto stack
{
     if (m_sp >= GRPSTKSZ) {
          CHAR *tmpBuf=new CHAR[FORNSZ*m_sp];
          groupPath(tmpBuf,FORNSZ*m_sp);
          // NOTE:  67 from shocst() in summary.c
          tmpBuf[min(AUDSIZ-67,(FORNSZ*m_sp)-1)]='\0';
          ::shocst("FORUM GROUPS NESTED TOO DEEPLY",tmpBuf);
          delete[] tmpBuf;
          return(false);
     }
     ++m_sp;
     return(true);
}

VOID
synGrpList::popGroup()             // pop group context from stack
{
     ASSERT(m_sp > 0);
     ::memset(&m_grpstk[m_sp],0,sizeof(m_grpstk[0]));
     --m_sp;
}

CHAR *                             //   returns pointer to destination
synGrpList::groupPath(             // generate current group path
CHAR *dst,                         //   buffer to form in
size_t dstSiz)                     //   size of destination buffer
{
     *dst='\0';
     for (INT i=1 ; i <= m_sp ; ++i) {
          ::catGroup(dst,m_grpstk[i].name,dstSiz);
     }
     return(dst);
}

VOID
synGrpList::formTree()             // form HTML code for tree
{
     ASSERT(m_psout == NULL);
     m_psout=new ostrstream;
     if (m_sp != 0) {
          for (INT i=1 ; i < m_sp ; ++i) {
               m_grpstk[i].flags&GL_HASSIB ? pTreePix->trunk(*m_psout)
                                           : pTreePix->blank(*m_psout);
          }
          m_grpstk[m_sp].flags&GL_HASSIB ? pTreePix->branch(*m_psout)
                                         : pTreePix->leaf(*m_psout);
     }
     m_grpstk[m_sp].flags&GL_HASFOR ? pTreePix->withfor(*m_psout)
                                    : pTreePix->empty(*m_psout);
}

synGrpList::~synGrpList()
{
     if (m_psout != NULL) {
          m_psout->rdbuf()->freeze(0);
          delete m_psout;
          m_psout=NULL;
     }
}

/* Forum list generation functions */

synForList::synForList(
acthSession *ses,
dnfMap &map) :
     fmSynthesis(ses),
     m_map(map)
{
     ::memset(&m_fdef,0,sizeof(struct fordef));
}

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

     if (firsttime()) {
          if (!userOK(rc)) {
               return(rc);
          }
          m_dnf=new dnfHandler(m_map,bout);
     }
     else if (startProc()) {
          do {
               if (m_fdef.forum == EMLID) {
                    ::clrForumTxv();
               }
               else {
                    ::setForDefTxv(&m_fdef);
               }
               switch (m_dnf->process()) {
               case DNFROWBEGIN:
                    {
                         if (nxtdefb(m_fdef.name,&m_fdef) == NULL) {
                              m_fdef.forum=EMLID;
                              m_dnf->tableDone();
                         }
                    }
                    break;
               case FORLINE:
                    break;
               case DNFEND:
                    rc=ACTHDONE;
                    break;
               }
               ::clrForumTxv();
          } while (contProc(rc));
     }
     return(rc);
}

/* Get group management form functions */

synGrpGetMgr::synGrpGetMgr(
acthSession *ses,
dnfMap &map) :
     synGroupURL(ses),
     m_map(map)
{
}

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

     if (startProc()) {
          do {
               if (firsttime()) {
                    if (userOK(rc)) {
                         if (initGrpInfo()) {
                              m_dnf=new dnfHandler(m_map,bout);
                         }
                         else {
                              rc=ACTHNOTFND;
                         }
                    }
               }
               else {
                    setGroupTxv();
                    do {
                         switch (m_dnf->process()) {
                         case DNFEND:
                              rc=ACTHDONE;
                              break;
                         }
                    } while (contProc(rc));
                    clrGroupTxv();
               }
          } while (contProc(rc));
     }
     return(rc);
}

/* Group management form response handler functions */

synGrpSetMgr::synGrpSetMgr(
acthSession *ses) :
     synGroupURL(ses),
     m_state(START)
{
     ::memset(m_work,0,GMEWRKSZ);
}

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

     if (startProc()) {
          do {
               switch (m_state) {
               case START:
                    if (!userOK(rc)) {
                         return(rc);
                    }
                    if (!initGrpInfo()) {
                         return(ACTHNOTFND);
                    }
                    ::memset(&m_grpBuf,0,sizeof(struct forgrp));
                    getGrpParms(m_grpBuf);
                    if (!::valfornm(m_grpBuf.name)) {
                         setError(ERR_NAME);
                    }
                    else if (*m_grpBuf.key != '\0'
                          && !::keynam(m_grpBuf.key)) {
                         setError(ERR_KEY);
                    }
                    else {
                         ::inigmerq(m_work);
                         m_state=PROCESS;
                         prep();
                    }
                    break;
               case PROCESS:
                    process();
                    break;
               case GENERR:
                    setGroupTxv();
                    do {
                         switch (m_dnf->process()) {
                         case ERRTEXT:
                              bout << errText();
                              break;
                         case DNFEND:
                              rc=ACTHDONE;
                              break;
                         }
                    } while (contProc(rc));
                    clrGroupTxv();
                    break;
               case GENRESP:
                    setGroupTxv();
                    do {
                         switch (m_dnf->process()) {
                         case DNFEND:
                              rc=ACTHDONE;
                              break;
                         }
                    } while (contProc(rc));
                    clrGroupTxv();
                    break;
               }
          } while (contProc(rc));
     }
     return(rc);
}

VOID
synGrpSetMgr::startResp(           // processing done, start response
dnfMap &map)                       //   DNF map for response
{
     ASSERT(m_state != GENERR && m_state != GENRESP);
     ASSERT(m_dnf == NULL);
     m_dnf=new dnfHandler(map,bout);
     m_state=GENRESP;
}

VOID
synGrpSetMgr::setError(            // report error
INT err)                           //   code describing error
{
     ASSERT(m_state != GENERR && m_state != GENRESP);
     m_err=err;
     m_dnf=new dnfHandler(getErrMap(),bout);
     m_state=GENERR;
}

const CHAR *                       //   returns pointer to temporary buffer
synGrpSetMgr::errText()            // generate text of error message
{
     ::setmbk(::fmmb);
     ::clrprf();
     switch (m_err) {
     case ERR_NAME:
          ::prfmsg(GENAME);
          break;
     case ERR_KEY:
          ::prfmsg(ERRKEY);
          break;
     case ERR_2DEEP:
          ::prfmsg(GE2DEEP);
          break;
     case GMEACC:
          ::prfmsg(ERRACC);
          break;
     case GMENFND:
          ::prfmsg(GENOPAR);
          break;
     case GMEDUP:
          ::prfmsg(GEDUP);
          break;
     case GMENFID:
          ::prfmsg(GE2MANY);
          break;
     default:
          ::prfmsg(ERRUNK,m_err);
          break;
     }
     ::rstmbk();
     ::stpans(::prfbuf);
     return(::prfbuf);
}

synGrpSetMgr::~synGrpSetMgr()
{
     if (::gmerqopn(m_work)) {
          ::clsgmerq(m_work);
     }
}

/* Create group form response handler functions */

dnfMap &                           //   returns reference to error map
synGrpSetCrt::getErrMap()          // do request-specific processing
{
     return(grpSetCrtErrMap);
}

VOID
synGrpSetCrt::prep()
{
     if (ses->urlargc()-2 >= GRPSTKSZ-1) {
          setError(ERR_2DEEP);
     }
     else {
          m_grpBuf.parid=m_grpID;
     }
}

VOID
synGrpSetCrt::process()
{
     INT rc=::gmeGCreateGrp(m_work,&m_grpBuf);
     if (rc > GMEAGAIN) {
          m_grpID=m_grpBuf.grpid;
          ::catGroup(m_grpPath,m_grpBuf.name,MAXGRPSTR);
          ::stlcpy(m_grpName,m_grpBuf.name,FORNSZ);
          ::stlcpy(m_grpTopic,m_grpBuf.topic,TPCSIZ);
          ::stlcpy(m_grpKey,m_grpBuf.key,KEYSIZ);
          startResp(grpSetCrtMap);
     }
     else if (rc < GMEAGAIN) {
          setError(rc);
     }
}

/* Modify group header form response handler functions */

dnfMap &                           //   returns reference to error map
synGrpSetMod::getErrMap()          // do request-specific processing
{
     return(grpSetModErrMap);
}

VOID
synGrpSetMod::prep()
{
     const struct forgrp *grpptr=gmeGetGrpP(m_grpID);
     m_grpBuf.parid=grpptr->parid;
     m_grpBuf.grpid=grpptr->grpid;
     m_grpBuf.flags=grpptr->flags;
}

VOID
synGrpSetMod::process()
{
     INT rc=::gmeGModGrpHdr(m_work,&m_grpBuf);
     if (rc > GMEAGAIN) {
          CHAR *cp=::strrchr(m_grpPath,'/');
          if (cp == NULL) {
               cp=m_grpPath;
          }
          *cp='\0';
          ::catGroup(m_grpPath,m_grpBuf.name,MAXGRPSTR);
          ::stlcpy(m_grpName,m_grpBuf.name,FORNSZ);
          ::stlcpy(m_grpTopic,m_grpBuf.topic,TPCSIZ);
          ::stlcpy(m_grpKey,m_grpBuf.key,KEYSIZ);
          startResp(grpSetModMap);
     }
     else if (rc < GMEAGAIN) {
          setError(rc);
     }
}

/* Delete group handler functions */

synGrpSetDel::synGrpSetDel(
acthSession *ses) :
     synGroupURL(ses),
     m_state(START)
{
     ::memset(m_work,0,GMEWRKSZ);
}

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

     if (startProc()) {
          do {
               switch (m_state) {
               case START:
                    if (!userOK(rc)) {
                         return(rc);
                    }
                    if (!initGrpInfo() || m_grpID == ROOTGRP) {
                         return(ACTHNOTFND);
                    }
                    ::inigmerq(m_work);
                    m_state=PROCESS;
                    break;
               case PROCESS:
                    m_err=gmeGDeleteGrp(m_work,m_grpID);
                    if (m_err > GMEAGAIN || m_err == GMENFND) {
                         m_dnf=new dnfHandler(grpSetDelMap,bout);
                         m_state=GENRESP;
                    }
                    else if (m_err < GMEAGAIN) {
                         m_dnf=new dnfHandler(grpSetDelErrMap,bout);
                         m_state=GENERR;
                    }
                    break;
               case GENERR:
                    setGroupTxv();
                    do {
                         switch (m_dnf->process()) {
                         case ERRTEXT:
                              bout << errText();
                              break;
                         case DNFEND:
                              rc=ACTHDONE;
                              break;
                         }
                    } while (contProc(rc));
                    clrGroupTxv();
                    break;
               case GENRESP:
                    setGroupTxv();
                    do {
                         switch (m_dnf->process()) {
                         case DNFEND:
                              rc=ACTHDONE;
                              break;
                         }
                    } while (contProc(rc));
                    clrGroupTxv();
                    break;
               }
          } while (contProc(rc));
     }
     return(rc);
}

const CHAR *                       //   returns pointer to temporary buffer
synGrpSetDel::errText()            // generate text of error message
{
     ::setmbk(::fmmb);
     ::clrprf();
     switch (m_err) {
     case GMENDEL:
          ::prfmsg(GENOTMT);
          break;
     default:
          ::prfmsg(ERRUNK,m_err);
          break;
     }
     ::rstmbk();
     ::stpans(::prfbuf);
     return(::prfbuf);
}

synGrpSetDel::~synGrpSetDel()
{
     if (::gmerqopn(m_work)) {
          ::clsgmerq(m_work);
     }
}

/* Generic group-related forum list generator functions */

synGrpForList::synGrpForList(
acthSession *ses,
dnfMap &map) :
     synGroupURL(ses),
     m_map(map)
{
     m_fdef.forum=EMLID;
}

bool                               //   returns false if error
synGrpForList::prep(               // prepare to process request
ACTHCODE &rc)                      //   updated return code if error
{
     if (userOK(rc)) {
          if (initGrpInfo()) {
               m_dnf=new dnfHandler(m_map,bout);
               return(true);
          }
          rc=ACTHNOTFND;
     }
     return(false);
}

bool                               //   returns false when done
synGrpForList::outputList()        // output forum list
{
     bool rc=true;

     setGroupTxv();
     if (m_fdef.forum == EMLID) {
          ::clrForumTxv();
     }
     else {
          ::setForDefTxv(&m_fdef);
     }
     switch (m_dnf->process()) {
     case DNFROWBEGIN:
          if (!prepNextForum()) {
               m_fdef.forum=EMLID;
               m_dnf->tableDone();
          }
          break;
     case FORLINE:
          break;
     case DNFEND:
          rc=false;
          break;
     }
     ::clrForumTxv();
     clrGroupTxv();
     return(rc);
}

bool
synGrpForList::prepNextForum()
{
     const struct fordef *fdef=nextForum();
     if (fdef != NULL) {
          ::memcpy(&m_fdef,fdef,sizeof(struct fordef));
          return(true);
     }
     return(false);
}

/* List of all forums to select in group functions */

synGrpForAll::synGrpForAll(
acthSession *ses) :
     synGrpForList(ses,grpForAllMap)
{
     *m_curForName='\0';
}

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

     if (startProc()) {
          do {
               if (firsttime()) {
                    prep(rc);
               }
               else {
                    if (!outputList()) {
                         rc=ACTHDONE;
                    }
               }
          } while (contProc(rc));
     }
     return(rc);
}

const struct fordef *              //   returns ptr to forum (NULL when done)
synGrpForAll::nextForum()          // get next forum to output
{
     const struct fordef *fdef=::nxtdefp(m_curForName);
     if (fdef != NULL) {
          ::stlcpy(m_curForName,fdef->name,FORNSZ);
     }
     return(fdef);
}

/* List of forums in a group functions */

synGrpForSel::synGrpForSel(
acthSession *ses) :
     synGrpForList(ses,grpForSelMap),
     m_state(START)
{
     *m_curForName='\0';
     ::memset(m_work,0,GMEWRKSZ);
}

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

     if (startProc()) {
          do {
               switch (m_state) {
               case START:
                    if (prep(rc)) {
                         if (ses->param(FP_ADDFOR)) {
                              getForInfo(FP_ADDFOR);
                              ::inigmerq(m_work);
                              m_state=ADDFOR;
                         }
                         else if (ses->param(FP_REMFOR)) {
                              getForInfo(FP_REMFOR);
                              ::inigmerq(m_work);
                              m_state=REMFOR;
                         }
                         else {
                              m_state=GENRESP;
                         }
                    }
                    break;
               case ADDFOR:
                    m_err=::gmeGAddGrpFor(m_work,m_grpID,m_forum);
                    if (m_err > GMEAGAIN) {
                         *m_curForName='\0';
                         m_state=GENRESP;
                    }
                    else if (m_err < GMEAGAIN) {
                         if (m_dnf != NULL) {
                              delete m_dnf;
                         }
                         m_dnf=new dnfHandler(grpForSelErrMap,bout);
                         m_state=GENERR;
                    }
                    break;
               case REMFOR:
                    if (::gmeGDelGrpFor(m_work,m_grpID,m_forum) != GMEAGAIN) {
                         *m_curForName='\0';
                         m_state=GENRESP;
                    }
                    break;
               case GENERR:
                    setGroupTxv();
                    ::txvForNameDisp.set(m_curForName);
                    do {
                         switch (m_dnf->process()) {
                         case ERRTEXT:
                              bout << errText();
                              break;
                         case DNFEND:
                              rc=ACTHDONE;
                              break;
                         }
                    } while (contProc(rc));
                    ::txvForNameDisp.clr();
                    clrGroupTxv();
                    break;
               case GENRESP:
                    if (!outputList()) {
                         rc=ACTHDONE;
                    }
                    break;
               }
          } while (contProc(rc));
     }
     return(rc);
}

VOID
synGrpForSel::getForInfo(          // get forum info from parameter
const CHAR *paramName)             //   name of parameter to use
{
     size_t len=ses->paramRoom(paramName);
     CHAR *buf=new CHAR[len];
     ses->param(paramName,buf,len);
     ::urlDecodeBuf(m_curForName,buf,FORNSZ);
     m_forum=::getfid(m_curForName);
     delete buf;
}

const CHAR *                       //   returns pointer to temporary buffer
synGrpForSel::errText()            // generate text of error message
{
     ::setmbk(::fmmb);
     ::clrprf();
     switch (m_err) {
     case GMENFND:
          ::prfmsg(GEFGONE);
          break;
     case GME2MFR:
          ::prfmsg(GE2MNYF);
          break;
     default:
          ::prfmsg(ERRUNK,m_err);
          break;
     }
     ::rstmbk();
     ::stpans(::prfbuf);
     return(::prfbuf);
}

const struct fordef *              //   returns ptr to forum (NULL when done)
synGrpForSel::nextForum()          // get next forum to output
{
     const struct fordef *fdef=::gmeNextGrpFDefP(m_grpID,m_curForName);
     if (fdef != NULL) {
          ::stlcpy(m_curForName,fdef->name,FORNSZ);
     }
     return(fdef);
}

synGrpForSel::~synGrpForSel()
{
     if (::gmerqopn(m_work)) {
          ::clsgmerq(m_work);
     }
}

/* Base create/modify forum handler functions */

synForSetMgr::synForSetMgr(
acthSession *ses) :
     fmSynthesis(ses),
     m_echoes(NULL),
     m_state(START)
{
     ::memset(m_work,0,GMEWRKSZ);
}

bool                               //   returns false if error
synForSetMgr::getShort(            // get short integer from form response
const CHAR *paramName,             //   form parameter name
SHORT &buf)                        //   buffer to receive value
{
     LONG val;
     CHAR numBuf[sizeof("-1234567890")];

     ses->param(paramName,numBuf,sizeof(numBuf));
     if (!(numBuf[0] == '-' ? ::alldgs(numBuf+1) : ::alldgs(numBuf))) {
          return(false);
     }
     val=::atol(numBuf);
     if (val < (-GCMAXSHORT) || val > GCMAXSHORT) {
          return(false);
     }
     buf=(SHORT)val;
     return(true);
}

bool                               //   returns false if error in data
synForSetMgr::getName(             // get forum name from form response
CHAR *name)                        //   buffer to receive name
{
     ses->param(FP_NAME,name,FORNSZ);
     if (!::valfornm(name)) {
          setError(ERR_NAME);
          return(false);
     }
     return(true);
}

bool                               //   returns false if error in data
synForSetMgr::getForumOp(          // get Forum-Op from form response
CHAR *forop)                       //   buffer to receive name
{
     ses->param(FP_FORUMOP,forop,UIDSIZ);
     if (!::uidxst(forop)) {
          setError(GMENFND);
          return(false);
     }
     return(true);
}

bool                               //   returns false if error in data
synForSetMgr::getMsgLife(          // get message lifetime from form response
SHORT &msglif)                     //   buffer to receive lifetime
{
     if (!getShort(FP_MSGLIFE,msglif) || msglif < -1) {
          setError(ERR_MSGLIF);
          return(false);
     }
     return(true);
}

bool                               //   returns false if error in data
synForSetMgr::getCharges(          // get forum charges from form response
SHORT &ccr,                        //   buffers to receive credit charges
SHORT &chgmsg,                     //   ...
SHORT &chgrdm,
SHORT &chgatt,
SHORT &chgadl,
SHORT &chgupk,
SHORT &chgdpk)
{
     if (!getShort(FP_CCR,ccr)) {
          setError(ERR_CRDRATE);
          return(false);
     }
     if (!getShort(FP_CHGWRMSG,chgmsg)) {
          setError(ERR_CHGWRTMSG);
          return(false);
     }
     if (!getShort(FP_CHGRDMSG,chgrdm)) {
          setError(ERR_CHGRDMSG);
          return(false);
     }
     if (!getShort(FP_CHGWRATT,chgatt)) {
          setError(ERR_CHGWRTATT);
          return(false);
     }
     if (!getShort(FP_CHGRDATT,chgadl)) {
          setError(ERR_CHGRDATT);
          return(false);
     }
     if (!getShort(FP_CHGWRATK,chgupk)) {
          setError(ERR_CHGWRTATTK);
          return(false);
     }
     if (!getShort(FP_CHGRDATK,chgdpk)) {
          setError(ERR_CHGRDATTK);
          return(false);
     }
     return(true);
}

bool                               //   returns false if error in data
synForSetMgr::getPfnLvl(           // get profanity level from form response
SHORT &pfnlvl)                     //   buffer to receive profanity level
{
     if (!getShort(FP_PFNLVL,pfnlvl)
      || pfnlvl < 0 || pfnlvl > DFTPFN) {
          setError(ERR_PFNLVL);
          return(false);
     }
     return(true);
}

bool                               //   returns false if error in data
synForSetMgr::getAccess(           // get access settings from form response
SHORT &dfnpv,                      //   buffers to receive access settings
SHORT &dfprv,                      //   ...
SHORT &mxnpv)
{
     if (!getShort(FP_DFTNON,dfnpv)
      || dfnpv&1 || dfnpv < 0 || dfnpv > OPAXES
      || !getShort(FP_DFTPRV,dfprv)
      || dfprv&1 || dfprv < 0 || dfprv > OPAXES
      || !getShort(FP_MAXNON,mxnpv)
      || mxnpv&1 || mxnpv < 0 || mxnpv > OPAXES) {
          setError(ERR_ACCLVL);
          return(false);
     }
     return(true);
}

bool                               //   returns false if error in data
synForSetMgr::getKey(              // get key from form response
CHAR *forlok)                      //   buffer to receive key
{
     ses->param(FP_KEY,forlok,KEYSIZ);
     if (*forlok != '\0' && !::keynam(forlok)) {
          setError(ERR_KEY);
          return(false);
     }
     return(true);
}

VOID
synForSetMgr::getDesc()            // get description from form response
{
     ses->param(FP_DESC,m_desc,MAXFDESC);
     ::unpad(::strmove(m_desc,::skptwht(m_desc)));
     CHAR *cp=m_desc;
     while ((cp=::strchr(cp,'\n')) != NULL) {
          ::strmove(cp,cp+1);
     }
}

bool                               //   returns false if error in data
synForSetMgr::getEchoes(           // get echoes from form response
SHORT &necho)                      //   buffer to receive number of echoes
{
     INT len=ses->paramRoom(FP_ECHOES);
     if (len != 0) {
          CHAR *echoStr=new CHAR[len];
          ses->param(FP_ECHOES,echoStr,len);
          ::unpad(::strmove(echoStr,::skptwht(echoStr)));
          if (*echoStr != '\0') {
               necho=::itemcntd(echoStr,";");
               ASSERT(necho != 0);
               m_echoes=new CHAR[necho*MAXADR];
               adr_t *echo=(adr_t *)m_echoes;
               CHAR *cp=echoStr;
               for (INT i=0 ; i < necho ; ++i) {
                    ASSERT(cp != NULL);
                    cp=::parscc(echo[i],cp);
                    if (!isexpa(echo[i]) || !fixadr(NULL,echo[i])) {
                         setError(ERR_ECHO);
                         return(false);
                    }
               }
          }
          delete[] echoStr;
     }
     return(true);
}

VOID
synForSetMgr::hdlProcess(          // handle return code from GME processing
INT gmeRC)                         //   GME status code
{
     if (gmeRC > GMEAGAIN) {
          m_dnf=new dnfHandler(getOKMap(),bout);
          m_state=GENRESP;
     }
     else if (gmeRC < GMEAGAIN) {
          setError(gmeRC);
     }
}

ACTHCODE
synForSetMgr::prcError()           // process error file output
{
     ACTHCODE rc=ACTHMORE;

     do {
          switch (m_dnf->process()) {
          case ERRTEXT:
               bout << errText();
               break;
          case DNFEND:
               rc=ACTHDONE;
               break;
          }
     } while (contProc(rc));
     return(rc);
}

ACTHCODE
synForSetMgr::prcResp()            // process response file output
{
     ACTHCODE rc=ACTHMORE;

     do {
          switch (m_dnf->process()) {
          case DNFEND:
               rc=ACTHDONE;
               break;
          }
     } while (contProc(rc));
     return(rc);
}

VOID
synForSetMgr::setError(            // report error
INT err)                           //   code describing error
{
     ASSERT(m_state != GENERR && m_state != GENRESP);
     m_err=err;
     m_dnf=new dnfHandler(getErrMap(),bout);
     m_state=GENERR;
}

const CHAR *                       //   returns pointer to temporary buffer
synForSetMgr::errText()            // generate text of error message
{
     ::setmbk(::fmmb);
     ::clrprf();
     switch (m_err) {
     case ERR_NAME:
          ::prfmsg(FENAME);
          break;
     case ERR_KEY:
          ::prfmsg(ERRKEY);
          break;
     case ERR_MSGLIF:
          ::prfmsg(FEMSGLIF);
          break;
     case ERR_CRDRATE:
          ::prfmsg(FECCR);
          break;
     case ERR_CHGWRTMSG:
          ::prfmsg(FECHGWM);
          break;
     case ERR_CHGRDMSG:
          ::prfmsg(FECHGRM);
          break;
     case ERR_CHGWRTATT:
          ::prfmsg(FECHGWA);
          break;
     case ERR_CHGRDATT:
          ::prfmsg(FECHGRA);
          break;
     case ERR_CHGWRTATTK:
          ::prfmsg(FECHGWAK);
          break;
     case ERR_CHGRDATTK:
          ::prfmsg(FECHGRAK);
          break;
     case ERR_PFNLVL:
          ::prfmsg(FEPFNLVL);
          break;
     case ERR_ACCLVL:
          ::prfmsg(FEACCLVL);
          break;
     case ERR_ECHO:
          ::prfmsg(FEECHO);
          break;
     case GMENFND:
          ::prfmsg(FEFOROP);
          break;
     case GMEUSE:
          ::prfmsg(FEUSE);
          break;
     case GMEDUP:
          ::prfmsg(FEDUP);
          break;
     case GME2MFL:
          ::prfmsg(FENFIL);
          break;
     case GME2MFR:
          ::prfmsg(FE2MANY);
          break;
     case GMENFID:
          ::prfmsg(FENOFID);
          break;
     case GMEMEM:
          ::prfmsg(FENOMEM);
          break;
     case GMEERR:
          ::prfmsg(FEFILPTH);
          break;
     default:
          ::prfmsg(ERRUNK,m_err);
          break;
     }
     ::rstmbk();
     ::stpans(::prfbuf);
     return(::prfbuf);
}

synForSetMgr::~synForSetMgr()
{
     if (::gmerqopn(m_work)) {
          ::clsgmerq(m_work);
     }
     if (m_echoes != NULL) {
          delete[] m_echoes;
          m_echoes=NULL;
     }
}

/* Get create-forum form handler functions */

synForGetCrt::synForGetCrt(
acthSession *ses) :
     fmSynthesis(ses),
     m_state(START)
{
     ::memset(m_work,0,GMEWRKSZ);
}

ACTHCODE                           //   returns enumerated status value
synForGetCrt::proceed()            // agent's request handler
{
     ACTHCODE rc=ACTHMORE;

     if (startProc()) {
          do {
               switch (m_state) {
               case START:
                    if (!userOK(rc)) {
                         return(rc);
                    }
                    ASSERT(ses->getUser() != NULL);
                    ::gmeGetForDft(&m_fdsk,ses->getUser()->userid());
                    ::inigmerq(m_work);
                    m_state=RECFIL;
                    break;
               case RECFIL:
                    {
                         INT grc=gmeRecommend(m_work,m_fdsk.datfil);
                         if (grc > 0) {
                              m_dnf=new dnfHandler(forGetCrtMap,bout);
                              m_state=OUTPUT;
                         }
                         else if (grc < 0) {
                              // this should never happen
                              rc=ACTHNOTFND;
                         }
                    }
                    break;
               case OUTPUT:
                    ::setForDskTxv(&m_fdsk);
                    do {
                         switch (m_dnf->process()) {
                         case DNFEND:
                              rc=ACTHDONE;
                              break;
                         }
                    } while (contProc(rc));
                    ::clrForumTxv();
                    break;
               }
          } while (contProc(rc));
     }
     return(rc);
}

synForGetCrt::~synForGetCrt()
{
     if (::gmerqopn(m_work)) {
          ::clsgmerq(m_work);
     }
}

/* Create forum handler functions */

synForSetCrt::synForSetCrt(
acthSession *ses) :
     synForSetMgr(ses)
{
     ::memset(&m_fdsk,0,sizeof(struct fordsk));
}

ACTHCODE                           //   returns enumerated status value
synForSetCrt::proceed()            // agent's request handler
{
     ACTHCODE rc=ACTHMORE;

     if (startProc()) {
          do {
               switch (m_state) {
               case START:
                    if (!userOK(rc)) {
                         return(rc);
                    }
                    ::gmeGetForDft(&m_fdsk,"");
                    if (!getName(m_fdsk.name)
                     || !getForumOp(m_fdsk.forop)
                     || !getMsgLife(m_fdsk.msglif)
                     || !getCharges(m_fdsk.ccr,m_fdsk.chgmsg,
                                    m_fdsk.chgrdm,m_fdsk.chgatt,
                                    m_fdsk.chgadl,m_fdsk.chgupk,
                                    m_fdsk.chgdpk)
                     || !getPfnLvl(m_fdsk.pfnlvl)
                     || !getAccess(m_fdsk.dfnpv,m_fdsk.dfprv,m_fdsk.mxnpv)
                     || !getKey(m_fdsk.forlok)
                     || !getEchoes(m_fdsk.necho)) {
                         ASSERT(m_state == GENERR);
                         break;
                    }
                    ses->param(FP_DATFIL,m_fdsk.datfil,GCSTRPTH);
                    if (!::valfdfnam(m_fdsk.datfil)) {
                         setError(GMEERR);
                         break;
                    }
                    ::fixfdfnam(m_fdsk.datfil);
                    ses->param(FP_ATTPATH,m_fdsk.attpath,GCSTRPTH);
                    ses->param(FP_TOPIC,m_fdsk.topic,TPCSIZ);
                    getDesc();
                    ::inigmerq(m_work);
                    m_state=PROCESS;
                    break;
               case PROCESS:
                    hdlProcess(gmeGCreateFor(m_work,&m_fdsk,m_desc,m_echoes));
                    break;
               case GENERR:
                    ::setForDskTxv(&m_fdsk);
                    rc=prcError();
                    ::clrForumTxv();
                    break;
               case GENRESP:
                    ::setForDskTxv(&m_fdsk);
                    rc=prcResp();
                    ::clrForumTxv();
                    break;
               }
          } while (contProc(rc));
     }
     return(rc);
}

dnfMap &                           //   returns reference to error map
synForSetCrt::getOKMap()           // get DNF map for success response
{
     return(forSetCrtMap);
}

dnfMap &                           //   returns reference to error map
synForSetCrt::getErrMap()          // get DNF map for error response
{
     return(forSetCrtErrMap);
}

/* Get modify-forum form handler functions */

synForGetMod::synForGetMod(
acthSession *ses) :
     fmSynthesis(ses),
     m_echoList(NULL),
     m_desc(NULL),
     m_dptr(NULL),
     m_state(START)
{
     ::memset(&m_fdsk,0,sizeof(struct fordsk));
}

ACTHCODE                           //   returns enumerated status value
synForGetMod::proceed()            // agent's request handler
{
     ACTHCODE rc=ACTHMORE;

     if (startProc()) {
          do {
               switch (m_state) {
               case START:
                    if (userOK(rc)) {
                         CHAR forName[FORNSZ];
                         ::urlDecodeBuf(forName,ses->urlargv(2),FORNSZ);
                         USHORT fid=::getfid(forName);
                         if (fid == EMLID) {
                              rc=ACTHNOTFND;
                         }
                         else {
                              CHAR *echoes;
                              const struct fordef *fdef=::getdefp(fid);
                              if (fdef->necho != 0) {
                                   echoes=new CHAR[fdef->necho*MAXADR];
                              }
                              CHAR *tmpbuf=new CHAR[MAXFDESC];
                              ::getallf(fid,&m_fdsk,tmpbuf,echoes);
                              ::unpad(::strmove(tmpbuf,::skptwht(tmpbuf)));
                              ::strrpl(tmpbuf,'\n','\r');
                              m_desc=entEncodeStr(tmpbuf);
                              delete[] tmpbuf;
                              if (m_fdsk.necho != 0) {
                                   size_t echoLen=0;
                                   adr_t *echo=(adr_t *)echoes;
                                   for (INT i=0 ; i < m_fdsk.necho ; ++i) {
                                        if (i > 0) {
                                             echoLen+=CSTRLEN("; ");
                                        }
                                        echoLen+=::strlen(echo[i]);
                                   }
                                   m_echoList=new CHAR[echoLen+1];
                                   *m_echoList='\0';
                                   for (INT i=0 ; i < m_fdsk.necho ; ++i) {
                                        if (i > 0) {
                                             ::strcat(m_echoList,"; ");
                                        }
                                        ::strcat(m_echoList,echo[i]);
                                   }
                                   delete[] echoes;
                              }
                              m_dnf=new dnfHandler(forGetModMap,bout);
                              m_state=OUTHEAD;
                         }
                    }
                    break;
               case OUTHEAD:
                    ::setForDskTxv(&m_fdsk);
                    if (m_echoList == NULL) {
                         ::txvEchoList.clr();
                    }
                    else {
                         ::txvEchoList.set(m_echoList);
                    }
                    do {
                         switch (m_dnf->process()) {
                         case FORDESC:
                              m_dptr=m_desc;
                              m_state=OUTDESC;
                              break;
                         case DNFEND:
                              rc=ACTHDONE;
                              break;
                         }
                    } while (m_state == OUTHEAD && contProc(rc));
                    ::clrForumTxv();
                    ::txvEchoList.clr();
                    break;
               case OUTDESC:
                    if (NULSTR(m_dptr)) {
                         m_state=OUTHEAD;
                    }
                    else {
                         if (*m_dptr == '\r') {
                              bout << crlf;
                              ++m_dptr;
                         }
                         CHAR *cp=::strchr(m_dptr,'\r');
                         size_t len=cp == NULL ? ::strlen(m_dptr) : (size_t)(cp-m_dptr);
                         bout.flush();
                         m_dptr+=ses->sndrsp(m_dptr,len);
                    }
                    break;
               }
          } while (contProc(rc));
     }
     return(rc);
}

synForGetMod::~synForGetMod()
{
     if (m_echoList != NULL) {
          delete[] m_echoList;
          m_echoList=NULL;
     }
     if (m_desc != NULL) {
          delete[] m_desc;
          m_desc=NULL;
     }
}

/* Modify forum handler functions */

synForSetMod::synForSetMod(
acthSession *ses) :
     synForSetMgr(ses),
     m_lock(NULL)
{
     ::memset(&m_fdef,0,sizeof(struct fordef));
}

ACTHCODE                           //   returns enumerated status value
synForSetMod::proceed()            // agent's request handler
{
     ACTHCODE rc=ACTHMORE;

     if (startProc()) {
          do {
               switch (m_state) {
               case START:
                    if (userOK(rc)) {
                         CHAR forName[FORNSZ];
                         ::urlDecodeBuf(forName,ses->urlargv(2),FORNSZ);
                         USHORT fid=::getfid(forName);
                         if (fid == EMLID) {
                              return(ACTHNOTFND);
                         }
                         ::getdefb(fid,&m_fdef);
                         SHORT pfnlvl,dfnpv,dfprv,mxnpv;
                         if (!getName(m_fdef.name)
                          || !getForumOp(m_fdef.forop)
                          || !getMsgLife(m_fdef.msglif)
                          || !getCharges(m_fdef.ccr,m_fdef.chgmsg,
                                         m_fdef.chgrdm,m_fdef.chgatt,
                                         m_fdef.chgadl,m_fdef.chgupk,
                                         m_fdef.chgdpk)
                          || !getPfnLvl(pfnlvl)
                          || !getAccess(dfnpv,dfprv,mxnpv)
                          || !getKey(m_fdef.forlok)
                          || !getEchoes(m_fdef.necho)) {
                              ASSERT(m_state == GENERR);
                              break;
                         }
                         m_fdef.pfnlvl=pfnlvl;
                         m_fdef.dfnpv=dfnpv;
                         m_fdef.dfprv=dfprv;
                         m_fdef.mxnpv=mxnpv;
                         ses->param(FP_TOPIC,m_fdef.topic,TPCSIZ);
                         getDesc();
                         ::inigmerq(m_work);
                         m_lock=new forumLock(m_work,m_fdef.forum);
                         m_state=PROCESS;
                    }
                    break;
               case PROCESS:
                    {
                         INT grc=gmeGModifyFor(m_work,&m_fdef,m_desc,m_echoes);
                         if (grc != GMEAGAIN) {
                              delete m_lock;
                              m_lock=NULL;
                         }
                         hdlProcess(grc);
                    }
                    break;
               case GENERR:
                    ::setForDefTxv(&m_fdef);
                    rc=prcError();
                    ::clrForumTxv();
                    break;
               case GENRESP:
                    ::setForDefTxv(&m_fdef);
                    rc=prcResp();
                    ::clrForumTxv();
                    break;
               }
          } while (contProc(rc));
     }
     return(rc);
}

dnfMap &                           //   returns reference to error map
synForSetMod::getOKMap()           // get DNF map for success response
{
     return(forSetModMap);
}

dnfMap &                           //   returns reference to error map
synForSetMod::getErrMap()          // get DNF map for error response
{
     return(forSetModErrMap);
}

synForSetMod::~synForSetMod()
{
     if (m_lock != NULL) {
          delete m_lock;
          m_lock=NULL;
     }
}

/* Get delete-forum confirmation handler functions */

synForGetDel::synForGetDel(
acthSession *ses) :
     fmSynthesis(ses)
{
}

ACTHCODE                           //   returns enumerated status value
synForGetDel::proceed()            // agent's request handler
{
     ACTHCODE rc=ACTHMORE;

     if (startProc()) {
          do {
               if (firsttime()) {
                    if (userOK(rc)) {
                         CHAR forName[FORNSZ];
                         ::urlDecodeBuf(forName,ses->urlargv(2),FORNSZ);
                         USHORT fid=::getfid(forName);
                         if (fid == EMLID) {
                              rc=ACTHNOTFND;
                         }
                         else {
                              ::getdefb(fid,&m_fdef);
                              m_dnf=new dnfHandler(forGetDelMap,bout);
                         }
                    }
               }
               else {
                    ::setForDefTxv(&m_fdef);
                    do {
                         switch (m_dnf->process()) {
                         case DNFEND:
                              rc=ACTHDONE;
                              break;
                         }
                    } while (contProc(rc));
                    ::clrForumTxv();
               }
          } while (contProc(rc));
     }
     return(rc);
}

/* Delete forum handler functions */

synForSetDel::synForSetDel(
acthSession *ses) :
     fmSynthesis(ses),
     m_lock(NULL),
     m_state(START)
{
     ::memset(m_work,0,GMEWRKSZ);
}

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

     if (startProc()) {
          do {
               switch (m_state) {
               case START:
                    if (userOK(rc)) {
                         CHAR forName[FORNSZ];
                         ::urlDecodeBuf(forName,ses->urlargv(2),FORNSZ);
                         USHORT fid=::getfid(forName);
                         if (fid == EMLID) {
                              rc=ACTHNOTFND;
                         }
                         else {
                              ::getdefb(fid,&m_fdef);
                              ::inigmerq(m_work);
                              m_lock=new forumLock(m_work,m_fdef.forum);
                              m_state=PROCESS;
                         }
                    }
                    break;
               case PROCESS:
                    m_err=gmeGDeleteFor(m_work,m_fdef.forum);
                    if (m_err > GMEAGAIN || m_err == GMENFND) {
                         delete m_lock;
                         m_lock=NULL;
                         m_dnf=new dnfHandler(forSetDelMap,bout);
                         m_state=GENRESP;
                    }
                    else if (m_err < GMEAGAIN) {
                         delete m_lock;
                         m_lock=NULL;
                         m_dnf=new dnfHandler(forSetDelErrMap,bout);
                         m_state=GENERR;
                    }
                    break;
               case GENERR:
                    ::setForDefTxv(&m_fdef);
                    do {
                         switch (m_dnf->process()) {
                         case ERRTEXT:
                              bout << errText();
                              break;
                         case DNFEND:
                              rc=ACTHDONE;
                              break;
                         }
                    } while (contProc(rc));
                    ::clrForumTxv();
                    break;
               case GENRESP:
                    ::setForDefTxv(&m_fdef);
                    do {
                         switch (m_dnf->process()) {
                         case DNFEND:
                              rc=ACTHDONE;
                              break;
                         }
                    } while (contProc(rc));
                    ::clrForumTxv();
                    break;
               }
          } while (contProc(rc));
     }
     return(rc);
}

const CHAR *                       //   returns pointer to temporary buffer
synForSetDel::errText()            // generate text of error message
{
     ::setmbk(::fmmb);
     ::clrprf();
     switch (m_err) {
     case GMENDEL:
          ::prfmsg(FEDFT);
          break;
     case GMEUSE:
          ::prfmsg(FEUSE);
          break;
     default:
          ::prfmsg(ERRUNK,m_err);
          break;
     }
     ::rstmbk();
     ::stpans(::prfbuf);
     return(::prfbuf);
}

synForSetDel::~synForSetDel()
{
     if (::gmerqopn(m_work)) {
          ::clsgmerq(m_work);
     }
     if (m_lock != NULL) {
          delete m_lock;
          m_lock=NULL;
     }
}

/* Text variable functions */

const CHAR *                       //   returns text var value
fmTimeStampTxv::resolve(           // resolve a text var name into its value
const CHAR *nam)                   //   name of text var being resolved
{
     static CHAR buf[sizeof("19800101120102")];

     (VOID)nam;
     USHORT d=today();
     USHORT t=now();
     sprintf(buf,"%04.4d%02.2d%02.2d%02.2d%02.2d%02.2d",
             ddyear(d),ddmon(d),ddday(d),dthour(t),dtmin(t),dtsec(t));
     return(buf);
}

const CHAR *                       //   returns text var value
fmTextVar::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
fmIntTxv::set(                     // set value to display
LONG newValue)                     //   to this
{
     sprintf(m_numBuf,"%ld",newValue);
     m_pVal=m_numBuf;
}

VOID
fmIntTxv::set(                     // set value to display
ULONG newValue)                    //   to this
{
     sprintf(m_numBuf,"%lu",newValue);
     m_pVal=m_numBuf;
}

VOID
fmIntTxv::set(                     // set value to display
INT newValue)                      //   to this
{
     sprintf(m_numBuf,"%d",newValue);
     m_pVal=m_numBuf;
}

VOID
fmIntTxv::set(                     // set value to display
UINT newValue)                     //   to this
{
     sprintf(m_numBuf,"%u",newValue);
     m_pVal=m_numBuf;
}

VOID
fmIntTxv::set(                     // set value to display
SHORT newValue)                    //   to this
{
     sprintf(m_numBuf,"%hd",newValue);
     m_pVal=m_numBuf;
}

VOID
fmIntTxv::set(                     // set value to display
USHORT newValue)                   //   to this
{
     sprintf(m_numBuf,"%hu",newValue);
     m_pVal=m_numBuf;
}

VOID
fmDateTxv::set(                    // set value to display
USHORT dosDate)                    //   DOS date format
{
     sprintf(m_dateBuf,"%02.2d/%02.2d/%04.4d",
             ddmon(dosDate),ddday(dosDate),ddyear(dosDate));
     m_pVal=m_dateBuf;
}

VOID
fmTimeTxv::set(                    // set value to display
USHORT dosTime)                    //   DOS time format
{
     sprintf(m_timeBuf,"%02.2d:%02.2d:%02.2d",
             dthour(dosTime),dtmin(dosTime),dtsec(dosTime));
     m_pVal=m_timeBuf;
}

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

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

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

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

VOID
fmSelectTxv::setSelected(          // set checked status
bool isSelected)                   //   is the option checked?
{
     if (isSelected) {
          set("SELECTED");
     }
     else {
          clr();
     }
}

fmAccessTxv::fmAccessTxv(          // required constructor
const CHAR *idStr)                 //   access type ID
{
     static CHAR *postfix[]={"ZERO","READ","DNLD","WRT","UPLD","COOP","FOROP"};

     for (INT i=0 ; i < 7 ; ++i) {
          pVar[i]=new fmSelectTxv(::spr("FM_%s_%s",idStr,postfix[i]));
     }
}

VOID
fmAccessTxv::set(                  // set contents
INT acc)                           //   access level to present
{
     ASSERT(acc >= 0 && acc < 15);
     clr();
     pVar[acc>>1]->setSelected(true);
}

VOID
fmAccessTxv::clr()                 // clear contents
{
     for (INT i=0 ; i < 7 ; ++i) {
          pVar[i]->setSelected(false);
     }
}

fmProfanTxv::fmProfanTxv(          // required constructor
const CHAR *prefix)                //   name prefix
{
     static CHAR *postfix[]={"NONE","MILD","MOD","SEV","DFT"};

     for (INT i=0 ; i < 5 ; ++i) {
          pVar[i]=new fmSelectTxv(::spr("%s_%s",prefix,postfix[i]));
     }
}

VOID
fmProfanTxv::set(                  // set contents
INT pfnlvl)                        //   profanity level to present
{
     ASSERT(pfnlvl >= 0 && pfnlvl < 5);
     clr();
     pVar[pfnlvl]->setSelected(true);
}

VOID
fmProfanTxv::clr()                 // clear contents
{
     for (INT i=0 ; i < 5 ; ++i) {
          pVar[i]->setSelected(false);
     }
}

/* Tree picture management functions */

treePictures::treePictures()
{
     ::setmbk(fmmb);
     m_rootdir=::stgopt(IMGPATH);
     m_empty=::stgopt(GRPMT);
     m_withfor=::stgopt(GRPFOR);
     m_blank=::stgopt(GRPBLANK);
     m_trunk=::stgopt(GRPTRUNK);
     m_branch=::stgopt(GRPBRNCH);
     m_leaf=::stgopt(GRPLEAF);
     m_altEmpty=::stgopt(GRPMTALT);
     m_altFor=::stgopt(GRPFOALT);
     m_altBlank=::stgopt(GRPBLALT);
     m_altTrunk=::stgopt(GRPTRALT);
     m_altBranch=::stgopt(GRPBRALT);
     m_altLeaf=::stgopt(GRPLEALT);
     m_preCode=::stgopt(PRECODE);
     m_midCode=::stgopt(MIDCODE);
     m_postCode=::stgopt(POSTCODE);
     ::rstmbk();
}

VOID
treePictures::empty(               // empty group picture
ostream &ost)                      //   output stream to write to
{
     outputPic(ost,urlPath(m_rootdir,m_empty),m_altEmpty);
}

VOID
treePictures::withfor(             // group with forums picture
ostream &ost)                      //   output stream to write to
{
     outputPic(ost,urlPath(m_rootdir,m_withfor),m_altFor);
}

VOID
treePictures::blank(               // tree blank space picture
ostream &ost)                      //   output stream to write to
{
     outputPic(ost,urlPath(m_rootdir,m_blank),m_altBlank);
}

VOID
treePictures::trunk(               // tree trunk picture
ostream &ost)                      //   output stream to write to
{
     outputPic(ost,urlPath(m_rootdir,m_trunk),m_altTrunk);
}

VOID
treePictures::branch(              // tree branch picture
ostream &ost)                      //   output stream to write to
{
     outputPic(ost,urlPath(m_rootdir,m_branch),m_altBranch);
}

VOID
treePictures::leaf(                // tree leaf picture
ostream &ost)                      //   output stream to write to
{
     outputPic(ost,urlPath(m_rootdir,m_leaf),m_altLeaf);
}

VOID
treePictures::outputPic(           // output HTML code for a picture
ostream &ost,                      //   output stream to write to
const CHAR *picPath,               //   path & file name of picture file
const CHAR *altStr)                //   alternate string
{
     ost << m_preCode << picPath << m_midCode << altStr << m_postCode;
}

const CHAR *
treePictures::urlPath(             // generate path of file in URL style
const CHAR *dirName,               //   directory name
const CHAR *fileName)              //   file name
{
     ::stlcpy(m_pathBuf,dirName,GCMAXPTH);
     if (!::samend(m_pathBuf,"/")) {
          ::stlcat(m_pathBuf,"/",GCMAXPTH);
     }
     return(::stlcat(m_pathBuf,fileName,GCMAXPTH));
}

treePictures::~treePictures()
{
     ::free(m_rootdir);
     ::free(m_empty);
     ::free(m_withfor);
     ::free(m_blank);
     ::free(m_trunk);
     ::free(m_branch);
     ::free(m_leaf);
     ::free(m_altEmpty);
     ::free(m_altFor);
     ::free(m_altBlank);
     ::free(m_altTrunk);
     ::free(m_altBranch);
     ::free(m_altLeaf);
     ::free(m_preCode);
     ::free(m_midCode);
     ::free(m_postCode);
}

/* Forum conflict checker functions */

forumLock::forumLock(              // required constructor
VOID *workBuf,                     //   work buffer to be used
USHORT forum)                      //   forum ID to lock
{
     ASSERT(::forumLockNum < ::nterms);
     ::forumLockID[::forumLockNum]=m_forum=forum;
     ::forumLockWork[::forumLockNum]=m_work=workBuf;
     ++::forumLockNum;
}

forumLock::~forumLock()
{
     for (INT i=0 ; i < ::forumLockNum ; ++i) {
          if (m_forum == ::forumLockID[i]) {
               ::memmove(&::forumLockID[i],&::forumLockID[i+1],
                         (::forumLockNum-(i+1))*sizeof(USHORT));
               ::memmove(&::forumLockWork[i],&::forumLockWork[i+1],
                         (::forumLockNum-(i+1))*sizeof(VOID *));
               --::forumLockNum;
               break;
          }
     }
}

GBOOL                              /*   returns TRUE if there is a conflict*/
forumLockCfl(                      /* check-for-conflict function          */
VOID *pWork,                       /*   work area being used for operation */
USHORT forum,                      /*   forum ID with conflict             */
LONG msgid)                        /*   message ID with conflict           */
{
     if (msgid == 0) {
          for (INT i=0 ; i < forumLockNum ; ++i) {
               if (forumLockID[i] == forum && forumLockWork[i] != pWork) {
                    return(TRUE);
               }
          }
     }
     return(FALSE);
}

/* Miscellaneous global functions */

CHAR *                             //   returns pointer to destination
catGroup(                          // concatenate a group onto a group path
CHAR *dst,                         //   buffer to concatenate onto
const CHAR *src,                   //   group name to add
size_t dstSiz)                     //   size of destination buffer
{
     ASSERT(dst != NULL);
     ASSERT(src != NULL);
     if (*dst != '\0') {
          stlcat(dst,"/",dstSiz);
     }
     return(stlcat(dst,src,dstSiz));
}

CHAR *                             //   returns allocated buffer
urlEncodeStr(                      // encode special characters in a URL
const CHAR *src)                   //   unencoded URL
{
     ASSERT(src != NULL);
     size_t len=urlEncodeSize(src);
     CHAR *buf=new CHAR[len];
     return(urlEncodeBuf(buf,src,len));
}

size_t
urlEncodeSize(                     // size of encoded URL (incl '\0')
const CHAR *src)                   //   unencoded URL
{
     size_t len=0;
     CHAR c;
     ASSERT(src != NULL);
     while ((c=*src++) != '\0') {
          if (urlNeedEncode(c)) {
               len+=CSTRLEN("%XX");
          }
          else {
               ++len;
          }
     }
     return(len+1);
}

CHAR *                             //   returns pointer to destination
urlEncodeBuf(                      // encode special characters in URL
CHAR *buf,                         //   buffer to receive encoded string
const CHAR *src,                   //   unencoded URL
size_t bufSiz)                     //   size of destination buffer
{
     if (bufSiz > 0) {
          size_t i=0;
          CHAR c;
          ASSERT(src != NULL);
          ASSERT(buf != NULL);
          while ((c=*src++) != '\0' && i < bufSiz-1) {
               if (urlNeedEncode(c)) {
                    if (i >= bufSiz-CSTRLEN("%XX")) {
                         break;
                    }
                    buf[i++]='%';
                    buf[i++]=hexdig((c>>4)&0x0F);
                    buf[i++]=hexdig(c&0x0F);
               }
               else {
                    buf[i++]=c;
               }
          }
          buf[i]='\0';
     }
     return(buf);
}

size_t
urlDecodeSize(                     // size of decoded URL (incl '\0')
const CHAR *src)                   //   encoded URL
{
     size_t len=0;
     CHAR c;
     ASSERT(src != NULL);
     while ((c=*src++) != '\0') {
          if (c == '%' && isxdigit(*src) && isxdigit(*(src+1))) {
               src+=2;
          }
          ++len;
     }
     return(len+1);
}

CHAR *                             //   returns pointer to destination
urlDecodeBuf(                      // decode special characters in URL
CHAR *buf,                         //   buffer to receive decoded string
const CHAR *src,                   //   unencoded URL
size_t bufSiz)                     //   size of destination buffer
{
     if (bufSiz > 0) {
          size_t i=0;
          CHAR c;
          ASSERT(src != NULL);
          ASSERT(buf != NULL);
          while ((c=*src++) != '\0' && i < bufSiz-1) {
               if (c == '%' && isxdigit(*src) && isxdigit(*(src+1))) {
                    c=(hexval(*src++)<<4)|(hexval(*src++));
               }
               buf[i++]=c;
          }
          buf[i]='\0';
     }
     return(buf);
}

bool
urlNeedEncode(                     // does this character need to be encoded?
CHAR c)                            //   character to check
{
     return(c <= ' ' || c >= '~' || strchr("\t\"#%<>?[\]^`{|}",c) != NULL);
}

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);
}

VOID
setForDskTxv(                      // set forum text vars
const struct fordsk *fdsk)         //   from on-disk definition format
{
     txvForumID.set(fdsk->forum);
     txvForNameURL.set(fdsk->name);
     txvForNameDisp.set(fdsk->name);
     txvForTopic.set(fdsk->topic);
     txvForumOp.set(fdsk->forop);
     txvDataFile.set(fdsk->datfil);
     txvAttPath.set(fdsk->attpath);
     txvNumThr.set(fdsk->nthrs);
     txvNumMsg.set(fdsk->nmsgs);
     txvNumFiles.set(fdsk->nfiles);
     txvNumWaitApp.set(fdsk->nw4app);
     txvForKey.set(fdsk->forlok);
     accDftNon.set(fdsk->dfnpv);
     accDftPrv.set(fdsk->dfprv);
     accMaxNon.set(fdsk->mxnpv);
     txvMsgLife.set(fdsk->msglif);
     txvChgWrtMsg.set(fdsk->chgmsg);
     txvChgRdMsg.set(fdsk->chgrdm);
     txvChgWrtAtt.set(fdsk->chgatt);
     txvChgRdAtt.set(fdsk->chgadl);
     txvChgWrtAttK.set(fdsk->chgupk);
     txvChgRdAttK.set(fdsk->chgdpk);
     txvCrdRate.set(fdsk->ccr);
     pfnForum.set(fdsk->pfnlvl);
     datCrtForum.set(fdsk->crdate);
     timCrtForum.set(fdsk->crtime);
}

VOID
setForDefTxv(                      // set forum text vars
const struct fordef *fdef)         //   from in-memory definition format
{
     SHORT tmp;

     txvForumID.set(fdef->forum);
     txvForNameURL.set(fdef->name);
     txvForNameDisp.set(fdef->name);
     txvForTopic.set(fdef->topic);
     txvForumOp.set(fdef->forop);
     txvNumThr.set(fdef->nthrs);
     txvNumMsg.set(fdef->nmsgs);
     txvNumFiles.set(fdef->nfiles);
     txvNumWaitApp.set(fdef->nw4app);
     txvForKey.set(fdef->forlok);
     tmp=fdef->dfnpv;
     accDftNon.set(tmp);
     tmp=fdef->dfprv;
     accDftPrv.set(tmp);
     tmp=fdef->mxnpv;
     accMaxNon.set(tmp);
     txvMsgLife.set(fdef->msglif);
     txvChgWrtMsg.set(fdef->chgmsg);
     txvChgRdMsg.set(fdef->chgrdm);
     txvChgWrtAtt.set(fdef->chgatt);
     txvChgRdAtt.set(fdef->chgadl);
     txvChgWrtAttK.set(fdef->chgupk);
     txvChgRdAttK.set(fdef->chgdpk);
     txvCrdRate.set(fdef->ccr);
     tmp=fdef->pfnlvl;
     pfnForum.set(tmp);
     datCrtForum.set(fdef->crdate);
     timCrtForum.set(fdef->crtime);
}

VOID
clrForumTxv()                      // clear forum-related text variables
{
     txvForumID.clr();
     txvForNameURL.clr();
     txvForNameDisp.clr();
     txvForTopic.clr();
     txvForumOp.clr();
     txvDataFile.clr();
     txvAttPath.clr();
     txvNumThr.clr();
     txvNumMsg.clr();
     txvNumFiles.clr();
     txvNumWaitApp.clr();
     txvForKey.clr();
     accDftNon.clr();
     accDftPrv.clr();
     accMaxNon.clr();
     txvMsgLife.clr();
     txvChgWrtMsg.clr();
     txvChgRdMsg.clr();
     txvChgWrtAtt.clr();
     txvChgRdAtt.clr();
     txvChgWrtAttK.clr();
     txvChgRdAttK.clr();
     txvCrdRate.clr();
     pfnForum.clr();
     datCrtForum.clr();
     timCrtForum.clr();
}

bool
valfdfnam(                         /* is this a valid forum data file name?*/
const CHAR *nam)                   /*   file name to change                */
{
     const CHAR *cp;

     ASSERT(nam != NULL);
     cp=strrchr(nam,'.');
     return(*nam != '\0' && (cp == NULL || sameas(cp,".dat")));
}

CHAR *                             /*   returns copy of pointer to buffer  */
fixfdfnam(                         /* fix up forum data file name          */
CHAR *nambuf)                      /*   file name buffer (must be MAXPATH) */
{
     CHAR *cp;

     ASSERT(nambuf != NULL);
     cp=strrchr(nambuf,'.');
     if (cp != NULL) {
          *cp='\0';
     }
     return(stlcat(nambuf,".dat",GCMAXPTH));
}

static CHAR *                      /*   copy of pointer to destination     */
makePath(                          /* combine directory and file name      */
CHAR *dst,                         /*   destination buffer                 */
const CHAR *dir,                   /*   directory name                     */
const CHAR *file,                  /*   file name                          */
size_t dstSiz)                     /*   size of destination buffer         */
{
     if (dir != NULL && dir != dst) {
          stlcpy(dst,dir,dstSiz);
     }
     if (dst[0] != '\0' && !isvalds(dst[strlen(dst)-1])) {
          stlcat(dst,SLS,dstSiz);
     }
     return(stlcat(dst,file,dstSiz));
}

static CHAR *                      /*   returns copy of ptr to dest        */
strmove(                           /* move string w/overlapping src/dest   */
CHAR *dst,                         /*   destination                        */
const CHAR *src)                   /*   source                             */
{
#ifdef GCDOSP
     /* necessary because naughty galmovmem() returns void */
     memmove(dst,src,strlen(src)+1);
     return(dst);
#else
     return((CHAR*)memmove(dst,src,strlen(src)+1));
#endif // GCDOSP
}

static CHAR
hexdig(                            /* get hexadecimal value from digit     */
UINT val)                          /*   value to convert                   */
{
     CHAR digits[16]="0123456789ABCDEF";

     ASSERT(val < 16);
     return(digits[val]);
}

static INT
hexval(                            /* get hexadecimal value from digit     */
CHAR dig)                          /*   hexadecimal digit to convert       */
{
     ASSERT(isxdigit(dig));
     if (isdigit((dig=toupper(dig)))) {
          return(dig-'0');
     }
     return((dig-'A')+10);
}
