//
//  File:   hdsSystem.cpp
//
//  Legal Notice:   This file is part of the HadronZoo C++ Class Library. Copyright 2025 HadronZoo Project (http://www.hadronzoo.com)
//
//  The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free
//  Software Foundation, either version 3 of the License, or any later version.
//
//  The HadronZoo C++ Class Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
//  A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses.
//
//
//  Dissemino HTML Generation
//
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <stdarg.h>
#include <dirent.h>
#include <netdb.h>
#include <signal.h>
#include <sys/stat.h>
#include <openssl/ssl.h>
#include "hzChars.h"
#include "hzString.h"
#include "hzChain.h"
#include "hzCodec.h"
#include "hzDate.h"
#include "hzDatabase.h"
#include "hzDocument.h"
#include "hzTextproc.h"
#include "hzTokens.h"
#include "hzHttpServer.h"
#include "hzMailer.h"
#include "hzProcess.h"
#include "hzDissemino.h"
using namespace std ;
/*
**  Variables
*/
global  hdsSphere*  _hzGlobal_Dissemino ;   //  Holds all live applications
//  Standard strings. Please note that it is forbidden to use the values set here as names for other fields in your forms!
static  const hzString  g_Errmsg = "errmsg" ;           //  The name of the event variable used on form redisplay to convey why form is being redisplayed.
static  const hzString  s_articleTitle = "x-title" ;    //  Name of HTTP header supplied upon request of an article
/*
**  Functions
*/
void    hdsApp::SetStdTypeValidations   (void)
{
    //  Attaches Validation JavaScript to the standard data types. For this to succeed, the standard data types have to be present in the ADP. This function must therefore, only be
    //  called after a call to m_ADP.InitStandard(). Execution is terminated if any of the standard types expected are missing. 
    //
    //  Arguments:  None
    //  Returns:    None
    _hzfunc("hdsApp::SetStdTypeValidations") ;
    const hdbDatatype*  dt ;            //  Pointer to Cpp data type
    const hdbHzotype*   ht ;            //  Pointer to Cpp data type
    const hzString      jsAlpha ;       //  Javascript for alpha
    const hzString      jsAlphnum ;     //  Javascript for alphanumeric
    const hzString      jsIpaddr ;      //  Javascript for IP addresses
    const hzString      jsDomain ;      //  Javascript for Email addresses
    const hzString      jsEmaddr ;      //  Javascript for Email addresses
    const hzString      jsURL ;         //  Javascript for URLs
    const hzString      jsXdate ;       //  Javascript for URLs
    const hzString      jsSdate ;       //  Javascript for URLs
    const hzString      jsTime ;        //  Javascript for URLs
    //InitDatabase() ;
    //  Add JS to Group 2
    dt = m_ADP.GetDatatype("ipaddr") ;
    ht = dynamic_cast<const hdbHzotype*>(dt) ;
    if (ht)
        ht->m_valJS=jsIpaddr;
    else
        hzexit(E_NOTFOUND, "%s: No ipaddr datatype declared", *m_Appname) ;
    dt = m_ADP.GetDatatype("domain") ;
    ht = dynamic_cast<const hdbHzotype*>(dt) ;
    if (ht)
        ht->m_valJS=jsDomain;
    else
        hzexit(E_NOTFOUND, "%s: No domain datatype declared", *m_Appname) ;
    dt = m_ADP.GetDatatype("emaddr") ;
    ht = dynamic_cast<const hdbHzotype*>(dt) ;
    if (ht)
        ht->m_valJS=jsEmaddr;
    else
        hzexit(E_NOTFOUND, "%s: No emaddr datatype declared", *m_Appname) ;
    dt = m_ADP.GetDatatype("url") ;
    ht = dynamic_cast<const hdbHzotype*>(dt) ;
    if (ht)
        ht->m_valJS=jsURL;
    else
        hzexit(E_NOTFOUND, "%s: No url datatype declared", *m_Appname) ;
    dt = m_ADP.GetDatatype("time") ;
    ht = dynamic_cast<const hdbHzotype*>(dt) ;
    if (ht)
        ht->m_valJS=jsTime;
    else
        hzexit(E_NOTFOUND, "%s: No time datatype declared", *m_Appname) ;
    dt = m_ADP.GetDatatype("sdate") ;
    ht = dynamic_cast<const hdbHzotype*>(dt) ;
    if (ht)
        ht->m_valJS=jsSdate ;
    else
        hzexit(E_NOTFOUND, "%s: No sdate datatype declared", *m_Appname) ;
    dt = m_ADP.GetDatatype("xdate") ;
    ht = dynamic_cast<const hdbHzotype*>(dt) ;
    if (ht)
        ht->m_valJS=jsXdate ;
    else
        hzexit(E_NOTFOUND, "%s: No xdate datatype declared", *m_Appname) ;
}
bool    hdsApp::IsPcEnt (hzString& pcntEnt, const char* input)
{
    //  Determine if the supplied cstr is a percent entity and if it is, populate the supplied string with the entity
    //
    //  Arguments:  1)  pcntEnt The hzString to contain the entity
    //              2)  input   The input text
    //
    //  Returns:    True    If the supplied cstr is a percent entity
    //              False   Otherwise
    _hzfunc("hdsApp::IsPcEnt") ;
    hzChain     W ;     //  Chain to build entity
    uint32_t    c ;     //  Char count
    pcntEnt.Clear() ;
    if (!input)
        return false ;
    if (input[0] != CHAR_PERCENT)
        return false ;
    if (input[1] != 'x' && input[1] != 'u' && input[1] != 's' && input[1] != 'e' && input[1] != 'i' && input[1] != 'v')
        return false ;
    if (input[2] != CHAR_COLON)
        return false ;
    //  Get the variable type and name component
    W.AddByte(CHAR_PERCENT) ;
    W.AddByte(input[1]) ;
    W.AddByte(CHAR_COLON) ;
    for (input += 3, c = 0 ; *input && *input != CHAR_SCOLON && IsAlpha(*input) ; c++, input++)
        W.AddByte(*input) ;
    if (c && *input == CHAR_PERIOD)
    {
        W.AddByte(*input) ;
        for (input++ ; *input && *input != CHAR_SCOLON && IsAlpha(*input) ; input++)
            W.AddByte(*input) ;
    }
    if (c && *input == CHAR_SCOLON)
        W.AddByte(*input) ;
    else
        c = 0 ;
    if (c)
        pcntEnt = W ;
    W.Clear() ;
    return c ? true : false ;
}
bool    hdsApp::AtPcEnt (hzString& pcntEnt, chIter& input)
{
    //  Determine if the supplied chain iterator is at the start of a percent entity and if it is, populate the supplied string (arg 1) with the entity
    //
    //  Arguments:  1)  pcntEnt The hzString to contain the entity
    //              2)  input   The input text
    //
    //  Returns:    True    If a percent entity is found
    //              False   Otherwise
    _hzfunc("hdsApp::AsPcEnt") ;
    hzChain     W ;         //  Chain to build entity
    chIter      zi ;        //  Temp iterator
    uint32_t    c = 0 ;     //  Char count
    pcntEnt.Clear() ;
    zi = input ;
    if (*zi != CHAR_PERCENT)
        return false ;
    W.AddByte(CHAR_PERCENT) ;
    zi++ ;
    if (*zi != 'x' && *zi != 'e' && *zi != 's' && *zi != 'u')
        return false ;
    W.AddByte(*zi) ;
    zi++ ;
    if (*zi != CHAR_COLON)
        return false ;
    W.AddByte(CHAR_COLON) ;
    //  Get the variable type and name component
    for (zi++ ; !zi.eof() && *zi != CHAR_SCOLON && IsAlpha(*zi) ; c++, zi++)
        W.AddByte(*zi) ;
    if (c && *zi == CHAR_PERIOD)
    {
        W.AddByte(*zi) ;
        for (zi++ ; !zi.eof() && *zi != CHAR_SCOLON && IsAlpha(*zi) ; zi++)
            W.AddByte(*zi) ;
    }
    if (c && *zi == CHAR_SCOLON)
        W.AddByte(*zi) ;
    else
        c = 0 ;
    if (c)
        pcntEnt = W ;
    W.Clear() ;
    return c ? true : false ;
}
hdbBasetype hdsApp::PcEntTest   (hzString& err, hdsFormdef* pFormdef, const hdbClass* pHost, const hzString& pcntEnt)
{
    //  Validate the supplied percent entity.
    //
    //  The scope tests differ by percent entity class as follows:-
    //
    //      X (%x)  Standard system values: These are global so as long as the named entity exists it will be valid.
    //      U (%u)  Standard user values:   Must exist and be within a form accessible to logged in users.
    //      S (%s)  User session variables: These are valid within any form as long as named variable has been created in an earlier <procdata> command.
    //      E (%e)  Event values:           These are tested against the fields known to exist in the supplied form.
    //      I (%i)  In-situ values:         Must be within a form and name members of the form's native data class.
    //      V (%v)  Session object values:  These are valid if the name members of a session object that an earlier <procdata> command has brought into focus.
    //
    //  Arguments:  1)  err         The error report string to be populated in the event of an error.
    //              2)  pFormdef    The host form where the percent entity is in (if any)
    //              3)  pHost       Applicable data class. If this is not supplied it will be the host class of the supplied form (if any)
    //              4)  pcntEnt     The string to tbe tested for validity as a percent entity
    //
    //  Returns     1)  E_SYNTAX    The entity is malformed
    //              2)  E_NOTFOUND  The entity or a component thereof, refers to a non existant class or variable
    //              3)  E_RANGE     The entity is not an available option
    //              4)  E_TYPE      The datatype of the entity is not as required (if set in arg2)
    //              5)  E_OK        The entity is valid
    _hzfunc("hdsApp::PcEntTest") ;
    const hdbClass*     pUC ;       //  User class (for checking %u: user entities
    const hdbMember*    pMbr ;      //  Class member
    hdbObjRepos*    pRepos = 0 ;    //  Relevent cache (either the cache for the logged in user or the cache containing current object of interest)
    hdsPage*        pPage = 0 ;     //  Hostpage of form
    hdsField*       pFld ;          //  Field within the form definition
    hzChain         W ;             //  Variable name buffer
    hzChain         erep ;          //  Error report
    hzAtom          atom ;          //  For litteral values and type testing
    const char*     i ;             //  Used to iterate input value
    hzString        w ;             //  Constructed variable name
    hzString        extn ;          //  Variable extension for binaries and documents (eg ->addr, ->data)
    hzString        val ;           //  Variable value
    uint32_t        srcInd ;        //  Source indicator
    uint32_t        n ;             //  General iterator
    hdbBasetype     dt ;            //  Actual datatype of entity
    hzEcode         rc = E_OK ;     //  Return code
    err.Clear() ;
    if (!pcntEnt)
        { err = "null entity" ; return BASETYPE_UNDEF ; }
    i = *pcntEnt ;
    if (*i != CHAR_PERCENT)
    {
        erep.Printf("Literal value %s not allowed", *pcntEnt) ;
        err = erep ;
        return BASETYPE_UNDEF ;
    }
    i++ ;
    srcInd = *i ;
    if (srcInd != 'x' && srcInd != 'u' && srcInd != 's' && srcInd != 'e' && srcInd != 'i' && srcInd != 'v')
        { err = "Invalid percent entity class" ; return BASETYPE_UNDEF ; }
    i++ ;
    if (*i != CHAR_COLON)
        { err = "Missing colon after percent entity class" ; return BASETYPE_UNDEF ; }
    //  Get the variable type and name component
    for (i++ ; *i && *i != CHAR_SCOLON && IsAlphanum(*i) ; i++)
        W.AddByte(*i) ;
    w = W ;
    W.Clear() ;
    if (*i == CHAR_PERIOD)
    {
        for (i++ ; *i && *i != CHAR_SCOLON && IsAlphanum(*i) ; i++)
            W.AddByte(*i) ;
        extn = W ;
        W.Clear() ;
    }
    if (*i != CHAR_SCOLON)
        { erep.Printf("PcEntTest: Missing semicolon after percent entity variable name (%s), char is [%c]", *pcntEnt, *i) ; err = erep ; return BASETYPE_UNDEF ; }
    i++ ;
    //  Lookup variable and determine its value
    dt = BASETYPE_UNDEF ;
    if (!pHost)
    {
        if (pFormdef)
            pHost = pFormdef->m_pClass ;
    }
    switch (srcInd)
    {
    case 'x':   //  System variables
        if (w == "count" || w == "objid")
        {
            //  Count is of objects in a class
            pRepos = m_ADP.GetObjRepos(extn) ;
            if (!pRepos)
                { rc = E_NOTFOUND ; erep.Printf("%s is not a declared repository", *extn) ; }
            else
                dt = BASETYPE_INT32 ;
            break ;
        }
        if (w == "date")        { dt = BASETYPE_SDATE ;     break ; }
        if (w == "time")        { dt = BASETYPE_TIME ;      break ; }
        if (w == "datetime")    { dt = BASETYPE_XDATE ;     break ; }
        if (w == "referer")     { dt = BASETYPE_URL ;       break ; }
        if (w == "usecs")       { dt = BASETYPE_UINT32 ;    break ; }
        if (w == "random")      { dt = BASETYPE_STRING ;    break ; }
        if (w == "uid")         { dt = BASETYPE_INT32 ;     break ; }
        if (w == "user")        { dt = BASETYPE_STRING ;    break ; }
        //  Also permitted are ....
        rc = E_RANGE ;
        erep.Printf("Invalid system entity (%s)", *w) ;
        break ;
    case 'u':   //  U-Class Percent Entities: For these to evaluate, the user must be logged in and the entity must name an atomic member of the applicable user class. However only
                //  the validity is being tested here, so user status is not important.
        if (!pFormdef)
            { rc = E_ARGUMENT ; erep.Printf("All user variables must occur within a form lying in a page accessable to logged on users") ; break ; }
        if (!m_ADP.m_pReposSubscriber)
            { rc = E_RANGE ; erep.Printf("Subscriber Repository not invoked - No users\n") ; break ; }
        pMbr = m_ADP.m_pClassSubscriber->GetMember(w) ;
        if (pMbr)
            { dt = pMbr->Basetype() ; break ; }
        for (n = 0 ; rc == E_OK && n < m_UserTypes.Count() ; n++)
        {
            val = m_UserTypes.GetKey(n) ;
            pUC = m_ADP.GetPureClass(val) ;
            if (!pUC)
                Fatal("No such user class as %s\n", *val) ;
            if (pPage->m_resAccess & m_UserTypes.GetObj(n))
            {
                pMbr = pUC->GetMember(w) ;
                if (pMbr)
                    dt = pMbr->Basetype() ;
                else
                {
                    rc = E_ARGUMENT ;
                    erep.Printf("User variable %s not a member of user class %s and yet this class of user can access the page", *w, pUC->txtType()) ;
                }
            }
        }
        break ;
    case 's':   //  Session variables (explicitly set as part of this or a parent form) constrained by the same set as event variables
        if (!m_tmpVarsSess.Exists(w))
            { rc = E_RANGE ; erep.Printf("%s is not in scope", *w) ; }
        dt = m_tmpVarsSess[w] ;
        break ;
    case 'e':   //  Event variables. The entity is legal if the refered to variable is in scope (is within the supplied set of available variables)
        if (pFormdef)
        {
            if (!pFormdef->m_mapFlds.Exists(w))
                erep.Printf("Variable %s is not incident in form %s (%p)", *w, *pFormdef->m_Formname, pFormdef) ;
            else
            {
                pFld = pFormdef->m_mapFlds[w] ;
                if (pFld)
                    { dt = pFld->m_Fldspec.m_pType->Basetype() ; break ; }
            }
        }
        if (pHost)
        {
            pMbr = pHost->GetMember(w) ;
            if (pMbr)
                dt = pMbr->Basetype() ;
            else
                { rc = E_RANGE ; erep.Printf("%s is not a member of class %s", *w, pHost->txtType()) ; }
            break ;
        }
        if (!pFormdef && pHost)
            erep.Printf("Variable %s cannot be tested as there is neither a host form or class", *w) ;
        break ;
    case 'v':   //  Current object variables require the page they appear in to have a current object. This means there must be a current object class
                //  and a current object ID.
        if (!pHost)
            { rc = E_ARGUMENT ; erep.Printf("All user variables must occur within the context of a host user class") ; break ; }
        pMbr = pHost->GetMember(w) ;
        if (!pMbr)
            { rc = E_RANGE ; erep.Printf("%s is not a member of class %s and nor is it a freely declared variable", *w, pHost->txtType()) ; }
        else
            dt = pMbr->Basetype() ;
        break ;
    }
    err = erep ;
    return dt ;
}
hzEcode hdsApp::PcEntScanStr    (hzString& err, hdsFormdef* pFormdef, hdbClass* pHost, const hzString& input)
{
    //  Scans a string looking for percent entities to test. If none found then the supplied text is automatically deemed valid. If there are percent entities,
    //  these are tested for valididity by calling PcEntTest(). If any of these are invalid or out of scope the supplied string is deemed invalid.
    //
    //  Arguments:  1)  err     The error report string.
    //              2)  pFormdef    The currently applicable form (if any)
    //              3)  pHost   The currently applicable class. If this is NULL the class will be that of the supplied form (if any)
    //              4)  input   The input text
    //
    //  Returns:    E_SYNTAX    If part of the text appears to be a malformed percent entity.
    //              E_OK        If the supplied input contain no percent entities or valid percent entities.
    _hzfunc("hdsApp::PcEntScanStr") ;
    hzChain         erep ;          //  For error report
    const char*     i ;             //  Input iterator
    hzString        pcntEnt ;       //  Current percent entity
    hzString        cerr ;          //  Current entity error
    hdbBasetype     peType ;        //  Returned datatype
    hzEcode         rc = E_OK ;     //  Return code
    for (i = *input ; *i ;)
    {
        if (*i == CHAR_PERCENT)
        {
            if (i[1] == CHAR_PERCENT)
                { i += 2 ; continue ; }
            if (IsAlpha(i[1]) && i[2] == CHAR_COLON && IsAlpha(i[3]))
            {
                if (IsPcEnt(pcntEnt, i))
                {
                    peType = PcEntTest(cerr, pFormdef, pHost, pcntEnt) ;
                    if (peType == BASETYPE_UNDEF)
                        { rc = E_SYNTAX ; erep.Printf("(%s %s)", *pcntEnt, *cerr) ; }
                    else
                        { i += pcntEnt.Length() ; continue ; }
                }
                else
                {
                    //  Sonething like %a:a but is not actually a percent entity is not allowed
                    rc = E_SYNTAX ;
                    erep.Printf("(%c%c%c%c - malformed percent entity)", i[0], i[1], i[2], i[3]) ;
                }
            }
        }
        i++ ;
    }
    err = erep ;
    return rc ;
}
hzEcode hdsApp::PcEntScanChain  (hzString& err, hdsFormdef* pFormdef, hdbClass* pHost, const hzChain& input)
{
    //  Scans a chain (of HTML) looking for percent entities to test. If none found then the supplied text is automatically deemed valid. If there are percent
    //  entities, these are tested for valididity by calling PcEntTest(). If any of these are invalid or out of scope the supplied text is deemed invalid.
    //
    //  Arguments:  1)  err     The error report string.
    //              2)  pFormdef    The currently applicable form (if any)
    //              3)  pHost   The currently applicable class (if any)
    //              4)  input   The input text
    //
    //  Returns:    E_SYNTAX    If part of the text appears to be a malformed percent entity.
    //              E_OK        If the supplied input contain no percent entities or valid percent entities.
    _hzfunc("hdsApp::PcEntScanChain") ;
    hzChain         erep ;          //  For error report
    chIter          zi ;            //  Input iterator
    hzString        pcntEnt ;       //  Current percent entity
    hzString        cerr ;          //  Current entity error
    hdbBasetype     peType ;        //  Returned datatype
    hzEcode         rc = E_OK ;     //  Return code
    for (zi = input ; !zi.eof() ;)
    {
        if (*zi == CHAR_PERCENT)
        {
            if (zi[1] == CHAR_PERCENT)
                { zi += 2 ; continue ; }
            if (IsAlpha(zi[1]) && zi[2] == CHAR_COLON && IsAlpha(zi[3]))
            {
                if (AtPcEnt(pcntEnt, zi))
                {
                    peType = PcEntTest(cerr, pFormdef, pHost, pcntEnt) ;
                    if (peType == BASETYPE_UNDEF)
                        { rc = E_SYNTAX ; erep.Printf("(%s %s)", *pcntEnt, *cerr) ; }
                    else
                        { zi += pcntEnt.Length() ; continue ; }
                }
                else
                {
                    //  Sonething like %a:a but is not actually a percent entity is not allowed
                    rc = E_SYNTAX ;
                    erep.Printf("(%c%c%c%c - malformed percent entity)", zi[0], zi[1], zi[2], zi[3]) ;
                }
            }
        }
        zi++ ;
    }
    err = erep ;
    return rc ;
}
hzEcode hdsApp::PcEntConv   (hzAtom& atom, const hzString& v, hzHttpEvent* pE)
{
    //  Convert percent entities to set the type and value of the supplied atom. Here the percent entity is supplied in isolation. The ConvertText function
    //  (which calls this function), is used where percent entities can appear as part of a larger string (that is otherwise to be used verbatim).
    //
    //  Arguments:  1)  atom    The hzAtom to be set with the variable's value
    //              2)  v       The percent entity
    //              3)  pE      HTTP event pointer
    //  
    //  Returns:    E_SYNTAX    If the supplied percent entity string is invalid
    //              E_NOTFOUND  If the supplied percent entity is not matched
    //              E_OK        If the supplied percent entity string is valid
    _hzfunc("hdsApp::PcEntConv") ;
    const hdbMember*    pMbr ;      //  Class member
    hzChain         W ;             //  Variable name buffer
    hdsInfo*        pInfo ;         //  User session if applicable
    hdsFormref*     pFormref ;      //  From the event context
    hdsFormdef*     pFormdef ;      //  From the form reference
    hdsField*       pFld ;          //  Selected by name from the form
    const hdbEnum*  pSlct ;         //  Selector (for conversion of ints to strings)
    hdbObjRepos*    pRepos = 0 ;    //  Repository
    const char*     i ;             //  Used to iterate input value
    hzString        w_val ;         //  Constructed variable name
    hzString        extn ;          //  Variable extension for binaries and documents (eg ->addr, ->data)
    hzString        val ;           //  Variable value
    uint32_t        nTest ;         //  Category of percent directive
    char            buf[12] ;       //  Buffer for random init code
    hzEcode         rc = E_OK ;     //  Return code
    if (!pE)
        hzexit(E_ARGUMENT, "%s: No HTTP event supplied for input=%s", *m_Appname, *v) ;
    atom.Clear() ;
    if (!v)
        return E_OK ;
    m_pLog->Log("PcEntConv processing %s\n", *v) ;
    //  If nothing to convert
    if (v[0] != CHAR_PERCENT)
    {
        atom = v ;
        return E_OK ;
    }
    //  All percent entities must have colon after the percent and the source directive
    if (v[2] != CHAR_COLON)
        return E_SYNTAX ;
    //  Get the variable type and name component
    i = *v ;
    for (i += 3 ; *i && *i != CHAR_SCOLON && IsAlpha(*i) ; i++)
        W.AddByte(*i) ;
    w_val = W ;
    W.Clear() ;
    if (*i == CHAR_PERIOD)
    {
        for (i++ ; *i && *i != CHAR_SCOLON && IsAlpha(*i) ; i++)
            W.AddByte(*i) ;
        extn = W ;
        W.Clear() ;
    }
    if (*i == CHAR_SCOLON)
        i++ ;
    //  If there is a sesssion - this affects many percent entities
    pInfo = (hdsInfo*) pE->Session() ;
    //  Lookup variable and determine its value
    switch (v[1])
    {
    case 'x':   //  SYSTEM VALUES
        if (w_val == "count")
        {
            //  Count is of objects in a class
            pRepos = m_ADP.GetObjRepos(extn) ;
            if (!pRepos)
                { rc = E_NOTFOUND ; m_pLog->Log("Cannot Xlate. Repository not found: %s\n", *extn) ; }
            else
                atom = pRepos->Count() ;
        }
        else if (w_val == "objid")
        {
            pRepos = m_ADP.GetObjRepos(extn) ;
            if (!pRepos)
                { rc = E_NOTFOUND ; m_pLog->Log("Cannot Xlate. Repository not found: %s\n", *extn) ; }
            else
            {
                if (pE->m_ObjIds.Exists(extn))
                    atom = pE->m_ObjIds[extn] ;
                else
                    { rc = E_NOTFOUND ; m_pLog->Log("Cannot Xlate. Event entry not found: %s\n", *extn) ; }
            }
        }
        else if (w_val == "date")       atom = pE->m_Occur.Date() ;
        else if (w_val == "time")       atom = pE->m_Occur.Time() ;
        else if (w_val == "datetime")   atom = pE->m_Occur ;
        else if (w_val == "referer")    atom.SetValue(BASETYPE_STRING, pE->Referer()) ; //.Resource()) ;
        else if (w_val == "usecs")      atom = pE->m_Occur.uSec() ;
        else if (w_val == "uid")        atom = (uint32_t) (pInfo ? pInfo->m_UserId : 0) ;
        else if (w_val == "random")     { sprintf(buf, "%06d", pE->m_Occur.uSec()) ; atom.SetValue(BASETYPE_STRING, buf) ; }
        else
            { rc = E_NOTFOUND ; m_pLog->Log("Cannot Xlate. Unknown parameter: %s\n", *w_val) ; }
        break ;
    case 'e':   //  HTTP EVENT VARIABLES
        pSlct = m_ADP.GetDataEnum(w_val) ;
        if (pSlct)
        //if (m_ADP.mapEnums.Exists(w_val))
        {
            //  If a selector, values returned in form submissions will be integers
            val = pE->m_mapStrings[w_val] ;
            if (IsPosint(nTest, *val))
            {
                //pSlct = m_ADP.mapEnums[w_val] ;
                val = pSlct->GetStr(nTest) ;
                
                rc = atom.SetValue(BASETYPE_STRING, val) ;
                break ;
            }
        }
        if (pE->m_Uploads.Exists(w_val))
        {
            //  Find an entry in m_Files paart of the HTTP event - then give either the file name or the file data
            hzHttpFile& hf = pE->m_Uploads[w_val] ;
            if (extn == "name")
                rc = atom.SetValue(BASETYPE_STRING, hf.m_filename) ;
            else if (extn == "data")
            {
                if (hf.m_file.Size())
                    { rc = E_OK ; atom = hf.m_file ; }
                else
                    rc = E_NODATA ;
                m_pLog->Log("Have file %s of %d bytes\n", *hf.m_filename, hf.m_file.Size()) ;
            }
            else
                rc = E_BADVALUE ;
            break ;
        }
        if (w_val == g_Errmsg)
        {
            if (pE->m_appError)
                atom = pE->m_appError ;
            break ;
        }
        if (w_val == "resarg")
        {
            atom.SetValue(BASETYPE_STRING, pE->m_Resarg) ;
            break ;
        }
        if (pE->m_mapStrings.Exists(w_val))
        {
            val = pE->m_mapStrings[w_val] ;
            if (pE->m_pContextForm)
            {
                pFormref = (hdsFormref*) pE->m_pContextForm ;
                pFormdef = m_FormDefs[pFormref->m_Formname] ;
                if (!pFormdef->m_mapFlds.Exists(w_val))
                {
                    m_pLog->Log("Case 1: Using string context value %s = %s\n", *w_val, *val) ;
                    rc = atom.SetValue(BASETYPE_STRING, val) ;
                }
                else
                {
                    pFld = pFormdef->m_mapFlds[w_val] ;
                    if (pFld)
                    {
                        m_pLog->Log("Case 2: Using string context value %s = %s %s\n", *w_val, Basetype2Txt(pFld->m_Fldspec.m_pType->Basetype()), *val) ;
                        rc = atom.SetValue(pFld->m_Fldspec.m_pType->Basetype(), val) ;
                    }
                    else
                    {
                        m_pLog->Log("Case 3: Using string context value %s = %s\n", *w_val, *val) ;
                        rc = atom.SetValue(BASETYPE_STRING, val) ;
                    }
                }
            }
            else
            {
                m_pLog->Log("Using context-less value %s\n", *val) ;
                rc = atom.SetValue(BASETYPE_STRING, val) ;
            }
            break ;
        }
        if (pE->m_mapChains.Exists(w_val))
        {
            val = pE->m_mapChains[w_val] ;
            if (pE->m_pContextForm)
            {
                pFormref = (hdsFormref*) pE->m_pContextForm ;
                pFormdef = m_FormDefs[pFormref->m_Formname] ;
                if (!pFormdef->m_mapFlds.Exists(w_val))
                {
                    m_pLog->Log("Case 1: Using chain context value %s = %s\n", *w_val, *val) ;
                    rc = atom.SetValue(BASETYPE_STRING, val) ;
                }
                else
                {
                    m_pLog->Log("Case 2: Using chain context value %s = %s\n", *w_val, *val) ;
                    pFld = pFormdef->m_mapFlds[w_val] ;
                    rc = atom.SetValue(pFld->m_Fldspec.m_pType->Basetype(), val) ;
                }
            }
            else
            {
                m_pLog->Log("Using context-less value %s\n", *val) ;
                rc = atom.SetValue(BASETYPE_STRING, val) ;
            }
            break ;
        }
        rc = E_NOTFOUND ;
        break ;
    case 's':   //  SESSION VARIABLES
        rc = E_NOTFOUND ;
        if (pInfo && pInfo->m_Sessvals.Count())
        {
            if (pInfo->m_Sessvals.Exists(w_val))
            {
                atom = pInfo->m_Sessvals[w_val] ;
                rc = E_OK ;
            }
            break ;
        }
        m_pLog->Log("Warning: No such session value [%s]\n", *w_val) ;
        break ;
    case 'i':   //  IN-SITU OBJECT MEMBERS
        rc = E_NOTFOUND ;
        if (!pInfo)
            break ;
        if (!pInfo->m_CurrRepos)
            break ;
        if (!pInfo->m_CurrObj)
            break ;
        //pRepos = m_ADP.m_arrRepositories[pInfo->m_CurrRepos] ;
        pRepos = m_ADP.GetObjRepos(pInfo->m_CurrRepos) ;
        //if (pRepos)
        //  pCache = dynamic_cast<hdbObjCache*>(pRepos) ;
        if (pRepos)
            pMbr = pRepos->GetMember(w_val) ;
        if (pMbr)
        {
            hdbObject   obj ;
            hzChain     Z ;
            rc = pRepos->Fetch(obj, pInfo->m_CurrObj) ;
            if (rc != E_OK)
                break ;
            if (pMbr->Basetype() == BASETYPE_BINARY || pMbr->Basetype() == BASETYPE_TXTDOC)
            {
                if (extn == "addr")
                {
                    m_pLog->Log("Fetching address %s\n", *w_val) ;
                    //rc = pRepos->Fetchval(atom, w_val, pInfo->m_CurrObj) ;
                    obj.GetValue(atom, pMbr) ;
                }
                if (extn == "data")
                {
                    m_pLog->Log("Fetching binary body %s\n", *w_val) ;
                    //rc = pRepos->Fetchbin(atom, w_val, pInfo->m_CurrObj) ;
                    rc = obj.GetBinary(Z, pMbr) ;
                    atom = Z ;
                }
            }
            else
            {
                m_pLog->Log("Fetching ord %s\n", *w_val) ;
                //rc = pRepos->Fetchval(atom, w_val, pInfo->m_CurrObj) ;
                rc = obj.GetValue(atom, pMbr) ;
            }
        }
        break ;
    case 'v':   //  CURRENT OBJECT MEMBERS
        break ;
    case 'u':   //  CURRENT USER-INFO MEMBERS (also user dirs and files)
        rc = E_NOTFOUND ;
        if (!pInfo)
            break ;
        m_pLog->Log("Fetching item %s for subscriber id %d\n", *w_val, pInfo->m_SubId) ;
        if (w_val == "username")
        {
            //  if (m_Allusers)
            //      rc = m_Allusers->Fetchval(atom, SUBSCRIBER_USERNAME, pInfo->m_SubId) ;
            //  m_pLog->Log("Fetched item %s\n", *atom.Str()) ;
            atom = pInfo->m_Username ;
            break ;
        }
        if (pInfo->m_CurrRepos && pInfo->m_UserId > 0)
        {
            //pRepos = m_ADP.m_arrRepositories[pInfo->m_CurrRepos] ;
            pRepos = m_ADP.GetObjRepos(pInfo->m_CurrRepos) ;
            //if (pRepos)
            //  pRepos = dynamic_cast<hdbObjCache*>(pRepos) ;
            if (pRepos)
                pMbr = pRepos->GetMember(w_val) ;
            if (!pMbr)
                break ;
            hdbObject   obj ;
            hzChain     Z ;
            rc = pRepos->Fetch(obj, pInfo->m_CurrObj) ;
            if (rc != E_OK)
                break ;
            if (pMbr->Basetype() == BASETYPE_BINARY || pMbr->Basetype() == BASETYPE_TXTDOC)
            {
                if (extn == "addr")
                {
                    m_pLog->Out("Fetching address %s\n", *w_val) ;
                    //rc = pRepos->Fetchval(atom, w_val, pInfo->m_UserId) ;
                    rc = obj.GetValue(atom, pMbr) ;
                }
                if (extn == "data")
                {
                    m_pLog->Out("Fetching binary body %s\n", *w_val) ;
                    //rc = pRepos->Fetchbin(atom, w_val, pInfo->m_UserId) ;
                    rc = obj.GetBinary(Z, pMbr) ;
                    atom = Z ;
                }
            }
            else
            {
                m_pLog->Out("Fetching ord %s\n", *w_val) ;
                //rc = pRepos->Fetchval(atom, w_val, pInfo->m_UserId) ;
                rc = obj.GetValue(atom, pMbr) ;
            }
        }
        break ;
    default:
        hzerr(E_SYNTAX, "Invalid Percent Entity Category (%c)", v[1]) ;
        rc = E_SYNTAX ;
    }
    return rc ;
}
void    hdsApp::ConvertText (hzChain& Z, hzHttpEvent* pE)
{
    //  Process the supplied data (assumed to be text) by replacing any incident percent entities by thier 'here and now' values. The data is supplied
    //  as a chain which remains in situ and will be unchanged if there are no succesful percent entity conversions.
    //
    //  Arguments:  1)  Z   Data to be treated as text which may contain one or more percent entities.
    //              2)  pE  The HTTP event from which the non system and non user variables can be garnered or from which the user can be established.
    //
    //  Returns:    None
    _hzfunc("hdsApp::ConvertText(1)") ;
    hzChain     W ;             //  Variable name buffer
    hzChain     C ;             //  Final value
    chIter      zi ;            //  Interation of contnt
    hzAtom      atom ;          //  User profile record variable
    hzString    pcntEnt ;       //  Constructed variable name
    hzEcode     rc ;            //  Variable location. 1 system, 2 event, 3 session, 4 user value (must be logged in with session & cookie)
    if (!Z.Size())
        return ;
    for (zi = Z ; !zi.eof() ;)
    {
        if (*zi == CHAR_AT)
        {
            if (zi == "@resarg;")
            {
                if (pE->m_Resarg)
                    C << pE->m_Resarg ;
                zi += 8 ;
                continue ;
            }
        }
        if (*zi == CHAR_PERCENT)
        {
            if (zi[1] == CHAR_PERCENT)
                { C << "%%" ; zi += 2 ; continue ; }
            if (IsAlpha(zi[1]) && zi[2] == CHAR_COLON && IsAlpha(zi[3]))
            {
                if (AtPcEnt(pcntEnt, zi))
                {
                    rc = PcEntConv(atom, pcntEnt, pE) ;
                    if (atom.Type() != BASETYPE_UNDEF)
                    {
                        if (atom.IsSet())
                            C << atom.Str() ;
                    }
                    else
                    {
                        m_pLog->Log("Atom type unknown %s\n", *pcntEnt) ;
                        C << pcntEnt ;
                    }
                    zi += pcntEnt.Length() ;
                    continue ;
                }
            }
        }
        C.AddByte(*zi) ;
        zi++ ;
    }
    Z.Clear() ;
    Z = C ;
}
hzString    hdsApp::ConvertText (const hzString& str, hzHttpEvent* pE)
{
    //  Process the supplied data (assumed to be text) by replacing any incident percent entities by thier 'here and now' values. The data is supplied
    //  as a string which remains in situ and will be unchanged if there are no succesful percent entity conversions.
    //
    //  Arguments:  1)  S   Data to be treated as text which may contain one or more percent entities.
    //              2)  pE  The HTTP event from which the non system and non user variables can be garnered or from which the user can be established.
    //
    //  Returns:    None
    _hzfunc("hdsApp::ConvertText(2)") ;
    hzChain     Z ;     //  Temporary chain
    hzString    S ;     //  Resultant string
    Z = str ;
    ConvertText(Z, pE) ;
    S = Z ;
    return S ;
}
const char* Exec2Txt    (Exectype eType) ;
/*
**  Main Section: Request Processing
*/
hzEcode hdsApp::_SubscriberAuthenticate (hzHttpEvent* pE)
{
    //  Handle login submissions
    //
    //  Note all user are authenticated against the subscriber repository
    _hzfunc("hdsApp::_SubscriberAuthenticate") ;
    hdbObject       obj ;           //  Subscriber object
    hzAtom          atom ;          //  Atom for fetching values
    hdsInfo*        pInfo ;         //  Session
    hdbObjRepos*    pRepos ;        //  User repository
    hzSysID         newCookie ;     //  For cookie generation
    //hzString      subsUser ;      //  Username (from subscriber object)
    hzString        subsPass ;      //  Password (from subscriber object)
    hzString        unam ;          //  Username (from login form)
    hzString        pass ;          //  Password (from login form)
    hzString        S ;             //  Temp string
    hzString        utName ;        //  User type name
    hzSDate         expires ;       //  For cookie expiry
    hzIpaddr        ipa ;           //  Client IP address
    uint32_t        objId ;         //  For user lookup
    uint32_t        utAccess ;      //  User type access flags
    hzEcode         rc = E_OK ;     //  Return code
    pInfo = (hdsInfo*) pE->Session() ;
    ipa = pE->ClientIP() ;
    if (!pE->m_mapStrings.Exists(m_UsernameFld) || !pE->m_mapStrings.Exists(m_UserpassFld))
    {
        pE->m_appError = "Error: Form has login status but not the expected fields" ;
        m_pLog->Log("Error: Form has login status but not the expected fields (%s and %s)\n", *m_UsernameFld, *m_UserpassFld) ;
        return E_NOTFOUND ;
    }
    unam = pE->m_mapStrings[m_UsernameFld] ;
    pass = pE->m_mapStrings[m_UserpassFld] ;
    m_pLog->Log("username fld = %s\n", *m_UsernameFld) ;
    m_pLog->Log("userpass fld = %s\n", *m_UserpassFld) ;
    m_pLog->Log("Master user = %s\n", *m_MasterUser) ;
    m_pLog->Log("Master pass = %s\n", *m_MasterPass) ;
    m_pLog->Log("Trying username (%s) password (%s)\n", *unam, *pass) ;
    if (m_MasterPage && unam == m_MasterUser && pass == m_MasterPass)
    {
        //  Log user in as administrator with admin access rights 
        m_pLog->Log("ADMIN PAGE !!!\n") ;
        if (!pInfo)
        {
            newCookie = MakeCookie(ipa, pE->EventNo()) ;
            expires.SysDate() ;
            expires += 365 ;
            pE->SetSessCookie(newCookie) ;
            pInfo = new hdsInfo() ;
            pE->SetSession(pInfo) ;
            m_SessCookie.Insert(newCookie, pInfo) ;
        }
        pInfo->m_Access |= ACCESS_ADMIN ;
        if (!_hzGlobal_StatusIP.Exists(ipa))
            SetStatusIP(ipa, HZ_IPSTATUS_WHITE_HTTP, 900) ;
        m_MasterPage->Display(pE) ;
        return E_OK ;
    }
    //  The username (which could be an email address) is used to look up the user in the subscriber repository. The class of the actual user (if any) will be derived from this.
    atom = unam ;
    rc = m_ADP.m_pReposSubscriber->Exists(objId, m_ADP.m_pMbr_Subscriber_username, atom) ;
    m_pLog->Log("Login atempt %s=%s, obj=%d rc=%s\n", *m_UserpassFld, *pass, objId, Err2Txt(rc)) ;
    if (!objId)
        return rc ;
    obj.Init(m_ADP.m_pClassSubscriber) ;
    rc = m_ADP.m_pReposSubscriber->Fetch(obj, objId) ;
    if (rc != E_OK)
        m_pLog->Log("No match on subscriber. Fetchval returns %s\n", Err2Txt(rc)) ;
    m_pLog->Log("Trying %s=%s, obj=%d\n", *m_UserpassFld, *pass, objId) ;
    rc = obj.GetMbrValue(subsPass, m_ADP.m_pMbr_Subscriber_userpass) ;
    if (subsPass != pass)
    {
        m_pLog->Log("No match on stored %s to supplied %s\n", *subsPass, *pass) ;
        pE->m_appError = "Please check your username and password and try again" ;
        return E_NOTFOUND ;
    }
    //  Log user in as a user of the appropriate class
    if (!pInfo)
    {
        newCookie = MakeCookie(ipa, pE->EventNo()) ;
        expires.SysDate() ;
        expires += 365 ;
        pE->SetSessCookie(newCookie) ;
        pInfo = new hdsInfo() ;
        pE->SetSession(pInfo) ;
        m_SessCookie.Insert(newCookie, pInfo) ;
    }
    pInfo->m_SubId = objId ;
    m_SessCookie.Insert(pE->Cookie(), pInfo) ;
    rc = obj.GetMbrValue(S, m_ADP.m_pMbr_Subscriber_type) ;
    if (rc != E_OK)
        return hzerr(rc, "Could not fetch value for subscriber user type with object id %d\n", objId) ;
    if (S)
    {
        utAccess = m_UserTypes[S] ;
        pRepos = m_ADP.GetObjRepos(S) ;
        if (pRepos)
            pInfo->m_UserRepos = pRepos->DeltaId() ;
    }
    pInfo->m_Username = unam ;
    pInfo->m_UserId = objId ;
    pInfo->m_Access &= ACCESS_ADMIN ;
    pInfo->m_Access |= utAccess ;
    m_pLog->Log("Fetched value for subscriber address with object id %d %d usrType %s and access %d\n", objId, pInfo->m_UserId, *S, pInfo->m_Access) ;
    return rc ;
}
hzSysID hdsApp::MakeCookie  (const hzIpaddr& ipa, uint32_t eventNo)
{
    //  Category:   Internet
    //
    //  HadronZoo::Dissemino cookies have names based on fixed names Make a unique cookie for your internet aplication
    //
    //  Arguments:  1)  Cookie  The target where the cookie will be stored (hzString reference)
    //              2)  ipa     The browser ip address
    //              3)  appname The application name
    //
    //  Returns:    E_MEMORY    If there was insufficient memory to complete the operation
    //              E_OK        If the operation was successful
    _hzfunc(__func__) ;
    hzSysID     cookie ;        //  The cookie to be
    uint64_t    seed ;          //  The cookie value to be
    time_t      pt ;            //  Current system epoch time stamp
    seed = (uint64_t) 0 ;
    pt = time(&pt) ;
    seed = (pt << 32) ;
    seed &= 0xffffffff00000000 ;
    seed |= (uint32_t) ipa ;
    cookie = seed ;
    return cookie ;
}
void    hdsApp::ProcForm    (hzHttpEvent* pE, hdsFormref* pFormref, hdsFormhdl* pFhdl)
{
    //  Execute all commands specified within a form handler and formulate the HTTP response.
    //
    //  This runs each command in the form-handler, in order of appearence in the configs. Form handlers are invoked on form submission and in all cases, there must be a full page
    //  HTTP response. Forms are protected by form-validation JavaScript by default, but this can be by-passed by robots and is anyway limited. The data can be nonsense but if the
    //  format is correct, it is likely to be passed on to the form-handler.
    //
    //  Note that email validation can be inconclusive. Malformed email addresses are rejected in all cases, however other error conditions cannot always be detected. If the DNS is
    //  down, it will not be possible to determine if the domain name is invalid or has no destination mailservers. Even with the DNS up, the destination mailserver may be down, or
    //  it may reject the email for temporary reasons. The policy is to report invalid emails to the user, but only where they are proved invalid by means that are immeadiately to
    //  hand. In all other cases, emails that are not proved valid are assumed valid, so the rest of the form can be processed.
    //
    //  While command outcomes are limited to either a fail, a success or a presumed success, remedial action for a failure will depend on the command. In cases where the original
    //  form can be modified to correct the error, the HTTP response should be to re-display the original form. Where the error cannot be corrected by the user the response should
    //  be to ask the user to try again later. The way this is handled is that all commands should be supplied with an error directive. At the end of the form-handler a <response>
    //  tag must be supplied to specify the HTTP response on success.
    //
    //  The responses must be a full page, either an actual PAGE or a C-Interface FUNCTION that generates a page. The response cannot be an article (a partial page).
    //
    //  Arguments:  1)  pE          The HTTP event pointer
    //              2)  pFormref    Form reference
    //              3)  pFhdl       Form handler
    //
    //  Returns:    None
    _hzfunc("hdsApp::ProcForm") ;
    hzList<hdsExec*>::Iter  ei ;    //  Command iterator
    hzChain         Z ;             //  For formulating response
    hzChain         errorReport ;   //  For formulating response
    hzPair          P ;             //  For checking presence of submitted data
    hdsInfo*        pInfo ;         //  Session
    hdsFormdef*     pFormdef ;      //  Form
    hdsField*       pFld ;          //  Field pointer
    hdsExec*        pExec ;         //  Exec function
    hdsResource*    pResponse ;     //  Resource to respond with
    hdsPage*        pPage = 0 ;     //  Page to respond with
    hdsCIFunc*      pFunc = 0 ;     //  C-Interface function to respond with
    hzAtom          atom ;          //  Atom for fetching values
    hzSysID         newCookie ;     //  For cookie generation
    hzXDate         now ;           //  Time now
    hzString        S ;             //  Temp string
    hzString        unam ;          //  Username (if form is login)
    hzString        pass ;          //  Password (if form is login)
    hzIpaddr        ipa ;           //  Client IP address
    hzSDate         expires ;       //  For cookie expiry
    uint32_t        nIndex ;        //  Name value pair iterator
    //hdsUsertype       usrType ;       //  User type (for loging in)
    hzEcode         rc = E_OK ;     //  Return code
    pInfo = (hdsInfo*) pE->Session() ;
    ipa = pE->ClientIP() ;
    now.SysDateTime() ;
    //  Check for form reference and form handler
    if (!pFormref)  Fatal("No form reference supplied") ;
    if (!pFhdl)     Fatal("No form handler supplied") ;
    m_pLog->Log("Processing form reference %p flags %x handler %p and info %p\n", pFormref, pFormref->m_flagVE, pFhdl, pInfo) ;
    pFormdef = pFhdl->m_pFormdef ;
    if (!pFormdef)
        Fatal("Form handler (%s) has no form", *pFhdl->m_Refname) ;
    m_pLog->Log("Processing form reference %p and info %p\n", pFormref, pInfo) ;
    m_pLog->Log("Processing form %p %s with %d fields and info %p\n", pFormdef, *pFormdef->m_Formname, pFormdef->m_vecFlds.Count(), pInfo) ;
    pE->m_pContextForm = pFormref ;
    //  Check if there is input
    if (!pE->Inputs())
    {
        rc = E_NODATA ;
        m_pLog->Log("Form has no data\n") ;
        goto respond ;
    }
    //  Check if input actually fits the expected format. Each field in the form ...
    for (nIndex = 0 ; nIndex < pE->m_Inputs.Count() ; nIndex++)
    {
        P = pE->m_Inputs[nIndex] ;
            m_pLog->Log("Doing field %s %s\n", *P.name, *P.value) ;
        pFld = pFormdef->m_mapFlds[P.name] ;
        if (!pFld)
        {
            m_pLog->Log("No such field as %s\n", *P.name) ;
            rc = E_NOTFOUND ;
        }
    }
    if (rc != E_OK)
        goto respond ;
    //  for (nIndex = 0 ; nIndex < pFormdef->m_mapFlds.Count() ; nIndex++)
    //  {
    //      pFld = pFormdef->m_mapFlds.GetObj(nIndex) ;
    //  }
    //  See if a session is needed
    if (pFhdl->m_flgFH & VE_COOKIES)
    {
        if (!pInfo)
        {
            newCookie = MakeCookie(ipa, pE->EventNo()) ;
            expires.SysDate() ;
            expires += 365 ;
            pE->SetSessCookie(newCookie) ;
            pInfo = new hdsInfo() ;
            pE->SetSession(pInfo) ;
            m_SessCookie.Insert(newCookie, pInfo) ;
            m_pLog->Log("New cookie %016x and session %p\n", *now, newCookie, pInfo) ;
        }
    }
    //  Perform exec functions
    for (ei = pFhdl->m_Exec ; ei.Valid() ; ei++)
    {
        m_pLog->Log("CASE 5\n") ;
        pExec = ei.Element() ;
        if (!pExec)
        {
            m_pLog->Log("ERROR. Null exec at posn %d\n", nIndex) ;
            continue ;
        }
        m_pLog->Log("HAVE EXEC %s\n", Exec2Txt(pExec->m_Command)) ;
        rc = pExec->Exec(errorReport, pE) ;
        m_pLog->Out(errorReport) ;
        errorReport.Clear() ;
        if (rc == E_OK)
        {
            m_pLog->Log("Sucess exec %s at posn %d (err=%s)\n", Exec2Txt(pExec->Whatami()), nIndex, Err2Txt(rc)) ;
            continue ;
        }
        //  Failed
        m_pLog->Log("Failed exec %s at posn %d (err=%s)\n", Exec2Txt(pExec->Whatami()), nIndex, Err2Txt(rc)) ;
        if (pExec->m_FailGoto || pExec->m_pFailResponse)
        {
            //  The exec step has a fail directive
            if (errorReport.Size())
                pE->m_appError = errorReport ;
            if (pExec->m_pFailResponse)
                pResponse = pExec->m_pFailResponse ;
            else
            {
                if (pExec->m_FailGoto[0] == CHAR_PERCENT)
                {
                    S = ConvertText(pExec->m_FailGoto, pE) ;
                    m_pLog->Log("Converted error page directive %s to %s\n", *pExec->m_FailGoto, *S) ;
                    pResponse = m_ResourcesName[S] ;
                    if (!pResponse)
                        pResponse = m_ResourcesPath[S] ;
                }
                else
                {
                    m_pLog->Log("Using named error page %s\n", *pExec->m_FailGoto) ;
                    pResponse = m_ResourcesName[pExec->m_FailGoto] ;
                    if (!pResponse)
                        pResponse = m_ResourcesPath[pExec->m_FailGoto] ;
                }
            }
            if (!pResponse)
                SendErrorPage(pE, HTTPMSG_OK, *_fn, "Could not find an error page for exec %s in form %s", Exec2Txt(pExec->Whatami()), *pFhdl->m_Refname) ;
            else
            {
                pPage = dynamic_cast<hdsPage*>(pExec->m_pFailResponse) ;
                if (pPage)
                {
                    m_pLog->Log("Using error page %s\n", *pPage->m_Title) ;
                    pPage->Display(pE) ;
                }
                else
                {
                    pFunc = dynamic_cast<hdsCIFunc*>(pResponse) ;
                    pFunc->m_pFunc(pE) ;
                }
            }
            return ;
        }
    }
    //  Formulate response. This is a case of running the process associated with form and/or the button used to submit the form. And
    //  then either presenting the results or going to a specified page.
respond:
    if (rc == E_OK)
    {
        if (pFhdl->m_pCompletePage)
            pResponse = pFhdl->m_pCompletePage ;
        else
        {
            if (pFhdl->m_CompleteGoto[0] == CHAR_PERCENT)
            {
                S = ConvertText(pFhdl->m_CompleteGoto, pE) ;
                m_pLog->Log("Converted response page directive %s to %s\n", *pFhdl->m_CompleteGoto, *S) ;
                pResponse = m_ResourcesName[S] ;
                if (!pResponse)
                    pResponse = m_ResourcesPath[S] ;
            }
            else
            {
                m_pLog->Log("Using named response page %s\n", *pFhdl->m_CompleteGoto) ;
                pResponse = m_ResourcesName[pFhdl->m_CompleteGoto] ;
                if (!pResponse)
                    pResponse = m_ResourcesPath[pFhdl->m_CompleteGoto] ;
            }
        }
        if (!pResponse)
            SendErrorPage(pE, HTTPMSG_NOTFOUND, *_fn, "Could not find a response page for form %s", *pFhdl->m_Refname) ;
        else
        {
            pPage = dynamic_cast<hdsPage*>(pResponse) ;
            if (pPage)
            {
                m_pLog->Log("Using response page %s\n", *pPage->m_Title) ;
                pPage->Display(pE) ;
            }
            else
            {
                pFunc = dynamic_cast<hdsCIFunc*>(pResponse) ;
                pFunc->m_pFunc(pE) ;
            }
        }
    }
    else
    {
        if (errorReport.Size())
            pE->m_appError = errorReport ;
        if (pFhdl->m_pFailDfltPage)
            pResponse = pFhdl->m_pFailDfltPage ;
        else
        {
            if (pFhdl->m_FailDfltGoto[0] == CHAR_PERCENT)
            {
                S = ConvertText(pFhdl->m_FailDfltGoto, pE) ;
                m_pLog->Log("Converted error page directive %s to %s\n", *pFhdl->m_FailDfltGoto, *S) ;
                pResponse = m_ResourcesName[S] ;
                if (!pResponse)
                    pResponse = m_ResourcesPath[S] ;
            }
            else
            {
                m_pLog->Log("Using named error page %s\n", *pFhdl->m_FailDfltGoto) ;
                pResponse = m_ResourcesName[pFhdl->m_FailDfltGoto] ;
                if (!pResponse)
                    pResponse = m_ResourcesPath[pFhdl->m_FailDfltGoto] ;
            }
        }
        if (!pResponse)
            SendErrorPage(pE, HTTPMSG_OK, *_fn, "Could not find an error page for form %s", *pFhdl->m_Refname) ;
        else
        {
            pPage = dynamic_cast<hdsPage*>(pFhdl->m_pFailDfltPage) ;
            if (pPage)
            {
                m_pLog->Log("Using error page %s\n", *pPage->m_Title) ;
                pPage->Display(pE) ;
            }
            else
            {
                pFunc = dynamic_cast<hdsCIFunc*>(pResponse) ;
                pFunc->m_pFunc(pE) ;
            }
        }
    }
    m_pLog->Log("Processed form %p %s with %d fields and info %p\n", pFormdef, *pFormdef->m_Formname, pFormdef->m_vecFlds.Count(), pInfo) ;
}
void    hdsApp::InPageQuery (hzHttpEvent* pE)
{
    //  Respond to an in-page query (Dissemino AJAX)
    //
    //  An in-page query is an AJAX request to a URL of the form '/ck-repos.member.value' - to which the response is a blank page with a HTTP return code of either 200 (OK) or 404
    //  (NOTFOUND). A code of 200 indicates that the repository exists, is populated and is of a class which possesses the named member.
    //
    //  The purpose of an in-page query is to support form validation. Fields which must contain a unique value such as an email address or a username (a common requirement in site
    //  membership application/registration forms), can be validated by these AJAX calls. 
    //
    //  Argument:   pE  The HTTP event pointer
    //
    //  Returns:    None
    _hzfunc("hdsApp::InPageQuery") ;
    hzChain         Z ;             //  For arg processing
    hzAtom          atom ;          //  Need for Exists()
    hdbObjRepos*    pRepos ;        //  Use class (of desired document)
    const char*     r ;             //  Pointer into event resource
    hzString        S ;             //  Temp string
    hzString        val ;           //  Value
    uint32_t        objId ;         //  Object found
    hzEcode         rc = E_OK ;     //  Return code
    //  Get class/cache name
    r = pE->GetResource() ;
    for (r += 4 ; IsAlpha(*r) ; r++)
        Z.AddByte(*r) ;
    S = Z ;
    Z.Clear() ;
    pRepos = m_ADP.GetObjRepos(S) ;
    if (!pRepos)
        { rc = E_NOTFOUND ; goto fail ; }
    if (*r != CHAR_PERIOD)
        { rc = E_FORMAT ; goto fail ; }
    //  Get member name & value
    for (r++ ; IsAlpha(*r) ; r++)
        Z.AddByte(*r) ;
    S = Z ;
    Z.Clear() ;
    if (*r != CHAR_PERIOD)
        { rc = E_FORMAT ; goto fail ; }
    val = ++r ;
    atom = val ;
    m_pLog->Out("Doing Binary fetch on %s %s\n", *S, *val) ;
    rc = pRepos->Exists(objId, pRepos->Class()->GetMember(S), atom) ;
    if (rc != E_OK)
        { m_pLog->Out("Binary fech function failed. Err=%s\n", Err2Txt(rc)) ; goto fail ; }
    //  Serve it
    m_pLog->Out("%s. Located record %d\n", __func__, objId) ;
    if (objId)
    {
        pE->SendAjaxResult(HTTPMSG_OK) ;
        return ;
    }
fail:
    pE->SendAjaxResult(HTTPMSG_NOTFOUND) ;
    return ;
}
#if 0
void    hdsApp::SendDocument    (hzHttpEvent* pE)
{
    //  Send a document to the client browser.
    //
    //  This function generates HTTP responses to requests for URLs begining with '/docs'. This path is reserved by Dissemino for documents or other binary objects, so it is not an
    //  actual directory. The response will incorporate the document if found, or be a 404 page if not.
    //
    //  The target document will in all cases, be stored in a binary datum repository and have a datum id. In the webapp however, ... Binary datum are not general form of the URL is '/docs/repository.member.objid' as this will uniquely identify the document. This function will produce
    //  a 404 HTML response if the remainder of the URL is malformed, the repository name does not exist as a repository, the named member does not exist within
    //  the repository class or is not of a document or binary datatype or the given object id is not found in the repository.
    //
    //  Arguments:  1)  pE  The HTTP event pointer
    //
    //  Returns:    None
    _hzfunc("hdsApp::SendDocument") ;
    hzChain         E ;             //  Error report
    hzChain         X ;             //  Recepticle
    hdbObject       obj ;           //  Data object from applicable repos
    hdsInfo*        pInfo = 0 ;     //  Session
    hdbObjRepos*    pRepos ;        //  Use class (of desired document)
    const char*     r ;             //  Pointer into event resource
    hzString        S ;             //  Temp string
    uint32_t        docid = 0 ;     //  Object ID
    hzMimetype      mt ;            //  Mimetype
    hzEcode         rc = E_OK ;     //  Return codee
    pInfo = m_SessCookie[pE->Cookie()] ;
    //  Get class/cache name
    r = pE->GetResource() ;
    for (r += 6 ; IsAlpha(*r) ; r++)
        X.AddByte(*r) ;
    S = X ;
    X.Clear() ;
    pRepos = m_ADP.GetObjRepos(S) ;
    if (!pRepos)
        { E.Printf("Dissemino could not locate the document cache") ; goto fail ; }
    if (*r != CHAR_PERIOD)
        { E.Printf("Bad format for document request") ; goto fail ; }
    //  Get member name
    for (r++ ; IsAlpha(*r) ; r++)
        X.AddByte(*r) ;
    S = X ;
    E.Clear() ;
    if (*r != CHAR_PERIOD)
        { E.Printf("Bad format for document request") ; goto fail ; }
    for (r++ ; IsDigit(*r) ; r++)
        { docid *= 10 ; docid += (*r - '0') ; }
    if (!docid)
        { E.Printf("Invalid document ID") ; goto fail ; }
    m_pLog->Out("%s. Class %s, member %s, docid %d\n", __func__, pRepos->txtName(), *S, docid) ;
    //  Get the document/binary
    if (pInfo->m_UserRepos != pRepos->DeltaId())
        { E.Printf("User permission failure. Err=%s\n", Err2Txt(rc)) ; goto fail ; }
    obj.Init(pRepos->Class()) ;
    rc = pRepos->Fetch(obj, S, docid) ;
    rc = pRepos->Fetchbin(X, S, docid) ;
    if (rc != E_OK)
        { E.Printf("Binary fech function failed. Err=%s\n", Err2Txt(rc)) ; goto fail ; }
    //  Serve it
    rc = pE->SendRawChain(HTTPMSG_OK, mt, X, 90, false) ;
    return ;
fail:
    S = E ;
    SendErrorPage(pE, HTTPMSG_NOTFOUND, __func__, *S) ;
    return ;
}
#endif
/*
**  Common URL request strings
*/
//  Master presets
const hzString  preset_master_proc_auth     = "/master" ;       //  Fast compare for master proc auth
//  const hzString  preset_masterResList        = "/masterResList" ;        //  Fast compare for master Resources list
//  const hzString  preset_masterVisList        = "/masterVisList" ;        //  Fast compare for master Visitors list
//  const hzString  preset_masterBanned         = "/masterBanned" ;         //  Fast compare for master Blacklist
//  const hzString  preset_masterDomain         = "/masterDomain" ;         //  Fast compare for master String list
//  const hzString  preset_masterEmaddr         = "/masterEmaddr" ;         //  Fast compare for master String list
//  const hzString  preset_masterStrFix         = "/masterStrFix" ;         //  Fast compare for master String list
//  const hzString  preset_masterStrGen         = "/masterStrGen" ;         //  Fast compare for master String list
//  const hzString  preset_masterFileList       = "/masterFileList" ;       //  Fast compare for master Config ile list
const hzString  preset_masterFileEdit       = "/masterFileEdit" ;       //  Fast compare for master edit resource page
const hzString  preset_masterFileEdit_hdl   = "/masterFileEditHdl" ;    //  Fast compare for master edit resource page
//  const hzString  preset_masterCfgList        = "/masterCfgList" ;        //  Fast compare for master list resource page
//  const hzString  preset_masterCfgEdit        = "/masterCfgEdit" ;        //  Fast compare for master edit resource page
const hzString  preset_masterCfgEdit_hdl1   = "/masterCfgEditHdl1" ;    //  Fast compare for master edit resource page
//  const hzString  preset_masterCfgEdit_hdl2   = "/masterCfgEditHdl2" ;    //  Fast compare for master edit resource page
//  const hzString  preset_masterCfgEdit_hdl3   = "/masterCfgEditHdl3" ;    //  Fast compare for master edit resource page
//  Non-master presets
const hzString  preset_usr_proc_auth    = "/usrProcAuth" ;      //  Fast compare for user proc auth
const hzString  preset_jsc              = "jsc" ;               //  Fast compare for javascript location "jsc"
const hzString  preset_js               = "js" ;                //  Fast compare for javascript location "js"
const hzString  preset_img              = "img" ;               //  Fast compare for images location "img"
const hzString  preset_sysimg           = "sysimg" ;            //  Fast compare for images location "img"
const hzString  preset_userdir          = "userdir" ;           //  Fast compare for user directory keyword "userdir"
const hzString  preset_textpg           = "textpg" ;            //  Fast compare for common literal "textpg"
const hzString  preset_docs             = "docs" ;              //  Fast compare for document location "docs"
const hzString  preset_robots           = "/robots.txt" ;       //  Fast compare for common literal "/robots.txt"
const hzString  preset_favicon          = "/favicon.ico" ;      //  Fast compare for common literal "/favicon.ico"
const hzString  preset_index            = "/index.html" ;       //  Fast compare for common literal "/index.html"
const hzString  preset_sitemap_txt      = "/sitemap.txt" ;      //  Fast compare for common literal "/sitemap.txt"
const hzString  preset_sitemap_xml      = "/sitemap.xml" ;      //  Fast compare for common literal "/sitemap.xml"
const hzString  preset_siteguide        = "/siteguide.html" ;   //  Fast compare for common literal "/siteguide.html"
const hzString  gen_noref               = "no-ref" ;            //  Fast compare for common literal "no-ref"
hzEcode hdsArticleCIF::Run  (hzHttpEvent* pE)
{
    //  Runs the C-Interface article function
    //
    //  Arguments:  1)  pE  The HTTP event pointer
    //
    //  Returns:    Enum TCP return code
    _hzfunc("hdsArticleCIF::Run") ;
    hzChain         Z ;     //  Outgoing chain
    if (!this)
        Fatal("No C-Interface Article") ;
    m_pFunc(Z, this, pE) ;
    pE->m_Error.Printf("AJAX case 8\n") ;
    return pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_HTML, Z, 0 /*43200*/, false) ;
}
hzTcpCode   hdsApp::ProcHTTP    (hzHttpEvent* pE)
{
    //  Process all HTTP requests and form submissions. 
    //
    //  Arguments:  1)  pE  The HTTP event pointer
    //
    //  Returns:    Enum TCP return code
    _hzfunc("hdsApp::ProcHTTP") ;
    hzChain         Z ;                 //  Outgoing chain
    hzChain         err ;               //  Error report chain
    hzIpConnex*     pConnex ;           //  Client Connection
    hdsProfile*     pVP ;               //  Visitor profle
    hzAtom          sv ;                //  Session value
    hdsInfo*        pInfo = 0 ;         //  Session
    hdsLang*        pLang ;             //  Language selected
    hdsResource*    pResource ;         //  Resource to respond with
    hdsFile*        pFix ;              //  Page to generate
    hdsPage*        pPage ;             //  Page to generate
    hdsCIFunc*      pCIF ;              //  C-Interface function
    hdsFormref*     pFormref ;          //  Form reference
    hdsFormhdl*     pFhdl ;             //  Form handler
    hdsNavtree*     pAG = 0 ;           //  Article group
    hdsArticle*     pArt = 0 ;          //  Generic article
    hdsArticleStd*  pArtStd = 0 ;       //  Standard article
    hdsArticleCIF*  pArtCIF = 0 ;       //  C-Interface article
    const char*     pReq ;              //  Requested resource
    const char*     i ;                 //  Resource iterator
    hzIpinfo        ipi ;               //  IP test address (blocking)
    hzSysID         cookie ;            //  The cookie
    hzString        iplocn ;            //  IP location of client
    hzString        currRef ;           //  Referer
    hzString        reqPath ;           //  Requested page/resource
    hzString        argA ;              //  1st argument (e.g. lang subdir or article group name)
    hzString        argB ;              //  2nd argument (e.g. article name)
    hzString        hname ;             //  Form handler name
    uint32_t        n ;                 //  Resource iterator limit
    uint32_t        nAgt ;              //  User agent number
    hzIpaddr        ipa ;               //  Client IP address
    hzEcode         rc ;                //  Function returns codes
    hzTcpCode       trc ;               //  TCP return code (of this function)
    char            argbuf[100] ;       //  For language codes
    //  No hdsApp instance?
    if (!this)
        Fatal("No Dissemino Application") ;
    pConnex = pE->GetConnex() ;
    if (!pConnex)
        Fatal("No Client Connection") ;
    //  Check request method is valid
    rc = E_FORMAT ;
    switch  (pE->Method())
    {
    case HTTP_GET:      rc = E_OK ; break ;
    case HTTP_HEAD:     rc = E_OK ; break ;
    case HTTP_POST:     rc = E_OK ; break ;
    case HTTP_OPTIONS:  err.Printf("START ProcHTTP (OPTIONS) %s\n", pE->Hostname()) ;   break ;
    case HTTP_PUT:      err.Printf("START ProcHTTP (PUT) %s\n", pE->Hostname()) ;       break ;
    case HTTP_DELETE:   err.Printf("START ProcHTTP (DELETE) %s\n", pE->Hostname()) ;    break ;
    case HTTP_TRACE:    err.Printf("START ProcHTTP (TRACE) %s\n", pE->Hostname()) ;     break ;
    case HTTP_CONNECT:  err.Printf("START ProcHTTP (CONNECT) %s\n", pE->Hostname()) ;   break ;
    case HTTP_INVALID:  err.Printf("START ProcHTTP (INVALID) %s\n", pE->Hostname()) ;   break ;
    default:    err.Printf("START ProcHTTP (INVALID) %s\n", pE->Hostname()) ;   break ;
    }
    //  Check IP address is valid
    ipa = pE->ClientIP() ;
    if (ipa == IPADDR_NULL || ipa == IPADDR_BAD)
    {
        err.Printf("IP Address of %s\n", *ipa) ;
        threadLog(err) ;
        SendErrorPage(pE, HTTPMSG_INTERNAL_SERVER_ERROR, __func__, "Could not dechiper IP address\n") ;
        pConnex->SendKill() ;
        return TCP_TERMINATE ;
    }
    //  Check IP address status
    if (_hzGlobal_StatusIP.Exists(ipa))
    {
        ipi = _hzGlobal_StatusIP[ipa] ;
        if (ipi.m_bInfo & HZ_IPSTATUS_BLACK_HTTP)
        {
            //  Banned IP? Kill connection
            err.Printf("BLOCKED IP ADDRESS %s - Killing Connection\n", *ipa) ;
            threadLog(err) ;
            pConnex->SendKill() ;
            return TCP_TERMINATE ;
        }
    }
    if (rc != E_OK)
    {
        err.Printf("BLOCKING IP ADDRESS %s - Killing Connection\n", *ipa) ;
        threadLog(err) ;
        SetStatusIP(ipa, HZ_IPSTATUS_BLACK_PROT, 9000) ;
        pConnex->SendKill() ;
        return TCP_TERMINATE ;
    }
    //  Get location from IP address
    iplocn = GetIpLocation(ipa) ;
    pE->m_pContextLang = pLang = m_pDfltLang ;
    //  Get keep-alive status, requested resource, cookie etc
    trc = pE->Connection() ? TCP_KEEPALIVE : TCP_TERMINATE ;
    pReq = pE->GetResource() ;
    currRef = pE->Referer() ;
    if (!pE->Cookie())
        pE->m_Report.Printf("Supplied Cookie: [0] info 0\n") ;
    else
    {
        //  Cookie supplied so get the session
        pInfo = m_SessCookie[pE->Cookie()] ;
        if (!pInfo)
        {
            pE->m_Report.Printf("Supplied Cookie: [%016X] is dead\n", pE->Cookie()) ;
            pE->DelSessCookie(pE->Cookie()) ;
        }
        else
        {
            pE->m_Report.Printf("Supplied Cookie: [%016X] info %p\n", pE->Cookie(), pInfo) ;
            pE->SetSession(pInfo) ;
            pE->m_Report.Printf("Access:        %d\n", pInfo->m_Access) ;
            pE->m_Report.Printf("Subscriber ID: %d\n", pInfo->m_SubId) ;
            pE->m_Report.Printf("User ID:       %d\n", pInfo->m_UserId) ;
            pE->m_Report.Printf("Curr Object:   %d\n", pInfo->m_CurrObj) ;
            for (n = 0 ; n < pInfo->m_Sessvals.Count() ; n++)
            {
                pE->m_Report.Printf("Sesion var: %s=%s\n", *pInfo->m_Sessvals.GetKey(n), *pInfo->m_Sessvals.GetObj(n).Str()) ;
            }
        }
        threadLog(pE->m_Report) ;
        pE->m_Report.Clear() ;
    }
    //  Check user agent
    if (pE->UserAgent())
    {
        if (m_UserAgents.Exists(pE->UserAgent()))
            nAgt = m_UserAgents[pE->UserAgent()] ;
        else
        {
            nAgt = m_UserAgents.Count() + 1 ;
            m_UserAgents.Insert(pE->UserAgent(), nAgt) ;
        }
    }
    //  Obtain basic profile on visitor from IP address
    pVP = m_Visitors[ipa] ;
    if (!pVP)
    {
        pVP = new hdsProfile() ;
        pVP->m_addr = ipa ;
        m_Visitors.Insert(ipa, pVP) ;
    }
    //  POST Method
    if (pE->Method() == HTTP_POST)
    {
        reqPath = pReq ;
        //  No user session?
        if (!pInfo)
        {
            //  Login page request?
            if (reqPath == m_LoginPost)
            {
                rc = _SubscriberAuthenticate(pE) ;
                if (rc == E_OK)
                    reqPath = m_LoginAuth ;
                else
                    reqPath = m_LoginFail ;
                goto proc_std_request ;
            }
        }
        //  Admin session?
        if (pInfo && pInfo->m_Access & ACCESS_ADMIN)
        {
            if (reqPath == preset_masterFileEdit_hdl)   { _masterFileEditHdl(pE) ; return trc ; }
            if (reqPath == preset_masterCfgEdit_hdl1)   { _masterCfgEditHdl(pE) ; return trc ; }
        }
        if (reqPath == preset_master_proc_auth && !pInfo)
            { rc = _masterProcAuth(pE) ; return trc ; }
        if (reqPath == preset_usr_proc_auth && !pInfo)
            { rc = _SubscriberAuthenticate(pE) ; return trc ; }
        //  Obtain form reference and form handler name from the submission URL, then obtain form handler from form handler name.
        pFormref = m_FormUrl2Ref[reqPath] ;
        hname = m_FormUrl2Hdl[reqPath] ;
        pFhdl = m_FormHdls[hname] ;
        //  No form handler?
        if (!pFhdl)
        {
            pVP->m_P404++ ;
            SendErrorPage(pE, HTTPMSG_NOTFOUND, __func__, "No such form handler as %s", *reqPath) ;
            if (pVP->m_P404 > 20)
            {
                pE->m_Report.Printf("Note: Blocking IP %s for illegal POST requests\n", *ipa) ;
                SetStatusIP(ipa, HZ_IPSTATUS_BLACK_PROT, 0) ;
            }
        }
        else
        {
            pVP->m_post++ ;
            ProcForm(pE, pFormref, pFhdl) ;
        }
        pE->m_Report.Printf("\n%s POST: %s (%s, %s) ", *pE->m_Occur, *ipa, *iplocn, *m_pDfltLang->m_Code) ;
        pE->m_Report.Printf("[cook=%016X info=%p] (%s) %s\n", pE->Cookie(), pInfo, *currRef, pE->GetResource()) ;
        pE->m_Report.Printf("<rbot=%d fav=%d art=%d, pg=%d scr=%d img=%d sp=%d fix=%d post=%d G404=%d P404=%d>\n",
            pVP->m_robot, pVP->m_favicon, pVP->m_art, pVP->m_page, pVP->m_scr, pVP->m_img, pVP->m_spec, pVP->m_fix, pVP->m_post, pVP->m_G404, pVP->m_P404) ;
        threadLog(pE->m_Report) ;
        pE->m_Report.Clear() ;
        return trc ;
    }
    /*
    **  Deal with GET and HEAD method requests: All mainstrean resource requests.
    */
    //  Not HTTP_GET or HTTP_HEAD?
    if (pE->Method() != HTTP_GET && pE->Method() != HTTP_HEAD)
    {
        //  This can't happen!
        SendErrorPage(pE, HTTPMSG_METHOD_NOT_ALLOWED, __func__, "Method not supported") ;
        return TCP_TERMINATE ;
    }
    //  Robot, favicon or sitemap?
    if (preset_robots == pE->GetResource())
    {
        rc = pE->SendRawString(HTTPMSG_OK, HMTYPE_TXT_PLAIN, m_Robot, 0 /*43200*/, false) ;
        if (rc != E_OK)
            pE->m_Report << pE->m_Error ;
        pVP->m_robot++ ;
        goto get_end ;
    }
    //  Favicon reqested?
    if (preset_favicon == pE->GetResource())
    {
        rc = pE->SendPageE(*m_Images, pReq + 1, 86400, false) ;
        if (rc != E_OK)
            pE->m_Report << pE->m_Error ;
        pVP->m_favicon++ ;
        goto get_end ;
    }
    //  Text sitemap requested?
    if (preset_sitemap_txt == pE->GetResource())
    {
        pVP->m_spec++ ;
        if (!m_rawSitemapTxt.Size())
            SendErrorPage(pE, HTTPMSG_NOTFOUND, __func__, "No such page as %s", pE->GetResource()) ;
        else
        {
            if (pE->Zipped() && m_zipSitemapTxt.Size())
                rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_PLAIN, m_zipSitemapTxt, 0, true) ;
            else
                rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_PLAIN, m_rawSitemapTxt, 0, false) ;
        }
        goto get_end ;
    }
    //  XML sitemap requested?
    if (preset_sitemap_xml == pE->GetResource())
    {
        pVP->m_spec++ ;
        if (!m_rawSitemapTxt.Size())
            SendErrorPage(pE, HTTPMSG_NOTFOUND, __func__, "No such page as %s", pE->GetResource()) ;
        else
        {
            if (pE->Zipped() && m_zipSitemapXml.Size())
                rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_APP_XML, m_zipSitemapXml, 0 /*43200*/, true) ;
            else
                rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_APP_XML, m_rawSitemapXml, 0 /*43200*/, false) ;
        }
        goto get_end ;
    }
    //  Site guide requested?
    if (preset_siteguide == pE->GetResource())
    {
        pVP->m_spec++ ;
        if (!m_rawSiteguide.Size())
            SendErrorPage(pE, HTTPMSG_NOTFOUND, __func__, "No such page as %s", pE->GetResource()) ;
        else
        {
            if (pE->Zipped() && m_zipSiteguide.Size())
                rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_HTML, m_zipSiteguide, 0 /*43200*/, true) ;
            else
                rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_HTML, m_rawSiteguide, 0 /*43200*/, false) ;
        }
        goto get_end ;
    }
    //  Master page request?
    if (pInfo && pInfo->m_Access & ACCESS_ADMIN)
    {
        if (reqPath == "/masterLogout")         { rc = _masterLogout(pE) ; goto get_end ; }
        if (!memcmp(pReq, "/masterAction", 13))
        {
            MasterArticle(pE) ;
            goto get_end ;
        }
    }
    /*
    if (pInfo && pInfo->m_Access & ACCESS_ADMIN)
    {
        if (pReq[1] == 'm' && pReq[2] == 'a' && pReq[3] == 's' && pReq[4] == 't' && pReq[5] == 'e' && pReq[6] == 'r')
        {
            reqPath = pReq ;    //+ 1 ;
            if (reqPath == "/masterMainMenu")   { rc = _masterMainMenu(pE) ;    goto get_end ; }
            if (reqPath == "/masterCfgList")    { rc = _masterCfgList(pE) ;     goto get_end ; }
            if (reqPath == "/masterCfgEdit")    { rc = _masterCfgEdit(pE) ;     goto get_end ; }
            if (reqPath == "/masterResList")    { rc = _masterResList(pE) ;     goto get_end ; }
            if (reqPath == "/masterVisList")    { rc = _masterVisList(pE) ;     goto get_end ; }
            if (reqPath == "/masterDomain")     { rc = _masterDomain(pE) ;      goto get_end ; }
            if (reqPath == "/masterEmaddr")     { rc = _masterEmaddr(pE) ;      goto get_end ; }
            if (reqPath == "/masterStrFix")     { rc = _masterStrFix(pE) ;      goto get_end ; }
            if (reqPath == "/masterStrGen")     { rc = _masterStrGen(pE) ;      goto get_end ; }
            if (reqPath == "/masterBanned")     { rc = _masterBanned(pE) ;      goto get_end ; }
            if (reqPath == "/masterMemstat")    { rc = _masterMemstat(pE) ;     goto get_end ; }
            if (reqPath == "/masterUSL")        { rc = _masterUSL(pE) ;         goto get_end ; }
            if (reqPath == "/masterFileList")   { rc = _masterFileList(pE) ;    goto get_end ; }
            if (reqPath == "/masterFileEdit")   { rc = _masterFileEdit(pE) ;    goto get_end ; }
            if (reqPath == "/masterDataModel")  { rc = _masterDataModel(pE) ;   goto get_end ; }
            if (reqPath == "/masterCfgRestart") { rc = _masterCfgRestart(pE) ;  goto get_end ; }
            if (reqPath == "/masterLogout")     { rc = _masterLogout(pE) ;      goto get_end ; }
        }
        //  Master article - should this not simply be get article???
        if (!memcmp(pReq, "/master_", 8))
            { MasterArticle(pE) ; goto get_end ; }
    }
    */
    /*
    **  Check for AJAX requests. These will not be the only source of requests in which there is one name-value pair submitted in the query part of the request URL, but requests to
    **  check a member value for availabilty (e.g. email address) and requests for articles are of this form. If the request is not for any of these, then drop through. 
    */
    //  In-page query?
    if (pE->QueryLen() && pE->m_mapStrings.Count() == 1)
    {
        //  Check for an in-page value query on a class member
        if (!memcmp(pReq, "/ck-", 4))
        {
            InPageQuery(pE) ;
            goto get_end ;
        }
        //  What follows must be an article request of the form "/article_group-article_refname"
        argA = pE->m_mapStrings.GetKey(0) ;
        argB = pE->m_mapStrings.GetObj(0) ;
        //  Check if the article group is public
        pAG = m_ArticleGroups[argA] ;
        if (!pAG)
        {
            //  Check if the request is for an article in a tree vested with the user session
            if (pInfo && pInfo->m_pTree && pInfo->m_pTree->m_Groupname == argA)
                pAG = pInfo->m_pTree ;
            if (!pAG)
            {
                threadLog("No Article Group Found\n") ;
                pE->SendAjaxResult(HTTPMSG_NOTFOUND, "Out of Range") ;
                goto get_end ;
            }
        }
        threadLog("Located Article Group %s\n", *pAG->m_Groupname) ;
        //  Have group, get article
        pArt = (hdsArticle*) pAG->GetItem(argB) ;
        if (!pArt)
        {
            //  Only remaining option is the default article group function, if this is provided.
            if (pAG->m_pFunc)
                pAG->m_pFunc(pE, argB) ;
            else
                pE->SendAjaxResult(HTTPMSG_NOTFOUND, "Out of Range") ;
            goto get_end ;
        }
        //  User has access?
        if (!(pArt->m_resAccess == ACCESS_PUBLIC
            || (pArt->m_resAccess == ACCESS_NOBODY && (!pInfo || !(pInfo->m_Access & ACCESS_MASK)))
            || (pInfo && (pInfo->m_Access & ACCESS_ADMIN || (pInfo->m_Access & ACCESS_MASK) & pArt->m_resAccess))))
        {
            threadLog("No Access\n") ;
            pE->SendAjaxResult(HTTPMSG_FORBIDDEN, "No Access") ;
            goto get_end ;
        }
        //  Access granted
        pVP->m_art++ ;
        pArtStd = dynamic_cast<hdsArticleStd*>(pArt) ;
        if (pArtStd)
        {
            //  Standard article
            pE->SetHdr(s_articleTitle, pArt->m_Title) ;
            /*  NOT SURE ???
            if (!pArtStd->m_USL)
            {
                //  No USL assigned
                if (!pArtStd->m_Content)
                    pE->SendAjaxResult(HTTPMSG_NOCONTENT, "Article is a heading only") ;
                else
                {
                    Z = pArtStd->m_Content ;
                    rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_HTML, Z, 0, false) ;
                }
                goto get_end ;
            }
            */
            if (pArtStd->m_flagVE & VE_ACTIVE)
            {
                //  Article has active VEs, so must be generated
                pArtStd->Display(Z, pE) ;
                if (!Z.Size())
                {
                    pE->m_Error.Printf("AJAX case 2\n") ;
                    pE->SendAjaxResult(HTTPMSG_NOCONTENT, "Article is a heading only") ;
                }
                else
                {
                    if (pE->Zipped())
                        rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_HTML, Z, 0, true) ;
                    else
                        rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_HTML, Z, 0, false) ;
                }
                goto get_end ;
            }
            //  Article is inert
            if (pE->Zipped())
            {
                //  Article is pre-zipped
                Z = pArtStd->m_zipHTML ;
                if (Z.Size())
                    rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_HTML, Z, 0 /*43200*/, true) ;
                else
                {
                    pE->m_Error.Printf("Cmd: Could not locate article zip data %s\n", *pArtStd->m_Title) ;
                    pE->SendAjaxResult(HTTPMSG_NOCONTENT, "Article is a heading only") ;
                }
                goto get_end ;
            }
            //  Article is not pre-zipped
            Z = pArtStd->m_rawHTML ;
            if (Z.Size())
                rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_HTML, Z, 0 /*43200*/, false) ;
            else
            {
                pE->m_Error.Printf("Cmd: Could not locate article raw data %s\n", *pArtStd->m_Title) ;
                pE->SendAjaxResult(HTTPMSG_NOCONTENT, "Article is a heading only") ;
            }
            goto get_end ;
        }
        pArtCIF = dynamic_cast<hdsArticleCIF*>(pArt) ;
        if (pArtCIF)
        {
            //  C-Interface article
            rc = pArtCIF->Run(pE) ;
            goto get_end ;
        }
        /*
        if (rc != E_OK || pE->m_Error.Size())
        {
            if (pE->m_Error.Size())
                pE->m_Report << pE->m_Error ;
            trc = TCP_TERMINATE ;
        }
        */
        goto get_end ;
    }
    //  Test for sub-directory directives
    if (pReq[1])
    {
        for (n = 0, i = pReq + 1 ; *i && n < 8 ; i++, n++)
        {
            argbuf[n] = *i ;
            if (*i == CHAR_FWSLASH)
            {
                //  We have a subdir. If this names a language, change the language and recheck if there is another subdir. Otherwise leave the subdir for other interpretation.
                argbuf[n] = 0 ;
                argA = argbuf ;
                if (!m_Languages.Exists(argA))
                    break ;
        
                pReq = i ;
                pE->m_pContextLang = pLang = m_Languages[argA] ;
                argA = (char*)0 ;
                n = -1 ;
            }
        }
        //  Argument found?
        if (argA)
        {
            //  External JavaScript?
            if (argA == preset_jsc || argA == preset_js)
            {
                if (argA == preset_jsc)
                    reqPath = pReq + preset_jsc.Length() + 2 ;
                else
                    reqPath = pReq + preset_js.Length() + 2 ;
                pE->m_Report.Printf("Serving script [%s]\n", *reqPath) ;
                if (m_rawScripts.Exists(reqPath))
                {
                    //  Try the global non-language dependent scripts first
                    pVP->m_scr++ ;
                    if (pE->Method() == HTTP_HEAD)
                        pE->SendHttpHead(m_rawScripts[reqPath], HMTYPE_TXT_JS, 86400) ;
                    else
                    {
                        if (pE->Zipped())
                        {
                            Z = m_zipScripts[reqPath] ;
                            rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_JS, Z, 86400, true) ;
                            if (rc != E_OK)
                                pE->m_Report << pE->m_Error ;
                        }
                        else
                        {
                            Z = m_rawScripts[reqPath] ;
                            rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_JS, Z, 86400, false) ;
                            if (rc != E_OK)
                            {
                                pE->m_Report << pE->m_Error ;
                            }
                        }
                    }
                    goto get_end ;
                }
                if (m_rawScripts.Exists(reqPath))
                {
                    //  Try the language dependent scripts
                    pVP->m_scr++ ;
                    if (pE->Method() == HTTP_HEAD)
                        pE->SendHttpHead(m_rawScripts[reqPath], HMTYPE_TXT_JS, 86400) ;
                    else
                    {
                        if (pE->Zipped())
                        {
                            Z = m_zipScripts[reqPath] ;
                            rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_JS, Z, 86400, true) ;
                            if (rc != E_OK)
                                pE->m_Report << pE->m_Error ;
                        }
                        else
                        {
                            Z = m_rawScripts[reqPath] ;
                            rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_JS, Z, 86400, false) ;
                            if (rc != E_OK)
                                pE->m_Report << pE->m_Error ;
                        }
                    }
                    goto get_end ;
                }
                SendErrorPage(pE, HTTPMSG_NOTFOUND, __func__, "No such script as %s (%s)", *reqPath, pE->GetResource()) ;
                goto get_end ;
            }
            //  Image file?
            if (argA == preset_sysimg)
            {
                pVP->m_img++ ;
                pE->m_Report.Printf("Serving image [%s]\n", pReq + 8) ;
                if (pE->Method() == HTTP_HEAD)
                    pE->SendFileHead(*_hzGlobal_SysImages, pReq + 8) ;
                else
                    pE->SendPageE(*_hzGlobal_SysImages, pReq + 8, 86400, false) ;
                goto get_end ;
            }
            if (argA == preset_img)
            {
                pVP->m_img++ ;
                pE->m_Report.Printf("Serving image [%s]\n", pReq + 5) ;
                if (pE->Method() == HTTP_HEAD)
                    pE->SendFileHead(*m_Images, pReq + 5) ;
                else
                    pE->SendPageE(*m_Images, pReq + 5, 86400, false) ;
                goto get_end ;
            }
            //  Special text page?
            if (argA == preset_textpg)
            {
                pVP->m_spec++ ;
                reqPath = pReq + preset_textpg.Length() + 1 ;
                pE->m_Report.Printf("Serving textpg [%s]\n", *reqPath) ;
                pE->SendPageE(*m_Docroot, pReq, 0 /*43200*/, false) ;
                goto get_end ;
            }
            //  Document or binary datum?
            /*
            if (argA == preset_docs)
            {
                pVP->m_spec++ ;
                SendDocument(pE) ;
                goto get_end ;
            }
            */
            //  Problem now is argA is not a js or an img, what is it?
        }
        //  Handle request for the stylesheet
        if (m_namCSS == pReq+1)
        {
            pVP->m_scr++ ;
            if (pE->Method() == HTTP_HEAD)
                pE->SendHttpHead(m_txtCSS, HMTYPE_TXT_CSS, 86400) ;
            else
            {
                if (pE->Zipped())
                    rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_CSS, m_zipCSS, 86400, true) ;
                else
                    rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_CSS, m_txtCSS, 86400, false) ;
                if (rc != E_OK)
                    pE->m_Report << pE->m_Error ;
            }
            goto get_end ;
        }
        if (pReq[1] == 'u' && !memcmp(pReq, "/userdir", 8))
        {
            //  Handle user-specific files
            if (!pInfo)
            {
                SendErrorPage(pE, HTTPMSG_NOTFOUND, __func__, "No such page as %s (no-session-info)", pE->GetResource()) ;
                goto get_end ;
            }
            sv = pInfo->m_Sessvals[preset_userdir] ;
            if (sv.IsNull())
                SendErrorPage(pE, HTTPMSG_NOTFOUND, __func__, "No such page as %s", pE->GetResource()) ;
            pVP->m_spec++ ;
            reqPath = m_Docroot + sv.Str() ;
            reqPath = ConvertText(reqPath, pE) ;
            pE->m_Report.Printf("Serving userfile [%s/%s]\n", *reqPath, pReq + preset_userdir.Length() + 2) ;
            rc = pE->SendFilePage(*reqPath, pReq + preset_userdir.Length() + 2, 0, false) ;
            if (rc != E_OK)
                SendErrorPage(pE, HTTPMSG_NOTFOUND, __func__, "No such page as %s", pE->GetResource()) ;
            goto get_end ;
        }
        //  Process the request
        if (pReq[1] == 'm')
        {
            if (!memcmp(pReq, "/memstats", 10))
            {
                Z <<
                "<html>\n"
                "<head>\n"
                "<style>\n"
                ".main  { text-decoration:none; font-family:verdana; font-size:11px; font-weight:normal; color:#000000; }\n"
                ".stdpg { height:600px; border:0px; margin-left:5px; overflow-x:auto; overflow-y:auto; }\n"
                "</style>\n"
                "</head>\n"
                "<body>\n" ;
                ReportMemoryUsage(Z, true) ;
                Z <<
                "</body>\n"
                "</html>\n" ;
                pE->SendAjaxResult(HTTPMSG_OK, Z) ;
                goto get_end ;
            }
            if (!memcmp(pReq, "/mutexes", 8))
            {
                ReportMutexContention(Z, true) ;
                pE->SendAjaxResult(HTTPMSG_OK, Z) ;
                goto get_end ;
            }
        }
    }
    reqPath = pReq ;
    if (pReq[0] == CHAR_PERCENT)
    {
        //  Does this ever happen?
        reqPath = ConvertText(reqPath, pE) ;
    }
    if (!reqPath)
        reqPath = "/" ;
    if (reqPath.Length() > 1)
    {
        if (reqPath[reqPath.Length()-1] == CHAR_FWSLASH)
            reqPath.Truncate(reqPath.Length()-1) ;
    }
    if (reqPath == m_LogoutURL)
    {
        pVP->m_spec++ ;
        if (pInfo)
        {
            //  pInfo->m_Access &= ACCESS_ADMIN ;
            pE->m_Report.Printf("Deleting cookie %016x\n", pE->Cookie()) ;
            m_SessCookie.Delete(pE->Cookie()) ;
            pE->SetSession(0) ;
            pE->m_Report.Printf("Deleting cookie %016x\n", pE->Cookie()) ;
        }
    }
    //  See if requested resource matches any C-Interface functions
    /*
    pCIF = m_AltFuncs[reqPath] ;
    if (pCIF)
    {
        pE->m_Report.Printf("Serving C-Interface function %s\n", *reqPath) ;
        rc = pCIF->m_pFunc(pE) ;
        pE->m_Report.Printf("Done C-Interface returned %s\n", Err2Txt(rc)) ;
        goto get_end ;
    }
    pFix = m_Fixed[reqPath] ;
    if (pFix)
    {
        pE->m_Report.Printf("Serving fixed page [%s]\n", *reqPath) ;
        pVP->m_fix++ ;
        if (pE->Zipped() && pFix->m_zipValue.Size())
            rc = pE->SendRawChain(HTTPMSG_OK, pFix->m_Mimetype, pFix->m_zipValue, 86400, true) ;
        else
            rc = pE->SendRawChain(HTTPMSG_OK, pFix->m_Mimetype, pFix->m_rawValue, 86400, false) ;
        if (rc != E_OK)
            pE->m_Report << pE->m_Error ;
        goto get_end ;
    }
    */
