//
//  File:   hzHttpServer.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.
//
//
//  Implimentation of the hzHttpEvent class described in hzHttpEvent.h
//
#include <fstream>
#include <sys/stat.h>
#include <unistd.h>
#include <stdarg.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include "hzErrcode.h"
#include "hzChars.h"
#include "hzMimetype.h"
#include "hzTextproc.h"
#include "hzCodec.h"
#include "hzDirectory.h"
#include "hzHttpServer.h"
#include "hzProcess.h"
#include "hzDissemino.h"
using namespace std ;
#define HZ_COOKIESIZE   28
/*
**  Variables
*/
global  hzMapS<hzString,hzChain>    s_SSIncludes ;      //  Server side file includes
global  hzMapS<hzString,hzChain*>   s_PageStore ;       //  Stored pages
global  hzString    _hzGlobal_runstart ;                //  Date string (of time of first serve this runtime)
/*
**  Non member functions
*/
static  uint32_t    _hexconvert (char* pStr, uint32_t nLen)
{
    //  Expected the supplied char ptr (arg 1) to be at the start of a hexadecimal number of nLen bytes (arg 2). If it is the value is converted to
    //  uint32_t and returned
    //
    //  Arguments:  1)  pStr    Pointer into an input string
    //              2)  nLen    Max number of chars to convert
    //
    //  Returns:    Value of hex conversion
    _hzfunc(__func__) ;
    uint32_t    nCount ;        //  Character count
    uint32_t    nValue = 0 ;    //  Value established
    if (!pStr)
        return 0 ;
    for (nCount = 0 ; *pStr && nCount < nLen ; nCount++)
    {
        nValue *= 16 ;
        if (pStr[nCount] >= '0' && pStr[nCount] <= '9')
            { nValue += (pStr[nCount] - '0') ; continue ; }
        if (pStr[nCount] >= 'A' && pStr[nCount] <= 'F')
            { nValue += 10 ; nValue += (pStr[nCount] - 'A') ; continue ; }
        if (pStr[nCount] >= 'a' && pStr[nCount] <= 'f')
            { nValue += 10 ; nValue += (pStr[nCount] - 'a') ; continue ; }
        return 0 ;
    }
    return nValue ;
}
hzHttpEvent::hzHttpEvent    (hzChain& ZI, hzIpConnex* pCx)
{
    m_Occur.SysDateTime() ;
    m_pCx = pCx ;
    if (pCx)
    {
        m_pLog = m_pCx->GetLogger() ; 
        m_ClientIP = m_pCx->ClientIP() ;
    }
    m_pContextApp = m_pContextLang = m_pContextForm = m_pContextObj = 0 ;
    m_pBuf = 0 ;
    Clear() ;
}
hzHttpEvent::hzHttpEvent    (void)
{
    m_Occur.SysDateTime() ;
    m_pLog = 0 ;
    m_pCx = 0 ; 
    m_pContextApp = m_pContextLang = m_pContextForm = m_pContextObj = 0 ;
    m_pBuf = 0 ;
    Clear() ;
}
hzHttpEvent::~hzHttpEvent   (void)
{
    m_pLog = 0 ;
    m_pCx = 0 ;
    m_pSession = 0 ;
    m_pContextApp = m_pContextLang = m_pContextForm = m_pContextObj = 0 ;
    Clear() ;
}
void    hzHttpEvent::Clear  (void)
{
    m_ClientIP.Clear() ;    // = (char*) 0 ;
    if (m_pBuf)
        delete m_pBuf ;
    //m_CookieNew = 0 ;
    //m_CookieOld = 0 ;
    m_Referer.Clear() ;
    m_Redirect.Clear() ;
    m_Auth.Clear() ;
    //m_CookieSub = 0 ;
    m_pAccept = m_pAcceptCharset = m_pAcceptLang = m_pAcceptCode = m_pCacheControl = m_pConnection = m_pContentType = m_pETag = m_pPragma = 0 ;
    m_pUserAgent = m_pProcessor = m_pVia = m_pCliIP = m_pHost = m_pXost = m_pFwrdIP = m_pProxIP = m_pServer = m_pFrom = m_pReferer = 0 ;
    //m_pReqPATH = m_pReqQURY = m_pReqFRAG = 0 ;
    m_pReqPATH = m_pReqFRAG = 0 ;
    m_LastMod.Clear() ;
    m_CookieExpire.Clear() ;
    m_pSession = 0 ;
    m_nHeaderLen = 0 ;
    m_nContentLen = 0 ;
    m_nQueryLen = 0 ;
    m_nMaxForwards = 0 ;
    m_eRetCode = HTTPMSG_OK ;
    m_eMethod = HTTP_INVALID ;
    m_bHdrComplete = false ;
    m_bMsgComplete = false ;
    m_bZipped = false ;
    m_nConnection = 0 ;
}
uint32_t    hzHttpEvent::_setnvpairs    (chIter& ci)
{
    //  Gather up the submitted data as a set of name-value pairs. Advance the supplied iterator to the end of the header.
    //
    //  Arguments:  1)  ci  Chain iterator to process submission data
    //
    //  Returns:    Number of places iterator has advanced
    _hzfunc("hzHttpEvent::_setnvpairs") ;
    hzChain     C ;         //  For building names/values
    hzPair      P ;         //  Name value pair
    uint32_t    nCount ;    //  Counter
    char        hex[4] ;    //  For hex conversion
    for (nCount = 0 ; !ci.eof() && *ci != CHAR_SPACE && *ci != CHAR_HASH ; nCount++, ci++)
    {
        //  Consider hex encoding first
        if (*ci == CHAR_PERCENT)
        {
            ci++ ; hex[0] = *ci ;
            ci++ ; hex[1] = *ci ;
            hex[2] = 0 ;
            nCount += 2 ;
            C.AddByte(_hexconvert(hex, 2)) ;
            continue ;
        }
        //  Hex encoding safe
        if (*ci == CHAR_EQUAL)
        {
            P.name = C ;
            C.Clear() ;
            continue ;
        }
        if (*ci == CHAR_AMPSAND || *ci <= CHAR_SPACE)
        {
            if (C.Size() > 4000)
                m_mapChains.Insert(P.name, C) ;
            else
            {
                P.value = C ;
                m_Inputs.Add(P) ;
                m_mapStrings.Insert(P.name, P.value) ;
                P.Clear() ;
            }
            C.Clear() ;
            if (*ci == CHAR_AMPSAND)
                continue ;
            else
                break ;
        }
        if (*ci == CHAR_PLUS)
            C.AddByte(CHAR_SPACE) ;
        else
            C.AddByte(*ci) ;
    }
    if (C.Size())
    {
        P.value = C ;
        m_Inputs.Add(P) ;
        m_mapStrings.Insert(P.name, P.value) ;
    }
    return nCount ;
}
void    hzHttpEvent::SetSessCookie  (const hzSysID& Cookie)
{
    //  Set a new cookie and expire any old.
    //
    //  Arguments:  1)  Cookie  The full cookie string
    //
    //  Returns:    None
    _hzfunc("hzHttpEvent::SetSessCookie") ;
    m_CookieNew = Cookie ;
    m_CookieExpire.Clear() ;
}
void    hzHttpEvent::SetPermCookie  (const hzSysID& Cookie, hzSDate& expires)
{
    //  Set a new cookie and expire any old.
    //
    //  Arguments:  1)  Cookie  The full cookie string
    //              2)  expires Short form date
    //
    //  Returns:    None
    _hzfunc("hzHttpEvent::SetPermCookie") ;
    m_CookieNew = Cookie ;
    m_CookieExpire = expires ;
}
hzEcode hzHttpEvent::ProcessEvent   (hzChain& ZI)
{
    //  Purpose:    Process a HTTP event
    //
    //  This function is called whenever data comes in from a connected HTTP client. Should the data contain a complete HTTP request, the request is processed.
    //
    //  Arguments:  1)  ZI      Input chain poplated by the incomming HTTP request or submission
    //
    //  Returns:    E_FORMAT    If the request is malformed in some way
    //              E_ARGUMENT  If the request does not indicate a URL or a host.
    //              E_OK        If the operation was successful.
    _hzfunc("hzHttpEvent::ProcessEvent") ;
    hzChain::BlkIter    bi ;        //  To get directly at input chain inner buffer
    hzChain         Head ;          //  The HTTP header
    hzChain         Word ;          //  For building tokens
    hzChain         deco ;          //  For decoding base64 values
    chIter          zi ;            //  Chain iterator
    chIter          xi ;            //  Chain iterator
    chIter          mkA ;           //  1st part of header line
    chIter          mkB ;           //  2nd part of header line
    chIter          mkC ;           //  End 2nd part of header line
    hzHttpFile      upload ;        //  To cope with file uploads
    hzPair          Pair ;          //  Name value pair
    const char*     i ;             //  Loop control
    char*           j ;             //  For string iteration
    char*           ph ;            //  Offset into m_pBuf ;
    uint64_t        cookie ;        //  Cookie value
    hzString        S ;             //  Temp string
    hzString        boundary ;      //  MIME boundary for multipart
    uint32_t        nLine = 0 ;     //  Header line number
    uint32_t        bErr = 0 ;      //  Format error
    uint32_t        hSofar ;        //  For header value extraction
    uint32_t        n ;             //  For cookie extraction
    uint32_t        len ;           //  Offset into buffer
    uint32_t        nFst ;          //  No of colon-space sequences in header
    uint32_t        nSnd ;          //  No of CR-NL sequences in header (excluding the last pair)
    if (!this)      Fatal("No Instance\n") ;
    if (!m_pLog)    Fatal("Cannot process requests. No logfile\n") ;
    if (!m_pCx)     Fatal("Cannot process requests. No client info\n") ;
    //  If we already have completed header part, skip
    m_Report.Printf("ProcessEvents: Input chain of %d bytes\n", ZI.Size()) ;
    m_Report << ZI ;
    //  Header complete?
    if (m_bHdrComplete)
    {
        //if (_hzGlobal_Debug & HZ_DEBUG_SERVER)
            m_Report.Printf("HTTP HEADER COMPLETE: Goto stage 2 with Header of %d bytes (conn=%p)\n", bi.Data(), m_nHeaderLen, m_pCx) ;
        goto stage_two ;
    }
    //  Input too small?
    if (ZI.Size() < 80)
    {
        zi = ZI ;
        //  CONNECT attempt?
        if (!(*zi >= 'A' && *zi <= 'Z') || zi == "CONNECT ")
        {
            m_Report << "Invalid Header [" << ZI << "]\n" ;
            SetStatusIP(m_pCx->ClientIP(), HZ_IPSTATUS_BLACK_HTTP, 9000) ;
            return E_FORMAT ;
        }
        //  Return and wait for more data
        m_Report << "Incomplete Header [" << ZI << "]\n" ;
        return E_OK ;
    }
    m_ClientIP = m_pCx->ClientIP() ;
    m_Occur.SysDateTime() ;
    //  Establish header size
    if (!m_nHeaderLen)
    {
        //  Read up to end of first line to measure length required to accommodate the request path and any query or fragment
        nFst = nSnd = hSofar = 0 ;
        for (zi = ZI ; !zi.eof() && Head.Size() < HZ_MAX_HTTP_HDR ; zi++)
        {
            if (*zi == CHAR_CR && zi == "\r\n")
                { Head << "\r\n" ; zi += 2 ; break ; }
            Head.AddByte(*zi) ;
        }
        hSofar = Head.Size() ;
        //  Then get rest of HTTP header
        for (; !zi.eof() && Head.Size() < HZ_MAX_HTTP_HDR ; zi++)
        {
            if (*zi == CHAR_CR && zi == "\r\n\r\n")
                { Head << "\r\n\r\n" ; m_nHeaderLen = Head.Size() ; break ; }
            if (zi == ": ")
                { nFst++ ; hSofar++ ; }
            if (nFst > nSnd)
                hSofar++ ;
            if (zi == "\r\n")
                nSnd++ ;
            Head.AddByte(*zi) ;
        }
        if (!m_nHeaderLen)
        {
            m_Report.Printf("\n%s: REJECTED REQUEST (Excessive HTTP Header)\n", *m_Occur) ;
            SendError(HTTPMSG_NOTFOUND, "Excessive HTTP Header\n") ;
            return E_RANGE ;
        }
        if (m_nHeaderLen >= HZ_MAX_HTTP_HDR)
        {
            m_Report.Printf("\n%s: REJECTED REQUEST (too large)\n", *m_Occur) ;
            SendError(HTTPMSG_ENTITY_TOO_LARGE, "Excessive HTTP Header\n") ;
            return E_RANGE ;
        }
        //  Extract HTTP header values
        if (hSofar < 1024)
            hSofar = 1024 ;
        ph = m_pBuf = new char[hSofar] ;
        zi = Head ;
        if      (zi == "GET ")      { zi += 4 ; m_eMethod = HTTP_GET ; }
        else if (zi == "HEAD ")     { zi += 5 ; m_eMethod = HTTP_HEAD ; }
        else if (zi == "POST ")     { zi += 5 ; m_eMethod = HTTP_POST ; }
        else if (zi == "OPTIONS ")  { zi += 8 ; m_eMethod = HTTP_OPTIONS ; }
        else if (zi == "PUT ")      { zi += 4 ; m_eMethod = HTTP_PUT ; }
        else if (zi == "DELETE ")   { zi += 7 ; m_eMethod = HTTP_DELETE ; }
        else if (zi == "TRACE ")    { zi += 6 ; m_eMethod = HTTP_TRACE ; }
        else if (zi == "CONNECT ")  { zi += 8 ; m_eMethod = HTTP_CONNECT ; }
        else
            bErr |= 0x01 ;
        //  Obtain the requested path and if present, the query and the fragment
        if (!bErr)
        {
            //  Get the requested path first
            for (m_pReqPATH = ph ; !zi.eof() && *zi != CHAR_SPACE && *zi != CHAR_QUERY && *zi != CHAR_HASH ; ph++, zi++)
                *ph = *zi ;
            *ph++ = 0 ;
            if (*zi == CHAR_QUERY)
            {
                //  Read until either a SPACE or a HASH
                zi++ ;
                m_nQueryLen = _setnvpairs(zi) ;
            }
            if (*zi == CHAR_HASH)
            {
                //  Read until a SPACE
                for (m_pReqFRAG = ph ; !zi.eof() && *zi == CHAR_SPACE ; ph++, zi++)
                    *ph = *zi ;
                *ph++ = 0 ;
            }
            if (*zi != CHAR_SPACE)
                bErr |= 0x02 ;
            if (!m_pReqPATH[0])
                bErr |= 0x02 ;
            zi++ ;
        }
        //  Obtain the HTTP version
        if (!bErr)
        {
            if (zi != "HTTP/")
                bErr |= 0x04 ;
            else
            {
                zi += 5 ;
                if      (zi == "1.0\r\n")   { zi += 5 ; m_nVersion = 0 ; }
                else if (zi == "1.1\r\n")   { zi += 5 ; m_nVersion = 1 ; }
                else if (zi == "2.0\r\n")   { zi += 5 ; m_nVersion = 2 ; }
                else
                    bErr |= 0x08 ;
            }
        }
        /*
        **  Now grab the other headers of interest. Note headers not of interest are ignored. The main objective is to reject HTTP requests that are malformed. The process assumes
        **  each header is in it's own line and is of the form "header_name: value\r\n".
        */
        //for (nLine = 2 ; !zi.eof() && !bErr && nLine <= nSnd ; nLine++)
        for (nLine = 2 ; !zi.eof() && !bErr ; nLine++)
        {
            //  Should be at the start of a line so establish line contents
            if (zi == "\r\n")
                break ;
            //  Discover 1st half of header line (upto colon and space)
            for (mkA = mkB = mkC = zi ; !zi.eof() ; zi++)
            {
                if (*zi == CHAR_COLON && zi[1] == CHAR_SPACE)
                {
                    //  Discover 2nd part of line (from colon and space to end of line)
                    zi += 2 ;
                    for (len = 0, mkB = zi ; !zi.eof() ; len++, zi++)
                    {
                        if (*zi == CHAR_CR && zi[1] == CHAR_NL)
                            { mkC = zi ; zi += 2 ; break ; }
                    }
                    if (!len)
                        bErr |= 0x10 ;
                    break ;
                }
            }
            //  Now have a complete line
            switch  (toupper(*mkA))
            {
            case 'A':
                if (mkA.Equiv("Accept"))            { for (m_pAccept = ph,          xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; break ; }
                if (mkA.Equiv("Accept-Charset"))    { for (m_pAcceptCharset = ph,   xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; break ; }
                if (mkA.Equiv("Accept-Language"))   { for (m_pAcceptLang = ph,      xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; break ; }
                if (mkA.Equiv("Accept-Encoding"))   { for (m_pAcceptCode = ph,      xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; break ; }
                if (mkA.Equiv("Authorization: basic"))
                {
                    for (j = ph, xi = mkB ; xi != mkC ; j++, xi++)
                        *j = *xi ;
                    *j++ = 0 ;
                    Base64Decode(deco, ph) ;
                    m_Auth = deco ;
                }
                break ;
            case 'C':
                if (mkA.Equiv("Cache-Control"))     { for (m_pCacheControl = ph,    xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; break ; }
                if (mkA.Equiv("Content-Type"))      { for (m_pContentType = ph,     xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; break ; }
                if (mkA.Equiv("Client-ip"))         { for (m_pCliIP = ph,           xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; break ; }
                if (mkA.Equiv("Content-Length"))    { for (j = ph, xi = mkB ; xi != mkC ; j++, xi++) *j = *xi ; *j++ = 0 ; m_nContentLen = *ph ? atoi(ph) : 0 ; break ; }
                if (mkA.Equiv("Connection"))        { m_nConnection = mkB.Equiv("keep-alive") ? 0 : 15 ; break ; }
                if (mkA.Equiv("Cookie"))
                {
                    //  Only interested in cookies with names matching the _hz_ prefix
                    for (xi = mkB ; *xi ; xi++)
                    {
                        //  Cookie match?
                        if (xi == "_hz_")   //  _hzGlobal_Dissemino->m_CookieName)
                        {
                            //  Found a cookie begining with _hz so copy upto the semicolon
                            //xi += _hzGlobal_HtmlApp->m_CookieName.Length() + 1 ;
                            xi += 5 ;   //_hzGlobal_Dissemino->m_CookieName.Length() + 1 ;
                            for (j = ph, n = 0 ; *xi && n < 32 && *xi > CHAR_SPACE && *xi != CHAR_SCOLON ; j++, xi++, n++)
                                *j = *xi ;
                            *j++ = 0 ;
                            IsHexnum(cookie, ph) ;
                            m_CookieSub = cookie ;
                            break ;
                        }
                    }
                }
                break ;
            case 'F':   if (mkA.Equiv("From"))  { for (m_pFrom = ph, xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; }   break ;
            case 'H':   if (mkA.Equiv("Host"))
                        {
                            for (m_pHost = ph, xi = mkB ; *xi != CHAR_COLON && xi != mkC ; ph++, xi++)
                                *ph = *xi ;
                            *ph++ = 0 ;
                        }
                        if (xi == CHAR_COLON)
                            for (; xi != mkC ; xi++) ;
                        break ;
            case 'I':   if (mkA.Equiv("If-Modified-Since")) { for (j = ph, xi = mkB ; xi != mkC ; j++, xi++) *j = *xi ; *j++ = 0 ; m_LastMod = ph ; break ; }
                        if (mkA.Equiv("If-None-Match"))     { for (m_pETag = ph, xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; }
                        break ;
            case 'K':   if (mkA.Equiv("Keep-Alive"))
                            m_nConnection = 15 ;
                        break ;
            case 'M':   if (mkA.Equiv("Max-Forwards"))  { for (j = ph, xi = mkB ; xi != mkC ; j++, xi++) *j = *xi ; *j++ = 0 ; m_nMaxForwards = *ph ? atoi(ph) : 0 ; }  break ;
            case 'P':   if (mkA.Equiv("Pragma"))        { for (m_pPragma = ph,  xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; }    break ;
            case 'R':   if (mkA.Equiv("Referer"))       { for (m_pReferer = ph, xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; m_Referer = m_pReferer ; }   break ;
            case 'U':   if (mkA.Equiv("User-Agent"))    { for (m_pUserAgent = ph, xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; break ; }
                        if (mkA.Equiv("UA-CPU"))        { for (m_pProcessor = ph, xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; }
                        break ;
            case 'V':   if (mkA.Equiv("Via"))
                            { for (m_pVia = ph, xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; }
                        break ;
            case 'x':
            case 'X':
                if (mkA.Equiv("X-Forwarded-For"))       { for (m_pFwrdIP = ph,  xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; break ; }
                if (mkA.Equiv("X-ProxyUser-IP"))        { for (m_pProxIP = ph,  xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; break ; }
                if (mkA.Equiv("X-Forwarded-Host"))      { for (m_pXost = ph,    xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; break ; }
                if (mkA.Equiv("X-Forwarded-Server"))    { for (m_pServer = ph,  xi = mkB ; xi != mkC ; ph++, xi++) *ph = *xi ; *ph++ = 0 ; }
                break ;
            default:
                break ;
            }
        }
        //  If no other errors, check we have an IP address
        if (m_ClientIP == IPADDR_BAD || m_ClientIP == IPADDR_NULL || m_ClientIP == IPADDR_LOCAL)
        {
            if (m_pCliIP)
                m_ClientIP = m_pCliIP ;
        }
        if (m_ClientIP == IPADDR_BAD || m_ClientIP == IPADDR_NULL || m_ClientIP == IPADDR_LOCAL)
        {
            if (m_pFwrdIP)
                m_ClientIP = m_pFwrdIP ;
        }
        if (m_ClientIP == IPADDR_BAD || m_ClientIP == IPADDR_NULL || m_ClientIP == IPADDR_LOCAL)
        {
            if (m_pProxIP)
                m_ClientIP = m_pProxIP ;
        }
        if (m_ClientIP == IPADDR_BAD || m_ClientIP == IPADDR_NULL || m_ClientIP == IPADDR_LOCAL)
        {
            //bErr |= 0x20 ;
        }
        if (!m_pHost)
            bErr |= 0x40 ;
        if (bErr)
        {
            m_bMsgComplete = true ;
            m_Report.Printf("%s Sock %d/%d REQUEST [\n", *m_Occur, m_pCx->CliSocket(), m_pCx->CliPort()) ;
            m_Report << ZI ;
            m_Report << "]\n" ;
            if (bErr & 0x01)    m_Report.Printf("Line 1: Failed to find HTTP Method\n") ;
            if (bErr & 0x02)    m_Report.Printf("Line 1: Malformed HTTP resource request\n") ;
            if (bErr & 0x04)    m_Report.Printf("Line 1: Invalid HTTP Version\n") ;
            if (bErr & 0x08)    m_Report.Printf("Line %d: No space after colon\n", nLine) ;
            if (bErr & 0x10)    m_Report.Printf("Line %d: Could not evaluate line\n", nLine) ;
            if (bErr & 0x20)    m_Report.Printf("Could not detect client IP\n") ;
            if (bErr & 0x40)    m_Report.Printf("No Host header supplied\n") ;
            if (m_eMethod == HTTP_CONNECT && (!m_pHost || m_pHost != _hzGlobal_Hostname))
            {
                //  This is an automatic ban
                if (!(bErr & 0x20))
                {
                    SetStatusIP(m_ClientIP, HZ_IPSTATUS_BLACK_PROT, 9000) ;
                    return E_FORMAT ;
                }
            }
            if (m_pBuf)             m_Report.Printf("m_pBuf           = %s\n", m_pBuf) ;
            if (m_pAccept)          m_Report.Printf("m_pAccept        = %s\n", m_pAccept) ;
            if (m_pAcceptCharset)   m_Report.Printf("m_pAcceptCharset = %s\n", m_pAcceptCharset) ;
            if (m_pAcceptLang)      m_Report.Printf("m_pAcceptLang    = %s\n", m_pAcceptLang) ;
            if (m_pAcceptCode)      m_Report.Printf("m_pAcceptCode    = %s\n", m_pAcceptCode) ;
            if (m_pCacheControl)    m_Report.Printf("m_pCacheControl  = %s\n", m_pCacheControl) ;
            if (m_pConnection)      m_Report.Printf("m_pConnection    = %s\n", m_pConnection) ;
            if (m_pContentType)     m_Report.Printf("m_pContentType   = %s\n", m_pContentType) ;
            if (m_pETag)            m_Report.Printf("m_pETag          = %s\n", m_pETag) ;
            if (m_pPragma)          m_Report.Printf("m_pPragma        = %s\n", m_pPragma) ;
            if (m_pUserAgent)       m_Report.Printf("m_pUserAgent     = %s\n", m_pUserAgent) ;
            if (m_pProcessor)       m_Report.Printf("m_pProcessor     = %s\n", m_pProcessor) ;
            if (m_pVia)             m_Report.Printf("m_pVia           = %s\n", m_pVia) ;
            if (m_pCliIP)           m_Report.Printf("m_pCliIP         = %s\n", m_pCliIP) ;
            if (m_pHost)            m_Report.Printf("m_pHost          = %s\n", m_pHost) ;
            if (m_pXost)            m_Report.Printf("m_pXost          = %s\n", m_pXost) ;
            if (m_pFwrdIP)          m_Report.Printf("m_pFwrdIP        = %s\n", m_pFwrdIP) ;
            if (m_pProxIP)          m_Report.Printf("m_pProxIP        = %s\n", m_pProxIP) ;
            if (m_pServer)          m_Report.Printf("m_pServer        = %s\n", m_pServer) ;
            if (m_pFrom)            m_Report.Printf("m_pFrom          = %s\n", m_pFrom) ;
            if (m_pReferer)         m_Report.Printf("m_pReferer       = %s\n", m_pReferer) ;
            if (m_pReqPATH)         m_Report.Printf("m_pReqPATH       = %s\n", m_pReqPATH) ;
            if (m_pReqFRAG)         m_Report.Printf("m_pReqFRAG       = %s\n", m_pReqFRAG) ;
            SendError(HTTPMSG_NOTFOUND, "SORRY! INTERNAL ERROR\n") ;
            return E_FORMAT ;
        }
        if (_hzGlobal_Debug & HZ_DEBUG_SERVER)
        {
            m_Report.Printf("%s Sock %d/%d REQUEST [\n", *m_Occur, m_pCx->CliSocket(), m_pCx->CliPort()) ;
            m_Report << ZI ;
            m_Report << "]\n" ;
        }
    }
    //  Header complete
    m_bHdrComplete = true ;
    //  The header has been processed and the content-length is known. The whole hit may not be in but we can test the header.
    //m_Resource.UrlDecode() ;
stage_two:
    //  We now can test that the hit has been sent in full
    if (ZI.Size() < (m_nHeaderLen + m_nContentLen))
    {
        m_Report.Printf("Not recv in full. Hdr %d, cont %d, actual %d\n", m_nHeaderLen, m_nContentLen, ZI.Size()) ;
        return E_OK ;
    }
    if (ZI.Size() > (m_nHeaderLen + m_nContentLen))
        m_Report.Printf("Msg over: Hdr %d, cont %d, actual %d\n", m_nHeaderLen, m_nContentLen, ZI.Size()) ;
    m_bMsgComplete = true ;
    //  Obtain POST data if applicable
    zi = ZI ;
    zi += m_nHeaderLen ;
    if (m_eMethod == HTTP_POST)
    {
        if (m_pContentType)
        {
            for (n = 0, i = m_pContentType ; *i ; i++)
            {
                if (*i == CHAR_SCOLON)
                    { n = 1 ; continue ; }
                if (n)
                {
                    if (!memcmp(i, "boundary=", 9))
                        { i += 9 ; boundary = i ; break ; }
                }
            }
        }
        if (!boundary)
            _setnvpairs(zi) ;
        else
        {
            for (; !zi.eof() ;)
            {
                if (zi != boundary)
                    { zi++ ; continue ; }
                /*
                **  In the general case, data occurs in the form:-
                **      boundary\nContent-Disposition: form-data; name="fldname"
                **      data
                **      blank line.
                **  Note that in the empty field case, there are two blank lines (3 \r\n sequences)
                **
                **  In the file upload case in the form:-
                **      boundary\nContent-Disposition: form-data; name="fldname"; filename="filename"
                **      Content-Type: ...
                **      blank line
                **      filedata
                **  Note that the filedata is terminated by the appearence of the boundary on a line by itself and that the whole submission is
                **  terminated by the boundary followed directly by two minus signs.
                */
                zi += boundary.Length() ;
                if (zi == "--")
                    break ;
                zi.Skipwhite() ;
                //  Content-Disposition?
                if (zi != "Content-Disposition: form-data; name=")
                {
                    m_Report.Printf("Malformed multipart form submission (case 1)\n") ;
                    for (; !zi.eof() && *zi != CHAR_NL ; zi++) ;
                    break ;
                }
                //  Get name part of name-value pair
                for (zi += 38 ; !zi.eof() && *zi != CHAR_DQUOTE ; zi++)
                    Word.AddByte(*zi) ;
                zi++ ;
                Pair.name = Word ;
                Word.Clear() ;
                //  Get value part of name-value pair
                if (*zi == CHAR_SCOLON)
                {
                    //  Malformed filename indicator?
                    if (zi != "; filename=")
                    {
                        m_Report.Printf("Malformed multipart form submission (case 2)\n") ;
                        for (; !zi.eof() && *zi != CHAR_NL ; zi++) ;
                        break ;
                    }
                    for (zi += 12 ; !zi.eof() && *zi != CHAR_DQUOTE ; zi++)
                        Word.AddByte(*zi) ;
                    zi++ ;
                    Pair.value = Word ;
                    Word.Clear() ;
                    upload.m_fldname = Pair.name ;
                    upload.m_filename = Pair.value ;
                    //  Now get file content
                    zi.Skipwhite() ;
                    if (zi != "Content-Type: ")
                    {
                        m_Report.Printf("Expected Content-Type for submitted file\n") ;
                        break ;
                    }
                    for (zi += 14 ; !zi.eof() && *zi != CHAR_NL ; zi++)
                    {
                        if (*zi == CHAR_CR)
                            continue ;
                        if (*zi == CHAR_NL)
                            break ;
                        Word.AddByte(*zi) ;
                    }
                    S = Word ;
                    Word.Clear() ;
                    upload.m_mime = Str2Mimetype(S) ;
                    if (_hzGlobal_Debug & HZ_DEBUG_SERVER)
                        m_Report.Printf("MIME type of %s is %d (%s)\n", *Pair.value, upload.m_mime, *S) ;
                    zi++ ;
                    if (zi == "\r\n")
                        zi += 2 ;
                    for (; !zi.eof() ; zi++)
                    {
                        if (zi == "\r\n--")
                        {
                            zi += 4 ;
                            if (zi == boundary)
                                break ;
                            upload.m_file << "\r\n--" ;
                            continue ;
                        }
                        upload.m_file.AddByte(*zi) ;
                    }
                    //m_Files.Add(W) ;
                    m_Inputs.Add(Pair) ;
                    m_mapStrings.Insert(Pair.name, Pair.value) ;
                    m_Report.Printf("Field name/value %s=%s\n", *Pair.name, *Pair.value) ;
                    m_Uploads.Insert(upload.m_fldname, upload) ;
                    m_Report.Printf("Got file of %d bytes\n", upload.m_file.Size()) ;
                }
                else
                {
                    //  Expect \r\n then a line of data terminated by \r\n\r\n
                    //  Carrige return?
                    if (*zi != CHAR_CR)
                        m_Report.Printf("Warning fld not at CR, char=%c instead\n", *zi) ;
                    zi.Skipwhite() ;
                    for (; !zi.eof() ; zi++)
                    {
                        if (zi == "\r\n")
                        {
                            zi += 2 ;
                            if (zi == "--")
                            {
                                zi += 2 ;
                                if (zi == boundary)
                                    break ;
                            }
                            Word.AddByte(CHAR_NL) ;
                            continue ;
                        }
                        Word.AddByte(*zi) ;
                    }
                    Pair.value = Word ;
                    Word.Clear() ;
                    m_Inputs.Add(Pair) ;
                    m_mapStrings.Insert(Pair.name, Pair.value) ;
                    m_Report.Printf("Field name/value %s=%s\n", *Pair.name, *Pair.value) ;
                }
            }
        }
    }
    return E_OK ;
}
hzEcode hzHttpEvent::Storeform  (const char* cpPath)
{
    //  Appends submitted forms to a file of the supplied pathname. This is quite separate from any processing of the form by the application.
    //  This can be convient for diagnostics or even serve as a rudimantry form of backup.
    //
    //  Arguments:  1)  cpPath  Filename to store form submissions
    //
    //  Returns:    E_ARGUMENT  If the pathname is not supplied
    //              E_OPENFAIL  If the store form file cannot be opened for writing
    //              E_WRITEFAIL If a write error occurs
    //              E_OK        If the form submission was stored
    _hzfunc("hzHttpEvent::Storeform") ;
    static  char cvLine [80] ;  //  Buffer for time stamp and field id
    std::ofstream   os ;        //  Output file for form submission storage
    hzPair      P ;             //  Field name/value pair
    hzXDate     now ;           //  System time stamp
    uint32_t    nIndex ;        //  Field iterator
    if (!cpPath || !cpPath[0])
        return hzerr(E_ARGUMENT, "No pathname supplied\n") ;
    os.open(cpPath, std::ios::app) ;
    if (os.fail())
        return hzerr(E_OPENFAIL, "Could not open file (%s) for writing\n", cpPath) ;
    now.SysDateTime() ;
    sprintf(cvLine, "@Date: %04d%02d%02d\n", now.Year(), now.Month(), now.Day()) ;
    os << cvLine ;
    sprintf(cvLine, "@Time: %02d%02d%02d\n", now.Hour(), now.Min(), now.Sec()) ;
    os << cvLine ;
    for (nIndex = 0 ; GetAt(P, nIndex) == E_OK ; nIndex++)
    {
        if (P.name && P.value)
            os << "@" << P.name << ":\t" << P.value << "\n" ;
        if (os.fail())
        {
            os.close() ;
            hzerr(E_WRITEFAIL, "_storeform: Write error on file (%s)\n", cpPath) ;
            return E_WRITEFAIL ;
        }
    }
    os << "@end:\n\n" ;
    os.close() ;
    return E_OK ;
}
hzEcode _storeform  (hzHttpEvent* pE, FILE* fp)
{
    //  This stores forms submitted by HTTP clients in the supplied file. This is essentially for diagnostics and more formal mathods
    //  are recomended for the operation of an Internet based application.
    //
    //  Arguments:  1)  cpPath  Filename to store form submissions
    //              2)  fp      FILE pointer (to open file)
    //
    //  Returns:    E_NOTOPEN   If the store form file pointer is not supplied
    //              E_WRITEFAIL If a write error occurs
    //              E_OK        If the form submission was stored
    _hzfunc("_storeform") ;
    hzPair      P ;             //  Field name/value pair
    hzXDate     now ;           //  System time stamp
    uint32_t    nIndex ;        //  Field iterator
    if (!fp)
        return E_NOTOPEN ;
    now.SysDateTime() ;
    if (fprintf(fp, "%04d%02d%02d-%02d%02d%02d\n", now.Year(), now.Month(), now.Day(), now.Hour(), now.Min(), now.Sec()) == -1)
        return hzerr(E_WRITEFAIL, "_storeform: Write error on file (1)") ;
    for (nIndex = 1 ; pE->GetAt(P, nIndex) == E_OK ; nIndex++)
    {
        if (fprintf(fp, "@%s:\t%s\n", *P.name, *P.value) == -1)
            return hzerr(E_WRITEFAIL, "_storeform: Write error on file (2)") ;
    }
    fflush(fp) ;
    return E_OK ;
}
hzEcode hzHttpEvent::SetHdr (const hzString& name, const hzString& value)
{
    //  Set a variable to be transmitted as a name/value pair in the header of the HTTP response. With suitable JavaScript in the response page or article, this
    //  variable can influence what is displayed.
    //
    //  This allows a page that is otherwise fixed, to have different manifestations to different users. Normally, the only way to do this is by having the page
    //  generated each time it is requested. A fixed content page or article can be pre-zipped so is quicker to serve and consumes less bandwidth.
    //
    //  Arguments:  1)  name    The header parameter name.
    //              2)  value   The header parameter value.
    //
    //  Returns:    E_ARGUMENT  If provided with blank variable name.
    //              E_NODATA    If no value is supplied..
    //              E_OK        If operation successful.
    _hzfunc("hzHttpEvent::SetHdr") ;
    hzPair  p ;     //  Header name/value pair
    if (!name)
        return E_ARGUMENT ;
    if (!value)
        return E_NODATA ;
    p.name = name ;
    p.value = value ;
    m_HdrsResponse.Add(p) ;
    return E_OK ;
}
hzEcode hzHttpEvent::SetVarString   (const hzString& name, const hzString& value)
{
    //  Sets a string value to the supplied value and places it in the hzHttpEvent's map of string values. Note that if there is a chain value
    //  of the same name, this is not allowed.
    //
    //  Note that the hzHttpEvent is very short lived. An instance is populated by a HTTP request, passed to the event handler ProcHTTP(), which processes it to
    //  formulate a response. Then one of its member functions is called to write out that response to the client socket. The only purpose in setting a value in
    //  the event's m_mapStrings member, is to influence the output by means of a percent-entity of the form [%e:var_name;] that is assumed to exist in the page
    //  template being served.
    //
    //  Arguments:  1)  name    The variable name.
    //              2)  value   The variable value as a string
    //
    //  Returns:    E_ARGUMENT  If the name is not supplied
    //              E_DUPLICATE If the name is already used
    //              E_OK        If the variable was added
    _hzfunc("hzHttpEvent::SetVariable[2]") ;
    if (!name)
        return hzerr(E_ARGUMENT, "Blank variable names are not allowed") ;
    if (m_mapStrings.Exists(name))
        m_mapStrings[name] = value ;
    else
    {
        if (m_mapChains.Exists(name))
            return hzerr(E_DUPLICATE, "Cannot assign value to an existing chain") ;
        if (m_mapStrings.Insert(name, value) != E_OK)
            return hzerr(E_MEMORY, "Could not insert variable %s", *name) ;
    }
    return E_OK ;
}
hzEcode hzHttpEvent::SetVarChain    (const hzString& name, const hzChain& Z)
{
    //  Sets a chain value to the supplied chain and places it in the hzHttpEvent's map of chain values. Note that if there is a string value
    //  of the same name, this is not allowed.
    //
    //  Note that the hzHttpEvent is very short lived. An instance is populated by a HTTP request, passed to the event handler ProcHTTP(), which processes it to
    //  formulate a response. Then one of its member functions is called to write out that response to the client socket. The only purpose in setting a value in
    //  the event's m_mapChains member, is to influence the output by means of a percent-entity of the form [%e:var_name;] that is assumed to exist in the page
    //  template being served.
    //
    //  Arguments:  1)  name    The variable name.
    //              2)  value   The variable value as a string
    //
    //  Returns:    E_ARGUMENT  If the name is not supplied
    //              E_DUPLICATE If the name is already used
    //              E_OK        If the variable was added
    _hzfunc("hzHttpEvent::SetVar(chain)") ;
    if (!name)
        return hzerr(E_ARGUMENT, "Blank variable names are not allowed") ;
    if (m_mapChains.Exists(name))
    {
        m_mapChains[name].Clear() ;
        m_mapChains[name] = Z ;
    }
    else
    {
        if (m_mapStrings.Exists(name))
            return hzerr(E_DUPLICATE, "Cannot assign value to an existing string") ;
        if (m_mapChains.Insert(name, Z) != E_OK)
            return hzerr(E_MEMORY, "Could not insert variable %s", *name) ;
    }
    return E_OK ;
}
hzEcode hzHttpEvent::GetVar (hzChain& Z, const hzString& name)
{
    //  Append the supplied chain assumed to be HTML output, with the value found in the variable of the supplied name
    //
    //  Arguments:  1)  Z       The chain to be aggregated with the variable's value
    //              2)  name    Of the variable to evaluate
    //
    //  Returns:    E_ARGUMENT  If a variable name is not supplied
    //              E_NOTFOUND  If the named varaible is not found in the event handler's maps
    //              E_OK        If the named variable is appended to the HTML output
    _hzfunc("hzHttpEvent::GetVar") ;
    if (!name)
        return E_ARGUMENT ;
    if (m_mapStrings.Exists(name))  { Z << m_mapStrings[name] ; return E_OK ; }
    if (m_mapChains.Exists(name))   { Z << m_mapChains[name] ; return E_OK ; }
    return E_NOTFOUND ;
}
hzEcode hzHttpEvent::_formhead  (hzChain& Z, HttpRC hrc, hzMimetype mtype, uint32_t nSize, uint32_t nExpires, bool bZip)
{
    //  Formulate HTTP header for outgoing response.
    //
    //  Arguments:  1) Z            The chain to aggregate to
    //              2) hrc          The HTTP return code
    //              3) mtype        The MIME type
    //              4) nSize        The size (content length)
    //              5) nExpires     The number of seconds the page should be considered valid for by the browser (if any)
    //              6) bZip         A boolean directive to state if the yet to be attached content is zipped
    //
    //  Returns:    E_ARGUMENT  If sent an invalid HTTP return code
    //              E_OK            If the operation was successful
    _hzfunc("hzHttpEvent::_formhead") ;
    hzXDate     now ;   //  For header dates
    hzString    S ;     //  Temp string
    Z.Clear() ;
    switch  (hrc)
    {
    case HTTPMSG_OK:                        Z << "HTTP/1.1 200 OK\r\n" ;                        break ;
    case HTTPMSG_NOCONTENT:                 Z << "HTTP/1.1 204 OK\r\n" ;                        break ;
    //  Request is OK but no page supplied because ...
    case HTTPMSG_REDIRECT_PERM:             Z << "HTTP/1.1 301 Temp Redirect\r\n" ;             break ;
    case HTTPMSG_FOUND_GOTO:                Z << "HTTP/1.1 301 Found\r\n" ;                     break ;
    case HTTPMSG_NOT_MODIFIED:              Z << "HTTP/1.1 304 Not Modified\r\n" ;              break ;
    case HTTPMSG_REDIRECT_TEMP:             Z << "HTTP/1.1 307 Temp Redirect\r\n" ;             break ;
    //  Errors in Request (not found or otherwise denied)
    case HTTPMSG_BAD_REQUEST:               Z << "HTTP/1.1 400 Bad Request\r\n" ;               break ;
    case HTTPMSG_UNAUTHORIZED:              Z << "HTTP/1.1 401 Unauthorized\r\n" ;              break ;
    case HTTPMSG_FORBIDDEN:                 Z << "HTTP/1.1 403 Forbidden\r\n" ;                 break ;
    case HTTPMSG_NOTFOUND:                  Z << "HTTP/1.1 404 Not found\r\n" ;                 break ;
    case HTTPMSG_METHOD_NOT_ALLOWED:        Z << "HTTP/1.1 405 Not Allowed\r\n" ;               break ;
    case HTTPMSG_REQUEST_TIME_OUT:          Z << "HTTP/1.1 408 Request Timed Out\r\n" ;         break ;
    case HTTPMSG_GONE:                      Z << "HTTP/1.1 410 Page Gone\r\n" ;                 break ;
    case HTTPMSG_LENGTH_REQUIRED:           Z << "HTTP/1.1 411 Length Required\r\n" ;           break ;
    case HTTPMSG_PRECONDITION_FAILED:       Z << "HTTP/1.1 412 Precondition Failed\r\n" ;       break ;
    case HTTPMSG_ENTITY_TOO_LARGE:          Z << "HTTP/1.1 413 Entity Too Large\r\n" ;          break ;
    case HTTPMSG_REQUEST_URI_TOO_LARGE:     Z << "HTTP/1.1 414 URI Too Large\r\n" ;             break ;
    case HTTPMSG_UNSUPPORTED_MEDIA_TYPE:    Z << "HTTP/1.1 415 Unsupported Media Type\r\n" ;    break ;
    //  System Errors. Can't help you regardless of how reasonable the request!
    case HTTPMSG_INTERNAL_SERVER_ERROR:     Z << "HTTP/1.1 500 Internal Server Error\r\n" ;     break ;
    case HTTPMSG_NOT_IMPLEMENTED:           Z << "HTTP/1.1 501 Not Implimented\r\n" ;           break ;
    case HTTPMSG_BAD_GATEWAY:               Z << "HTTP/1.1 502 Bad Gateway\r\n" ;               break ;
    case HTTPMSG_SERVICE_UNAVAILABLE:       Z << "HTTP/1.1 503 Service Unavailable\r\n" ;       break ;
    case HTTPMSG_VARIANT_ALSO_VARIES:       Z << "HTTP/1.1 506 Variant Also Varies\r\n" ;       break ;
    default:
        m_Error.Printf("Invalid HTTP return code (%d)\n", (uint32_t) hrc) ;
        return E_ARGUMENT ;
    } ;
    now.SysDateTime() ;
    S = now.Txt(FMT_DT_INET) ;
    if (!_hzGlobal_runstart)
        _hzGlobal_runstart = S ;
    Z << "Date: " << S << "\r\n" ;
    Z << "Server: HTTP/1.1 (HadronZoo::Dissemino 9.7, Linux)\r\n" ;
    Z << "Last-Modified: " << _hzGlobal_runstart << "\r\n" ;
    if (!nExpires)
    {
        Z << "Pragma: No-cache\r\n" ;
        Z << "Cache-Control: no-cache\r\n" ;
        Z << "Expires: 0\r\n" ;
    }
    else
    {
        now.altdate(SECOND, nExpires) ;
        S = now.Txt(FMT_DT_INET) ;
        Z.Printf("Expires: %s\r\n", *S) ;
    }
    now.altdate(SECOND, -10000000) ;
    //  If there is a cookie being sent by the browser which is no longer in use this is deleted by DelSessCookie() which sets m_CookieOld to the redundant
    //  submited cookie. Thus if m_CookieOld is set we tell the browser to delete it here
    if (m_CookieOld)
    {
        if (_hzGlobal_Debug & HZ_DEBUG_SERVER)
            m_Report.Printf("Expiring old cookie %016X\n", m_CookieOld) ;
        //Z.Printf("Set-Cookie: %s=%016X; path=/; expires: %s\r\n", *_hzGlobal_HtmlApp->m_CookieName, m_CookieOld, now.Txt(FMT_DT_INET)) ;
        Z.Printf("Set-Cookie: _hz_=%016X; path=/; expires: %s\r\n", m_CookieOld, now.Txt(FMT_DT_INET)) ;
    }
    //  Send a new session cookie
    if (m_CookieNew)
    {
        //  If there is a cookie from the browser and it does not agree with a new cookie from the server, give the directive to the browser to delete it.
        if (m_CookieSub && (m_CookieNew != m_CookieSub))
        {
            if (_hzGlobal_Debug & HZ_DEBUG_SERVER)
                m_Report.Printf("Expiring cookie %016X and setting cookie %016x\n", m_CookieSub, m_CookieNew) ;
            //Z.Printf("Set-Cookie: %s=%016X; path=/; expires: %s\r\n", *_hzGlobal_HtmlApp->m_CookieName, m_CookieOld, now.Txt(FMT_DT_INET)) ;
            Z.Printf("Set-Cookie: _hz_=%016X; path=/; expires: %s\r\n", m_CookieOld, now.Txt(FMT_DT_INET)) ;
        }
        //Z.Printf("Set-Cookie: %s=%016X; path=/\r\n", *_hzGlobal_HtmlApp->m_CookieName, m_CookieNew) ;
        Z.Printf("Set-Cookie: _hz_=%016X; path=/\r\n", m_CookieNew) ;
    }
    //  Send the content location if app has set URI
    if (m_Redirect)
    {
        if (hrc == HTTPMSG_FOUND_GOTO)
            Z.Printf("Location: %s\r\n", *m_Redirect) ;
        else
            Z.Printf("Content-Location: %s\r\n", *m_Redirect) ;
    }
    Z << "Accept-Ranges: bytes\r\n" ;
    if (bZip)
        Z << "Content-Encoding: gzip\r\n" ;
    Z.Printf("Content-Length: %d\r\n", nSize) ;
    if (m_LangCode)
        Z.Printf("Content-Language: %s\r\n", *m_LangCode) ;
    else
        Z.Printf("Content-Language: en-US\r\n") ;
    if (m_HdrsResponse.Count())
    {
        hzList<hzPair>::Iter    ip ;    //  Resident headers iterator
        for (ip = m_HdrsResponse ; ip.Valid() ; ip++)
        {
            Z << ip.Element().name ;
            Z << ": " ;
            Z << ip.Element().value ;
            Z << "\r\n" ;
        }
    }
    if (!m_nConnection)
        Z << "Connection: close\r\n" ;
    else
    {
        //  Z.Printf("keep-alive: timeout=%d\r\n", m_nConnection) ;
        Z << "Connection: close\r\n" ;
        //  Z << "Connection: keep-alive\r\n" ;
    }
    Z << "Content-Type: " << Mimetype2Txt(mtype) << "\r\n" ;
    Z << "X-Powered-By: HadronZoo::Dissemino 9.6\r\n\r\n" ;
    if (_hzGlobal_Debug & HZ_DEBUG_SERVER)
        m_Report << Z ;
    return E_OK ;
}
hzEcode hzHttpEvent::SendRawChain   (HttpRC hrc, hzMimetype type, const hzChain& Data, uint32_t nExpires, bool bZip)
{
    //  Compile a send a HTML response to the HTTP client. The HTML page content is supplied as a hzChain
    //
    //  Arguments:  1)  hrc         The HTTP return code to appear in the header.
    //              2)  type        The MIME type of HTTP message
    //              3)  Data        The page content
    //              4)  nExpires    The expiry time for the page
    //              5)  bZip        A boolean flag to indicate if the content has been zipped. It sets an indicator in the outgoing header.
    //
    //  Returns:    E_ARGUMENT  If any of the arguments are invalid
    //              E_WRITEFAIL If the HTTP response could not be sent to the browser.
    //              E_OK        If the operation was successful.
    //
    _hzfunc("hzHttpEvent::SendRawChain") ;
    hzChain Z ;     //  For building header
    hzEcode rc ;    //  Return code
    rc = _formhead(Z, hrc, type, Data.Size(), nExpires, bZip) ;
    if (rc != E_OK)
        return hzerr(rc, "Could not formulate HTTP header (sock=%d)", m_pCx->CliSocket()) ;
    //Z << Data ;
    if (m_pCx->SendData(Z, Data) != E_OK)
    {
        hzerr(E_WRITEFAIL, "Event %p Failed to send response (size=%d + %d, sock=%d)", this, Z.Size(), Data.Size(), m_pCx->CliSocket()) ;
        return E_WRITEFAIL ;
    }
    return E_OK ;
}
hzEcode hzHttpEvent::SendRawString  (HttpRC hrc, hzMimetype type, const hzString& Content, uint32_t nExpires, bool bZip)
{
    //  Compile a send a HTML response to the HTTP client. The HTML page content is supplied as a hzString
    //
    //  Arguments:  1)  hrc         The HTTP return code to appear in the header.
    //              2)  type        The MIME type of HTTP message
    //              3)  Contyent    The page content
    //              4)  nExpires    The expiry time for the page
    //              5)  bZip        A boolean flag to indicate if the content has been zipped. It sets an indicator in the outgoing header.
    //
    //  Returns:    E_ARGUMENT  If any of the arguments are invalid
    //              E_WRITEFAIL If the HTTP response could not be sent to the browser.
    //              E_OK        If the operation was successful.
    //
    //  Note:       This function is deprecated. Please use hzHttpEvent::SendRawChain instead
    _hzfunc("hzHttpEvent::SendRawString") ;
    hzChain Z ;     //  For building header
    hzEcode rc ;    //  Return code
    rc = _formhead(Z, hrc, type, Content.Length(), nExpires, bZip) ;
    if (rc != E_OK)
        return hzerr(rc, "Could not formulate HTTP header (sock=%d)", m_pCx->CliSocket()) ;
    Z << Content ;
    if (m_pCx->SendData(Z) != E_OK)
    {
        hzerr(E_WRITEFAIL, "hzHttpEvent %p Failed to send response (size=%d, sock=%d)", this, Z.Size(), m_pCx->CliSocket()) ;
        return E_WRITEFAIL ;
    }
    return E_OK ;
}
hzEcode hzHttpEvent::SendFilePage   (const char* cpDir, const char* cpFilename, uint32_t nExpires, bool bZip)
{
    //  This sends a file assumed to be a whole HTML page. The HTML must not contain a server side include as there is no processing in this function to detect
    //  any such construct. The page may of course contain links to other resources as a means to complete rendering by the browser.
    //
    //  Arguments:  1)  cpDir       The directory of the file (can be relative to current dir)
    //              2)  cpFilename  The file name.
    //              3)  nExpires    Expire time for page (for browser use only)
    //              4)  bZip        Flag to indicate if the content is to be zipped
    //
    //  Returns:    E_NOTFOUND  If the directory or filename cannot be accessed.
    //              E_OPENFAIL  If the file could not be opened.
    //              E_NODATA    If the file is empty.
    //              E_WRITEFAIL If the HTML data could not be sent to the browser.
    //              E_OK        If the operation was successful.
    //
    //  Note this function does not use OpenInputStrm to open the file as it does not need the error code detail.
    _hzfunc("hzHttpEvent::SendFilePage") ;
    ifstream        is ;            //  Read file stream
    FSTAT           fs ;            //  File info
    hzChain         Z ;             //  Response for browser is built here
    const char*     pEnd ;          //  Filename extension and hence type
    hzString        Pagename ;      //  Full name (inc path) of page
    hzMimetype      type ;          //  File's HTTP type
    hzEcode         rc ;            //  Return code from sending function
    //  Formulate full path of page and then use lstat
    if (cpDir && cpDir[0])
        { Z << cpDir ; Z.AddByte(CHAR_FWSLASH) ; }
    if (!cpFilename || !cpFilename[0] || (cpFilename[0] == CHAR_FWSLASH && cpFilename[1] == 0))
        Z += "index.html" ;
    else
        Z += cpFilename ;
    Pagename = Z ;
    Z.Clear() ;
    if (lstat(*Pagename, &fs) == -1)
    {
        SendError(HTTPMSG_NOTFOUND, "Could not locate %s\n", *Pagename) ;
        return E_NOTFOUND ;
    }
    //  Determine the type of file so that the correct header can be sent to the browser
    pEnd = strrchr(*Pagename, CHAR_PERIOD) ;
    if (!pEnd)
        type = HMTYPE_TXT_PLAIN ;
    else
        type = Filename2Mimetype(pEnd) ;
    //  Read in the file
    is.open(*Pagename) ;
    if (is.fail())
    {
        SendError(HTTPMSG_NOTFOUND, "Could not open requested file (%s\n", *Pagename) ;
        return E_OPENFAIL ;
    }
    Z += is ;
    is.close() ;
    if (!Z.Size())
    {
        SendError(HTTPMSG_NOTFOUND, "File (%s) of zero size\n", *Pagename) ;
        return E_NODATA ;
    }
    //  If not a html file, no server side includes are possible so just send
    rc = SendRawChain(HTTPMSG_OK, type, Z, nExpires, bZip) ;
    if (rc != E_OK)
    {
        hzerr(E_WRITEFAIL, "Response data not sent to browser (sock=%d)", m_pCx->CliSocket()) ;
        return E_WRITEFAIL ;
    }
    return E_OK ;
}
hzEcode hzHttpEvent::SendFileHead   (const char* cpDir, const char* cpFilename, uint32_t nExpires)
{
    //  Sends a HTML or other file to the browser
    //
    //  Arguments:  1)  cpDir       The directory of the file (can be relative to current dir)
    //              2)  cpFilename  The file name.
    //              3)  nExpires    Expiry date
    //
    //  Returns:    E_NOTFOUND  If the directory or filename cannot be accessed.
    //              E_OPENFAIL  If the file could not be opened.
    //              E_NODATA    If the file is empty.
    //              E_WRITEFAIL If the HTML data could not be sent to the browser.
    //              E_OK        If the operation was successful.
    _hzfunc("hzHttpEvent::SendFileHead") ;
    ifstream    is ;            //  Read file stream
    FSTAT       fs ;            //  File info
    hzXDate     d ;             //  Date for header lines
    hzChain     Z ;             //  Response for browser is built here
    const char* pEnd ;          //  Filename extension and hence type
    hzString    Pathname ;      //  File to load
    uint32_t    nLen = 0 ;      //  File size
    hzMimetype  type ;          //  File's HTTP type
    HttpRC      hrc ;           //  HTTP return code
    hzEcode     rc ;            //  Return code from sending function
    //  Establish real filename, either cpFilename or index.htm(l)
    Pathname = cpDir ;
    if (cpFilename[0] == CHAR_FWSLASH && cpFilename[1] == 0)
        Pathname += "/index.html" ;
    else
    {
        if (cpFilename[0] == CHAR_FWSLASH)
            Pathname += cpFilename ;
        else
        {
            Pathname += "/" ;
            Pathname += cpFilename ;
        }
    }
    if (stat(*Pathname, &fs) == -1)
    {
        hrc = HTTPMSG_NOTFOUND ;
        nLen = 0 ;
    }
    else
    {
        hrc = HTTPMSG_OK ;
        nLen = fs.st_size ;
    }
    //  Determine the type of file so that the correct header can be
    //  sent to the browser
    pEnd = strrchr(*Pathname, CHAR_PERIOD) ;
    if (!pEnd)
        type = HMTYPE_TXT_PLAIN ;
    else
        type = Filename2Mimetype(pEnd) ;
    //  Send the header
    rc = _formhead(Z, hrc, type, nLen, nExpires, false) ;
    if (rc != E_OK)
    {
        hzerr(rc, "Could not formulate HTTP header (sock=%d)", m_pCx->CliSocket()) ;
        return rc ;
    }
    if (m_pCx->SendData(Z) != E_OK)
    {
        hzerr(E_WRITEFAIL, "hzHttpEvent %p Failed to send response to browser (sock=%d)", this, m_pCx->CliSocket()) ;
        return E_WRITEFAIL ;
    }
    return E_OK ;
}
//  FnGrp:  hzHttpEvent::SendHttpHead
//
//  Sends a HTML header only, to the browser
//
//  Arguments:  1)  fixContent  The output string or chain
//              2)  type        The MIME type.
//              3)  nExpiry     The expiry interval
//
//  Returns:    E_NODATA    If the file is empty.
//              E_WRITEFAIL If there was a transmission failure
//              E_OK        If the operation was successful.
//
//  Func:   hzHttpEvent::SendHttpHead(const hzString&,hzMimetype,uint32_t)
//  Func:   hzHttpEvent::SendHttpHead(const hzChain&,hzMimetype,uint32_t)
hzEcode hzHttpEvent::SendHttpHead   (const hzString& fixContent, hzMimetype type, uint32_t nExpires)
{
    _hzfunc("hzHttpEvent::SendHttpHead(str)") ;
    hzChain Z ;     //  Output chain (for head)
    hzEcode rc ;    //  Return code
    if (!fixContent)
        return E_NODATA ;
    rc = _formhead(Z, HTTPMSG_OK, type, fixContent.Length(), nExpires, false) ;
    if (rc != E_OK)
    {
        hzerr(rc, "Could not formulate HTTP header (sock=%d)", m_pCx->CliSocket()) ;
        return rc ;
    }
    if (m_pCx->SendData(Z) != E_OK)
    {
        hzerr(E_WRITEFAIL, "hzHttpEvent %p Failed to send response to browser (sock=%d)", this, m_pCx->CliSocket()) ;
        return E_WRITEFAIL ;
    }
    return rc ;
}
hzEcode hzHttpEvent::SendHttpHead   (const hzChain& fixContent, hzMimetype type, uint32_t nExpires)
{
    _hzfunc("hzHttpEvent::SendHttpHead(ch)") ;
    hzChain Z ;     //  Output chain (for head)
    hzEcode rc ;    //  Return code
    if (!fixContent.Size())
        return E_NODATA ;
    rc = _formhead(Z, HTTPMSG_OK, type, fixContent.Size(), nExpires, false) ;
    if (rc != E_OK)
    {
        hzerr(rc, "Could not formulate HTTP header (sock=%d)", m_pCx->CliSocket()) ;
        return rc ;
    }
    if (m_pCx->SendData(Z) != E_OK)
    {
        hzerr(E_WRITEFAIL, "hzHttpEvent %p Failed to send response to browser (sock=%d)", this, m_pCx->CliSocket()) ;
        return E_WRITEFAIL ;
    }
    return rc ;
}
hzEcode hzHttpEvent::SendPageE  (const char* dir, const char* fname, uint32_t nExpires, bool bZip)
{
    //  Purpose:    Sends a HTML or other file to the browser but from memory. The first time the file is requested it is loaded into
    //              memory and then sent. On subsequent requests it is served from memory.
    //
    //  Arguments:  1)  dir         The directory of the file (can be relative to current dir)
    //              2)  fname       The file name.
    //              3)  nExpires    Expire time for page (for browser use only)
    //              4)  bZip        Flag to indicate if the content is to be zipped
    //
    //  Returns:    E_NOTFOUND  If the directory or filename cannot be accessed.
    //              E_OPENFAIL  If the file could not be opened.
    //              E_NODATA    If the file is empty.
    //              E_WRITEFAIL If the HTML data could not be sent to the browser.
    //              E_OK        If the operation was successful.
    //
    //  Note this function does not use OpenInputStrm to open the file as it does not need the error code detail.
    _hzfunc("hzHttpEvent::SendPageE") ;
    hzChain*        pChain ;        //  Response for browser is built here
    const char*     pEnd ;          //  Filename extension and hence type
    hzString        Pagename ;      //  File to load
    hzString        Filename ;      //  File to load
    hzString        Content ;       //  Page content
    hzMimetype      type ;          //  File's HTTP type
    hzEcode         rc ;            //  Return code from sending function
    if (!dir || !dir[0])
        return hzerr(E_ARGUMENT, "No directory supplied") ;
    if (!fname || !fname[0])
        return hzerr(E_ARGUMENT, "No filename supplied") ;
    //  Lookup resource in page store
    //  Establish real filename, either fname or index.htm(l)
    if (fname[0] != CHAR_FWSLASH)
        Filename = fname ;
    else
    {
        if (fname[1] == 0)
            Filename = "index.html" ;
        else
            Filename = fname + 1 ;
    }
    Pagename = dir ;
    Pagename += "/" ;
    Pagename += Filename ;
    if (s_PageStore.Exists(Pagename))
    {
        pChain = s_PageStore[Pagename] ;
        if (!pChain)
        {
            hzerr(E_CORRUPT, "Null entry in stored page for %s\n", *Pagename) ;
            return E_CORRUPT ;
        }
    }
    else
    {
        //  Check directory and filename of resource
        ifstream    is ;            //  Read file stream
        FSTAT       fs ;            //  File info
        if (stat(*Pagename, &fs) == -1)
        {
            //return hzerr(E_NOTFOUND, "could not locate (%s)", *Pagename) ;
            SendError(HTTPMSG_NOTFOUND, "Could not locate %s\n", *Pagename) ;
            return E_NOTFOUND ;
        }
        if (fs.st_size == 0)
        {
            //return hzerr(E_NODATA, "File (%s) of zero size!", *Pagename) ;
            SendError(HTTPMSG_NOTFOUND, "No content available for file %s\n", *Pagename) ;
            return E_NODATA ;
        }
        pChain = new hzChain() ;
        //  Read in the file
        is.open(*Pagename) ;
        if (is.fail())
            return hzerr(E_OPENFAIL, "Could not open requested file (%s)", *Pagename) ;
        *pChain += is ;
        is.close() ;
        if (pChain->Size() == 0)
            return hzerr(E_MEMORY, "Could not load file (%s)", *Pagename) ;
        //  Check chain has loaded
        if (s_PageStore.Insert(Pagename, pChain) != E_OK)
            return hzerr(E_MEMORY, "Could not store file (%s)", *Pagename) ;
    }
    //  Determine the type of file so that the correct header can be
    //  sent to the browser
    pEnd = strrchr(*Filename, CHAR_PERIOD) ;
    if (!pEnd)
        type = HMTYPE_TXT_PLAIN ;
    else
        type = Filename2Mimetype(pEnd) ;
    //  If not a html file, no server side includes are possible so just send
    rc = SendRawChain(HTTPMSG_OK, type, *pChain, nExpires, bZip) ;
    if (rc != E_OK)
        return hzerr(E_WRITEFAIL, "Response data not sent to browser (size=%d, sock=%d)", pChain->Size(), m_pCx->CliSocket()) ;
    return E_OK ;
}
hzEcode hzHttpEvent::Redirect   (const hzUrl& url, uint32_t nExpires, bool bZip)
{
    //  Send a temporary redirection to the browser
    //
    //  Arguments:  1)  url         The redirection URL
    //              2)  nExpires    Expire time for page (for browser use only)
    //              3)  bZip        Flag to indicate if the content is to be zipped
    //
    //  Returns:    E_WRITEFAIL If the error message could not be sent
    //              E_OK        If the operation was successful.
    _hzfunc("hzHttpEvent::Redirect") ;
    hzChain     Z ;     //  Output chain
    m_Redirect = url ;
 
    Z << "<html>\n<head>\n<title>Moved</title>\n" ;
    Z << "</head>\n<body>\n<h1>Moved</h1>\n" ;
    Z.Printf("<p>You are being redirected. Please <a href=\"%s\">click here</a></p>\n", *url) ;
    Z << "</body>\n</html>\n" ;
    if (SendRawChain(HTTPMSG_FOUND_GOTO, HMTYPE_TXT_HTML, Z, nExpires, bZip) != E_OK)
    {
        hzerr(E_WRITEFAIL, "Response data not sent (size=%d, sock=%d)", Z.Size(), m_pCx->CliSocket()) ;
        return E_WRITEFAIL ;
    }
    return E_OK ;
}
hzEcode hzHttpEvent::SendNotFound   (hzUrl& url)
{
    //  Purpose:    Send a 404 not found message to browser. This message will be a default message if the global variable g_WebPageNotFound is not
    //              set within an application.
    //
    //  Arguments:  1)  url     The resource (page) that was not found
    //
    //  Returns:    E_WRITEFAIL If the error message could not be sent
    //              E_OK        If the operation was successful.
    _hzfunc("hzHttpEvent::SendNotFound") ;
    hzChain Z ;     //  Output chain
    Z <<
    "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 5.0//EN\">\n"
    "<html><head>\n"
    "<title>404 Not Found</title>\n"
    "</head><body>\n"
    "<h1>Not Found</h1>\n"
    "<p>The requested URL " << url << " was not found on this server.</p>\n"
    "<hr>\n" ;
    Z.Printf("<address>HadronZoo Internet: %s Port %d</address>\n", *url.Domain(), url.Port()) ;
    Z << "</body></html>\n" ;
    if (SendRawChain(HTTPMSG_NOTFOUND, HMTYPE_TXT_HTML, Z, 0, false) != E_OK)
    {
        hzerr(E_WRITEFAIL, "Response data not sent (size=%d, sock=%d)", Z.Size(), m_pCx->CliSocket()) ;
        return E_WRITEFAIL ;
    }
    return E_OK ;
}
hzEcode hzHttpEvent::SendError  (HttpRC hrc, const char* va_alist ...)
{
    //  Purpose:    Send a general error message to browser
    //
    //  Arguments:  1)  hrc         HTTP return code
    //              2)  va_alist    Varaiable printf style arguments
    //
    //  Returns:    E_WRITEFAIL If the error message could not be sent
    //              E_OK        If the operation was successful.
    _hzfunc("hzHttpEvent::SendError") ;
    va_list     ap ;    //  Variable argument list
    const char* fmt ;   //  Format control
    hzChain     Z ;     //  Output chain
    va_start(ap, va_alist) ;
    fmt = va_alist ;
    Z << "<html>\n<head>\n<title>HadronZoo Internet Error Report</title>\n\n" ;
    Z << "<style type=\"text/css\">\n" ;
    Z << "<!--\n" ;
    Z << ".a1 {text-decoration:none; font-family:arial; font-size:24px; font-weight:bold; color:#FFFFFF;}\n" ;
    Z << ".a2 {text-decoration:none; font-family:verdana; font-size:13px; font-weight:bold; color:#000000;}\n" ;
    Z << ".a3 {text-decoration:none; font-family:verdana; font-size:12px; font-weight:bold; color:#000000;}\n" ;
    Z << "-->\n" ;
    Z << "</style>\n" ;
    Z << "</head>\n" ;
    Z += "<body bgcolor=#CCCCCC>\n" ;
    Z << "<table width=100% border=0 cellspacing=0 cellpadding=0>\n" ;
    Z << "<tr height=100 bgcolor=#000000>\n" ;
    Z << " <td align=center class=a1>HadronZoo Internet Error Report<td>\n" ;
    Z << "</tr>\n" ;
    Z << "<tr height=400 bgcolor=#F0FFF0>\n" ;
    Z << " <td align=center class=a2>" ;
    Z._vainto(fmt, ap) ;
    Z << "</td>\n" ;
    Z << "</tr>\n" ;
    Z << "<tr height=50 bgcolor=#CCCCCC>\n" ;
    Z << " <td align=center class=a3>Powered by HadronZoo</td>\n" ;
    Z << "</tr>\n" ;
    Z << "</table>\n" ;
    Z << "</body>\n" ;
    Z << "</html>\n" ;
    if (SendRawChain(hrc, HMTYPE_TXT_HTML, Z, 0, false) != E_OK)
    {
        hzerr(E_WRITEFAIL, "Response data not sent (size=%d, sock=%d)", Z.Size(), m_pCx->CliSocket()) ;
        return E_WRITEFAIL ;
    }
    return E_OK ;
}
hzEcode hzHttpEvent::SendAjaxResult (HttpRC hrc)
{
    //  Send a HTTP response code only.
    //
    //  Note: This function should only be invoked in response to AJAX HTTP requests
    //
    //  Arguments:  1)  hrc         HTTP return code
    //
    //  Returns:    E_WRITEFAIL If the response could not be sent
    //              E_OK        If the AJAX result was sent
    _hzfunc("hzHttpEvent::SendCmdResult(1)") ;
    hzChain Z ;     //  For building header
    hzEcode rc ;    //  Return code
    rc = _formhead(Z, hrc, HMTYPE_TXT_HTML, 0, 0, false) ;
    if (rc != E_OK)
    {
        hzerr(rc, "Could not formulate HTTP header (sock=%d)", m_pCx->CliSocket()) ;
        return rc ;
    }
    if (m_pCx->SendData(Z) != E_OK)
    {
        hzerr(E_WRITEFAIL, "hzHttpEvent %p Failed to send response (size=%d, sock=%d)", this, Z.Size(), m_pCx->CliSocket()) ;
        return E_WRITEFAIL ;
    }
    return E_OK ;
}
hzEcode hzHttpEvent::SendAjaxResult (HttpRC hrc, const char* va_alist ...)
{
    //  Send a string, without headers of any kind, as a response.
    //
    //  Note: This function should only be invoked in response to AJAX HTTP requests
    //
    //  Arguments:  1)  hrc         HTTP return code
    //              2)  va_alist    Varaiable printf style arguments
    //
    //  Returns:    E_WRITEFAIL If the response could not be sent
    //              E_OK        If the AJAX result was sent
    _hzfunc("hzHttpEvent::SendCmdResult(2)") ;
    va_list     ap ;    //  Variable argument list
    const char* fmt ;   //  Format control
    hzChain     Z ;     //  Output chain
    va_start(ap, va_alist) ;
    fmt = va_alist ;
    Z._vainto(fmt, ap) ;
    if (SendRawChain(hrc, HMTYPE_TXT_HTML, Z, 0, false) != E_OK)
    {
        hzerr(E_WRITEFAIL, "Response data not sent (size=%d, sock=%d)", Z.Size(), m_pCx->CliSocket()) ;
        return E_WRITEFAIL ;
    }
    return E_OK ;
}
hzEcode hzHttpEvent::SendAjaxResult (HttpRC hrc, hzChain& Z)
{
    //  Send a string, without headers of any kind, as a response.
    //
    //  Note: This function should only be invoked in response to AJAX HTTP requests
    //
    //  Arguments:  1)  hrc     HTTP return code
    //              2)  Z       Varaiable printf style arguments
    //
    //  Returns:    E_WRITEFAIL If the response could not be sent
    //              E_OK        If the AJAX result was sent
    _hzfunc("hzHttpEvent::SendCmdResult(3)") ;
    if (SendRawChain(hrc, HMTYPE_TXT_HTML, Z, 0, false) != E_OK)
    {
        hzerr(E_WRITEFAIL, "Response data not sent (size=%d, sock=%d)", Z.Size(), m_pCx->CliSocket()) ;
        return E_WRITEFAIL ;
    }
    return E_OK ;
}