proc_std_request:
    pResource = m_ResourcesPath[reqPath] ;
    pE->m_Report.Printf("Got resource %p from request %s\n", pResource, *reqPath) ;
    if (!pResource)
    {
        if (reqPath == "/")
            pResource = m_ResourcesPath[preset_index] ;
    }
    if (!pResource)
    {
        if (reqPath.Contains(CHAR_EQUAL))
        {
            reqPath.TruncateUpto("=") ;
            pResource = m_ResourcesPath[reqPath] ;
        }
    }
    if (!pResource)
    {
        pE->m_Report.Printf("No page case 2: Serving page with arg [%s]\n", *reqPath) ;
        //  No session and master path requested?
        if (!pInfo && m_MasterPath == pReq)
        {
            //  Supply login page
            rc = _masterLoginPage(pE) ;
            goto get_end ;
        }
        //  The page does not exist within the mormal Dissemino regime but it may exist as a page relative to the document root. If so and the flag for this is
        //  set, then stat and serve
        //  Page in document root?
        if (m_OpFlags & DS_APP_NORMFILE)
        {
            argB = m_Docroot + reqPath ;
            rc = TestFile(*argB) ;
            if (rc != E_OK)
                pE->m_Report.Printf("Cannot find normal page [%s]\n", *argB) ;
            else
            {
                pE->m_Report.Printf("Serving normal page [%s]\n", *argB) ;
                pE->SendFilePage(*m_Docroot, *reqPath, 0, false) ;
                goto get_end ;
            }
        }
        pE->m_Report.Printf("Non-exist page [%s]\n", *reqPath) ;
        pVP->m_G404++ ;
        //  Invalid page request allowance exceeded?
        if (pVP->m_G404 > 1000 || (!pVP->m_art && pVP->m_page && pVP->m_G404 > 20))
        {
            pE->m_Report.Printf("Note: Blocking IP %s for invalid GET requests\n", *ipa) ;
            SetStatusIP(ipa, HZ_IPSTATUS_BLACK_PROT, 9000) ;
        }
        if (pVP->m_robot)
            SendErrorPage(pE, HTTPMSG_GONE, __func__, "Page %s no longer exists", pE->GetResource()) ;
        else
            SendErrorPage(pE, HTTPMSG_NOTFOUND, __func__, "No such page as %s (info %p)", pE->GetResource(), pInfo) ;
        pE->m_Report.Printf("Served non-exist page [%s]\n", *reqPath) ;
    }
    else
    {
        pVP->m_page++ ;
        //  User has access?
        if (pResource->m_resAccess == ACCESS_PUBLIC
            || (pResource->m_resAccess == ACCESS_NOBODY && (!pInfo || !(pInfo->m_Access & ACCESS_MASK)))
            || (pInfo && (pInfo->m_Access & ACCESS_ADMIN || (pInfo->m_Access & ACCESS_MASK) & pResource->m_resAccess)))
        {
            if ((pPage = dynamic_cast<hdsPage*>(pResource)))
            {
                pE->m_Report.Printf("Serving page [%s] (%s)\n", *reqPath, *pPage->m_Title) ;
                if (pE->Method() == HTTP_HEAD)
                    pPage->Head(pE) ;
                else
                {
                    pPage->m_HitCount++ ;
                    if (pE->m_Resarg || pPage->m_flagVE & VE_ACTIVE)
                    {
                        pPage->Display(pE) ;
                    }
                    else
                    {
                        if (pE->Zipped())
                        {
                            Z = pPage->m_zipHTML ;
                            if (Z.Size())
                                rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_HTML, Z, 0 /*43200*/, true) ;
                            else
                                pE->m_Report.Printf("Cmd: Could not locate page zip data %s\n", *pPage->m_Title) ;
                        }
                        else
                        {
                            Z = pPage->m_rawHTML ;
                            if (Z.Size())
                                rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_HTML, Z, 0 /*43200*/, false) ;
                            else
                                pE->m_Report.Printf("Cmd: Could not locate page raw data %s\n", *pPage->m_Title) ;
                        }
                    }
                }
            }
            if ((pFix = dynamic_cast<hdsFile*>(pResource)))
            {
                pE->m_Report.Printf("Serving fixed page [%s]\n", *reqPath) ;
                pVP->m_fix++ ;
                if (pE->Zipped() && pFix->m_zipValue.Size())
                    rc = pE->SendRawChain(HTTPMSG_OK, pFix->m_Mimetype, pFix->m_zipValue, 86400, true) ;
                else
                    rc = pE->SendRawChain(HTTPMSG_OK, pFix->m_Mimetype, pFix->m_rawValue, 86400, false) ;
                if (rc != E_OK)
                    pE->m_Report << pE->m_Error ;
                    //{ m_pLog->Out("[") ; m_pLog->Out(pE->m_Error) ; m_pLog->Out("]\n") ; }
                    //goto get_end ;
            }
            if ((pCIF = dynamic_cast<hdsCIFunc*>(pResource)))
            {
                pCIF = dynamic_cast<hdsCIFunc*>(pResource) ;
                if (pCIF)
                {
                    pE->m_Report.Printf("Serving C-Interface Function [%s]\n", *reqPath) ;
                    pCIF->m_pFunc(pE) ;
                }
                else
                    pE->m_Report.Printf("NOT Serving C-Interface Function [%s]\n", *reqPath) ;
            }
        }
        else
            SendErrorPage(pE, HTTPMSG_FORBIDDEN, __func__, "No access to page %s", pE->GetResource()) ;
    }
get_end:
    if (rc != E_OK || pE->m_Error.Size())
    {
        if (pE->m_Error.Size())
            pE->m_Report << pE->m_Error ;
        trc = TCP_TERMINATE ;
    }
    if (pE->m_Report.Size())
    {
        threadLog(pE->m_Report) ;
        pE->m_Report.Clear() ;
    }
    pE->m_Report.Printf("ev=%d sk=%d ", pE->EventNo(), pE->CliSocket()) ;
    if (pE->Connection())
        pE->m_Report << "ka " ;
    else
        pE->m_Report << "cl " ;
    pE->m_Report.Printf("<bot=%d art=%d pg=%d scr=%d img=%d sp=%d fix=%d post=%d G404=%d P404=%d> ",
        pVP->m_robot, pVP->m_art, pVP->m_page, pVP->m_scr, pVP->m_img, pVP->m_spec, pVP->m_fix, pVP->m_post, pVP->m_G404, pVP->m_P404) ;
    if (pE->Method() == HTTP_HEAD)
        pE->m_Report << "HED: " ;
    else
        pE->m_Report << "GET: " ;
    pE->m_Report.Printf("%s lang %s %s vtotal=%u ", *iplocn, *pLang->m_Code, *ipa, m_Visitors.Count()) ;
    if (pInfo)
        pE->m_Report.Printf("[cook=%016X info=%p] (%s) %s\n", pE->Cookie(), pInfo, *currRef, pE->GetResource()) ;
    else
        pE->m_Report.Printf("[cook=%016X info=0] (%s) %s\n", pE->Cookie(), *currRef, pE->GetResource()) ;
    threadLog(pE->m_Report) ;
    pE->m_Report.Clear() ;
    return trc ;
}
void    hdsApp::Shutdown    (void)
{
    //  Shutsdown Dissemino Application. Deallocates all allocated resources before desconstruction
    //
    //  Arguments:  None
    //  Returns:    None
    _hzfunc("hdsApp::Shutdown") ;
    hdsArticle*     pArt ;          //  All articles
    hdsFormdef*     pForm ;         //  All known forms
    hdsFormhdl*     pFormhdl ;      //  All known form handlers
    hdsFormref*     pFormref ;      //  All known form handlers
    hdsBlock*       pBlock ;        //  All known forms
    hdsNavtree*     pAG ;           //  All article groups
    hdsResource*    pPage ;         //  All known pages by path
    hdsInfo*        pInfo ;         //  All sessions
    uint32_t    n ;     //  Iterator
    uint32_t    x ;     //  Iterator
    for (n = 0 ; n < m_FormDefs.Count() ; n++)
        { pForm = m_FormDefs.GetObj(n) ; delete pForm ; }
    m_FormDefs.Clear() ;
    for (n = 0 ; n < m_FormHdls.Count() ; n++)
        { pFormhdl = m_FormHdls.GetObj(n) ; delete pFormhdl ; }
    m_FormHdls.Clear() ;
    for (n = 0 ; n < m_FormUrl2Ref.Count() ; n++)
        { pFormref = m_FormUrl2Ref.GetObj(n) ; delete pFormref ; }
    m_FormUrl2Ref.Clear() ;
    for (n = 0 ; n < m_Includes.Count() ; n++)
        { pBlock = m_Includes.GetObj(n) ; delete pBlock ; }
    printf("Cleared %d Includes\n", m_Includes.Count()) ;
    fflush(stdout) ;
    m_Includes.Clear() ;
    for (n = 0 ; n < m_ArticleGroups.Count() ; n++)
    {
        pAG = m_ArticleGroups.GetObj(n) ;
        if (!pAG)
            continue ;
        for (x = 0 ; x < pAG->Count() ; x++)
        {
            pArt = (hdsArticle*) pAG->GetItem(x) ;
            delete pArt ;
        }
        printf("Cleared %d Articles\n", pAG->Count()) ;
        fflush(stdout) ;
        pAG->Clear() ;
    }
    //for (n = 0 ; n < m_Fixed.Count() ; n++)
    //  { pFix = m_Fixed.GetObj(n) ; delete pFix ; }
    //printf("Cleared %d Fixed resources\n", m_Fixed.Count()) ;
    //fflush(stdout) ;
    //m_Fixed.Clear() ;
    for (n = 0 ; n < m_ResourcesPath.Count() ; n++)
        { pPage = m_ResourcesPath.GetObj(n) ; delete pPage ; }
    printf("Cleared %d Pages\n", m_ResourcesPath.Count()) ;
    fflush(stdout) ;
    m_ResourcesPath.Clear() ;
    m_ResourcesName.Clear() ;
    for (n = 0 ; n < m_Responses.Count() ; n++)
        { pPage = m_Responses.GetObj(n) ; delete pPage ; }
    printf("Cleared %d Formhdl response pages\n", m_Responses.Count()) ;
    fflush(stdout) ;
    m_Responses.Clear() ;
    for (n = 0 ; n < m_SessCookie.Count() ; n++)
        { pInfo = m_SessCookie.GetObj(n) ; delete pInfo ; }
    printf("Cleared %d Sessions\n", m_SessCookie.Count()) ;
    fflush(stdout) ;
    m_SessCookie.Clear() ;
    printf("Cleared %d Styles\n", m_Styles.Count()) ;
    fflush(stdout) ;
    m_Styles.Clear() ;
    printf("Cleared %d Passives\n", m_Passives.Count()) ;
    fflush(stdout) ;
    m_Passives.Clear() ;
    printf("Cleared All\n") ;
    fflush(stdout) ;
}