/***************************************************************************
 *                                                                         *
 *   GALACTH.CPP                                                           *
 *                                                                         *
 *   Copyright (c) 1996-1997 Galacticomm, Inc.    All rights reserved.     *
 *                                                                         *
 *   Active HTML kernel.                                                   *
 *                                                                         *
 *                                                - RNStein  8/6/96        *
 *                                                                         *
 ***************************************************************************/

#define _RWSTDDLL                  // avoid STL string constructor blowups
#include "gcomm.h"
#include "majorbbs.h"
#include <stdcomp.h>               // Avoids linker duplicate symbol warnings
#define RWSTD_NO_BOOL              // related to vector<bool> with galact.cpp
#include <vector>
#include <string>
#include <fstream.h>
#include "iterand.h"
using namespace std;
class acthParamMgr;
#define ACTHUVECTOR std::vector<const CHAR *>
#define ACTHPVECTOR acthParamMgr
#include "galacth.h"
#include "parmmgr.h"
#include "qrystg.h"
#include "mimeb64.h"
#include "mimectyp.h"
#include "phasedbg.h"
#include "objdata.h"
#include "opt.h"
#include "tvb.h"

#if defined( DEBUGTRACE )
#define TRACE(s,t) shocst("GALACTH", (s), (t))
#else
#define TRACE(s,t)   ((void)0)
#endif

#pragma warn -par                  // supresses warning from memory.h line 208

#define FILREV "$Revision: 31 $"

#define PPFIX "galacth/"           // path prefix for static files

typedef vector<acthAgent *> AGENTVEC; // vector of agents
AGENTVEC *pfxAgents=NULL;          // vector of agents w/URL prefixes
AGENTVEC *unvAgents=NULL;          // vector of universal agents

const INT BUFBUMPSIZE = 1024;      // bytes to increase full buffers by

static
UIDFACTORY *uidfactory=NULL;       // User-ID factory, implementation-specific

class tvbBasic : public tvbDefinition { // basic text variable

     CHAR const * m_val;           // value holder

public:
     tvbBasic(                     // construct
     CHAR const * nam) :
          tvbDefinition(nam)
     {
          registerDef();
          clr();
     }

     ~tvbBasic()                   // destroy
     {}

     VOID
     set(                          // set value
     CHAR const * val)             //   to this (must remain during use)
     {
          m_val=val;
     }

     VOID
     clr()                         // clear value
     {
          m_val="";
     }

     CHAR const *
     resolve(                      // resolve variable (called by processor)
     CHAR const * nam)
     {
          return(m_val);
     }
};

tvbBasic tvbRequestUrl("ACTH_ORIGINAL_URL");

///////////////////////////////////////////////////////////////////////////
// Configurable option data

INT AuthTypeFlags=ATF_BROWSER|ATF_COOKIE;    // allowed authentication types
INT AuthDefault=ATF_BROWSER;                 // default authentication type
CHAR AuthRealm[MAXREALM]="Worldgroup";       // authentication realm
CHAR AuthCookie[MAXCOOKIE]="WebLiteAuth";    // authentication cookie name

///////////////////////////////////////////////////////////////////////////
// Extra session data handling

struct SessionExtraData {          // additional session data
     AUTHTYPE AuthType;            //   type of authentication used
     AUTHMETH AuthMeth;            //   authentication sub-type/method

     SessionExtraData()            // initialize members
     {
          AuthType=AT_UNKNOWN;
          AuthMeth=AM_UNKNOWN;
     }
};

ObjectDataHolder<acthSession,SessionExtraData> SesDataHold;
                                   // holder for extra session data

///////////////////////////////////////////////////////////////////////////

MARKSOURCE(galacth)

///////////////////////////////////////////////////////////////
// KERNEL'S CLASSES (acthSession, acthOstreambuf acthUserID) //
///////////////////////////////////////////////////////////////

acthSession::acthSession(          // constructor, begin a new session
acthRequest *_req,                 //   already-instantiated sssRequest
acthResponse *_rsp) :              //   already-instantiated sssResponse
     req(_req),
     rsp(_rsp),
     agt(NULL),
     syn(NULL),
     usr(NULL),
     authChecked(FALSE),
     _host(NULL),
     _urlsfx(NULL),
     urlv(NULL),
     moretogo(TRUE),
#ifndef WEBCAST
     usrfail(USERUNKNOWN),
#endif // WEBCAST
     urlparst(NULL)
{
     parmv=new ACTHPVECTOR(_req),
     osb=new acthOstreambuf(this);
     boutp=new ostream(osb);
     ::SesDataHold.add(this);
}

acthSession::~acthSession()        // destructor, session over
{
     ::SesDataHold.del(this);
     if (syn != NULL) {
          delete syn;
          syn=NULL;
     }
     if (usr != NULL) {
          delete usr;
          usr=NULL;
     }
     if (boutp != NULL) {
          delete boutp;
          boutp=NULL;
     }
     if (osb != NULL) {
          delete osb;
          osb=NULL;
     }
     if (_urlsfx != NULL) {
          delete[] _urlsfx;
          _urlsfx=NULL;
     }
     if (_host != NULL) {
          delete[] _host;
          _host=NULL;
     }
     if (urlv != NULL) {
          delete urlv;
          urlv=NULL;
          delete[] ualc;
     }
     freereqStuff();
}

VOID
acthSession::freereqStuff()        // preparations for req->freereq()
{
     if (parmv != NULL) {
          delete parmv;
          parmv=NULL;
     }
}

GBOOL                              //   returns TRUE=someone, FALSE=no one
acthSession::claim()               // check if an agent will handle resource
{
     GBOOL retval=FALSE;

     ASSERT(req != NULL);
     if (findAgent()) {
          retval=TRUE;
     }
     return(retval);
}

GBOOL
acthSession::proceed()             // handle ses, TRUE=call again, FALSE=done
{
     if (moretogo) {

          // parse parameters
          if (!parmv->isReady()) {
               return(TRUE);
          }

          // check authentication
          if (!authChecked) {
               authCheck();
               authChecked=TRUE;
          }

          // allocate synthesis
          if (syn == NULL) {
               ASSERT(agt != NULL);
               BEG_PHASE("agt->newSynthesis",agt);
               syn=agt->newSynthesis(this);
               END_PHASE("agt->newSynthesis",0);
          }

          // synthesis processing
          ASSERT(syn != NULL);
          /* The following phase transition uses intimate knowledge of the *
           * structure of the vtable for the synthesis class in order to   *
           * get the address of the proceed() member function about to be  *
           * called.  This is highly non-portable and may break at any     *
           * time, without notice.  Unfortunately, there seems to be no    *
           * other way to do it.                                           */
          BEG_PHASE("syn->proceed",(*((LONG **)syn))[1]);
          ACTHCODE rc=syn->proceed();
          END_PHASE("syn->proceed",rc);
          syn->postproceed();
          switch (rc) {
          case ACTHNOTFND:
               rsp->notFound();
               break;
          case ACTHFORBID:
               setStatus("403 Forbidden");
               contypeMIME("text/plain");
               *boutp << "403 Forbidden" << crlf;
               break;
          case ACTHNOANON:
#ifndef WEBCAST
               if (usrfail == USERSUSPENDED) {
                    sndfile(PPFIX "suspend.htm");
               }
               else if (AuthDefault == ATF_BROWSER) {
                    setStatus("401 Unauthorized");
                    ostrstream ost;
                    ost << "Basic realm=\"" << AuthRealm << "\"" << ends;
                    headerField("WWW-Authenticate",ost.str());
                    ost.rdbuf()->freeze(0);
                    sndfile(PPFIX "unauth.htm");
               }
               else if (AuthDefault == ATF_COOKIE) {
                    ifstream ist(PPFIX "login.htm",ios::in|ios::binary);
                    if (::sameas("GET",method())) {
                         ::tvbRequestUrl.set(url());
                    }
                    else {
                         ::tvbRequestUrl.clr();
                    }
                    tvbTranslation trans(ist,*boutp);
                    trans.proceed();
                    ::tvbRequestUrl.clr();
               }
#if defined(DEBUG)
               else {
                    ASSERT(FALSE);
               }
#endif // DEBUG
#else // WEBCAST
               setStatus("401 Unauthorized");
               headerField("WWW-Authenticate: Basic realm=\"Worldgroup\"");
               sndfile(PPFIX "unauth.htm");
#endif // WEBCAST
               break;
          case ACTHDONE:
               break;
          case ACTHMORE:
               break;
          }
          moretogo=ACTHMORETOGO(rc);
          return(TRUE);
     }
     else {
          boutp->flush();
          return(boutp->rdbuf()->out_waiting() != 0);
     }
}

VOID
acthSession::abort()               // server -> kernel, request died
{                                  //   (expect no more proceed() calls)
     if (syn != NULL) {
          syn->abort();
     }
}

ULONG                              //   network order: 0x01020304 => 1.2.3.4
acthSession::clientIP()            // IP address of remote client
{
     return(req->clientIP());
}

USHORT                             //   usually 1024 to 65535
acthSession::clientPort()          // TCP port number of remote client
{
     return(req->clientPort());
}

INT
acthSession::age()                 // age of connection in seconds
{
     return(req->age());
}

VOID
acthSession::freereq()             // done with request information
{
     freereqStuff();
     req->freereq();
}

const CHAR *
acthSession::host()                // request host, eg "www.company.com"
{
     if (_host == NULL) {
          CHAR hbuf[256];
          const CHAR *hptr;
          if (header("Host",hbuf,sizeof(hbuf))) {
               hptr=hbuf;
          }
          else {
               hptr=mainHost();
          }
          _host=strcpy(new CHAR[strlen(hptr)+1],hptr);
     }
     return(_host);
}

const CHAR *
acthSession::urlsfx()              // e.g. "sub/index.htm" (suffix)
{
     if (_urlsfx == NULL) {
          const CHAR *uwhole=url();
          const CHAR *usuffix;

          if (*uwhole == '/') {
               uwhole++;
          }
          if (agt != NULL
           && agt->urlpfx() != NULL
           && sameto(agt->urlpfx(),uwhole)) {
               usuffix=uwhole+strlen(agt->urlpfx());
               if (*usuffix == '/') {
                    usuffix++;
               }
          }
          else {
               usuffix=uwhole;
          }
          INT nsfx=strlen(usuffix);
          const CHAR *qpos;
          if ((qpos=strchr(usuffix,'?')) != NULL) {
               nsfx=static_cast<INT>(qpos-usuffix);
          }
          _urlsfx=stlcpy(new CHAR[nsfx+1],usuffix,nsfx+1);
     }
     return(_urlsfx);
}

INT
acthSession::urlargc()             // parse suffix, return no. path elements
{
     if (urlv == NULL) {
          urlv=new ACTHUVECTOR;
          const CHAR *uorg=urlsfx();
          ualc=strcpy(new CHAR[strlen(uorg)+1],uorg);
          for (const CHAR *up=strtok(ualc,"/") ; up != NULL ;
                           up=strtok(NULL,"/")) {
               urlv->push_back(up);
          }
     }
     return(urlv->size());
}

CHAR *                             //   returns copy of namval
acthSession::urlargv(              // return path element 0 to urlargc()-1
INT index,                         //   index 0 to urlargc()-1
CHAR *namval,                      //   where to store path-fragment of URL
INT size)                          //   room for pair (incl '\0')
{
     ASSERT(urlv != NULL);
     ASSERT(0 <= index && index < urlargc());
     return(stlcpy(namval,(*urlv)[index],size));
}

const CHAR *
acthSession::urlargv(              // return path element 0 to urlargc()-1
INT index)                         //   index 0 to urlargc()-1
{
     return((*urlv)[index]);
}

PARAMENC                           //   returns encoding type code
acthSession::paramEncoding()       // get type of encoding used for parameters
{
     return(parmv->paramEncoding());
}


INT
acthSession::nparam()              // number of parameters
{
     return(parmv->nparam());
}

VOID
acthSession::paramInit()           // initialize parameter tracking vector
{
     // this function is obsolete, but is retained for compatibility
}

GBOOL                              //   TRUE=parameter specified, FALSE=missing
acthSession::param(                // get parameter by name (FALSE=no such)
const CHAR *name,                  //   name of parameter (eg "parm")
CHAR *value,                       //   where to store value (NULL=don't)
INT size)                          //   room for value (incl '\0')
{
     return((GBOOL)(parmv->param(name,value,size)));
}

INT                                //   returns length (not incl term)
acthSession::param(                // get parameter by index
INT index,                         //   0 to nparam()-1
CHAR *name,                        //   where to put name (eg "parm")
INT namsiz,                        //   room for name (incl '\0')
CHAR *value,                       //   where to put value (eg "2")
INT valsiz)                        //   room for value (incl '\0')
{
     return(parmv->param(index,name,namsiz,value,valsiz));
}

bool                               //   true=parameter specified, false=missing
acthSession::paramStreamOpen(      // get parameter input stream by name
CHAR const * name,                 //   name of parameter (case ignored)
istream ** ppstParam)              //   buffer to receive stream pointer
{
     return(parmv->paramStreamOpen(name,ppstParam));
}

VOID
acthSession::paramStreamOpen(      // get parameter input stream by index
INT index,                         //   0 to nparam()-1
istream ** ppstParam)              //   buffer to receive stream pointer
{
     parmv->paramStreamOpen(index,ppstParam);
}

VOID
acthSession::paramStreamClose(     // finish up use of parameter stream
istream * pstParam)                //   stream pointer from paramStreamOpen
{
     parmv->paramStreamClose(pstParam);
}

GBOOL                              //   TRUE=found, FALSE=none
acthSession::paramIndex(           // get index of parameter name
const CHAR *name,                  //   name of parameter (eg "parm")
INT& idx)                          //   answer, 0 to nparam()-1
{
     return((GBOOL)(parmv->paramIndex(name,idx)));
}

INT                                //   room needed to store param (incl term)
acthSession::paramRoom(            // room needed to decode entire param
const CHAR *name)                  //   name of parameter (eg "parm")
{
     return(parmv->paramRoom(name));
}

INT                                //   room needed to store param (incl term)
acthSession::paramRoom(            // room needed to decode entire param
INT index)                         //   0 to nparam()-1
{
     return(parmv->paramRoom(index));
}

INT
acthSession::paramNumHeaders(      // get number of parameter headers
INT index) const                   //   0 to nparam()-1
{
     return(parmv->paramNumHeaders(index));
}

size_t                             //   returns length of value (less term.)
acthSession::paramHeader(          // get a parameter header
INT index,                         //   index of parameter (0 to nparam()-1)
INT hdrIndex,                      //   index of header
CHAR * namBuf,                     //   buffer to receive name
size_t namSiz,                     //   size of name buffer
CHAR * valBuf,                     //   buffer to receive result
size_t valSiz) const               //   size of buffer
{
     return(parmv->paramHeader(index,hdrIndex,namBuf,namSiz,valBuf,valSiz));
}

bool                               //   returns true if header exists
acthSession::paramHeader(          // get a parameter header
INT index,                         //   index of parameter (0 to nparam()-1)
CHAR const * hdrName,              //   name of header
CHAR * valBuf,                     //   buffer to receive result
size_t valSiz) const               //   size of buffer
{
     return(parmv->paramHeader(index,hdrName,valBuf,valSiz));
}

INT
acthSession::paramNumHeaders(      // get number of parameter headers
CHAR const * name) const           //   name of parameter (eg "parm")
{
     return(parmv->paramNumHeaders(name));
}

bool                               //   returns true if parameter exists
acthSession::paramHeader(          // get a parameter header
CHAR const * name,                 //   name of parameter (case ignored)
INT hdrIndex,                      //   index of header
CHAR * namBuf,                     //   buffer to receive name
size_t namSiz,                     //   size of name buffer
CHAR * valBuf,                     //   buffer to receive result
size_t valSiz) const               //   size of buffer
{
     return(parmv->paramHeader(name,hdrIndex,namBuf,namSiz,valBuf,valSiz));
}

bool                               //   returns true if parameter/header exist
acthSession::paramHeader(          // get a parameter header
CHAR const * name,                 //   name of parameter (case ignored)
CHAR const * hdrName,              //   name of header
CHAR * valBuf,                     //   buffer to receive result
size_t valSiz) const               //   size of buffer
{
     return(parmv->paramHeader(name,hdrName,valBuf,valSiz));
}

INT
acthSession::vernum()              // 0=HTTP/0.9, 100=HTTP/1.0, 101=HTTP/1.1
{
     return(req->vernum());
}

GBOOL
acthSession::header(               // get hdr field by name (FALSE=no such)
const CHAR *name,                  //   name of field (eg "Accept")
CHAR *value,                       //   where to put value (eg "*/*")
INT valsiz)                        //   room for value (incl '\0')
{
     const CHAR *hdr;

     INT nlen=strlen(name);
     if (nlen > 0 && name[nlen-1] == ':') {
          nlen--;                  // allow name to end in ":"
     }
     for (hdr=req->hdr1st() ; hdr != NULL ; hdr=req->hdrnxt()) {
          if (sameto(name,hdr) && hdr[nlen] == ':') {
               stlcpy(value,skpwht(hdr+nlen+1),valsiz);
               return(TRUE);
          }
     }
     return(FALSE);
}

const CHAR *
acthSession::method()              // e.g. "GET"
{
     return(req->method());
}

const CHAR *
acthSession::url()                 // e.g. "/dynamic/app/index.htm?parm=2"
{
     return(req->url());
}

const CHAR *
acthSession::version()             // e.g. "HTTP/1.0"
{
     return(req->version());
}

const CHAR *
acthSession::hdr1st()              // e.g. "Host: www.abc.com" (NULL=none)
{
     return(req->hdr1st());
}

const CHAR *
acthSession::hdrnxt()              // e.g. "Accept: */*" ... (NULL=no more)
{
     return(req->hdrnxt());
}

LONG
acthSession::lenBody()             // length of request's entity-body
{
     return(req->lenBody());
}

istream&
acthSession::bodyin()              // request entity body input stream
{
     return(req->bodyin());
}

VOID
acthSession::headerFollows()       // agent makes header ("nph" in CGI lingo)
{
     rsp->headerFollows();
}

VOID
acthSession::setStatus(            // specify status (ala CGI "Status:" hdr)
const CHAR *sts)                   //   e.g. "200 OK"
{
     rsp->setStatus(sts);
}

VOID
acthSession::redirect(             // redirect request to another URL (sts 302)
const CHAR *url)                   //   full URL of alternate resource
{
     rsp->redirect(url);
}

GBOOL                              //   TRUE=redir to new URL, FALSE=ok as is
acthSession::forceDir()            // force URL to be a directory (end in "/")
{
     const CHAR *urlfull,*qpos;
     ostrstream ost;

     ost << "http://" << host();
     if ((qpos=strchr(urlfull=url(),'?')) == NULL) {
          if (samend(urlfull,"/")) {
               return(FALSE);           // ends in slash, ok as is
          }
          ost << urlfull << "/";
     }
     else {
          INT beforq=qpos-urlfull;
          if (beforq > 0) {
               if (qpos[-1] == '/') {
                    return(FALSE);      // slash before '?', ok as is
               }
               ost.write(urlfull,beforq);
          }
          ost << "/" << qpos;
     }
     ost << ends;
     redirect(ost.str());
     ost.rdbuf()->freeze(0);
     return(TRUE);
}

VOID
acthSession::contypeFext(          // specify content type by file extension
const CHAR *fext)                  //   e.g. "htm"
{
     rsp->contypeFext(fext);
}

VOID
acthSession::contypeMIME(          // specify Content-type header field value
const CHAR *mimetype)              //   e.g. "text/html" (default: text/html)
{
     rsp->contypeMIME(mimetype);
}

VOID
acthSession::addlVersion(          // append product version to Server header
const CHAR *prodver)               //   product and version, e.g. "Zhoots/6.0"
{
     rsp->addlVersion(prodver);
}

VOID
acthSession::headerField(          // inject custom header field into response
const CHAR *field)                 //   e.g. "Who-knows: what"
{
     rsp->headerField(field);
}

VOID
acthSession::headerField(          // inject custom header field into response
const CHAR *field,                 //   e.g. "Who-knows: what"
const CHAR *value)
{
     rsp->headerField(field, value);
}

VOID
acthSession::setUser(              // set authentication cookie
const CHAR *uid,                   //   User-ID
const CHAR *psw)                   //   password
{
     if (!(AuthTypeFlags&ATF_COOKIE)) {
          return;
     }
     ostrstream unp;
     unp << uid << ":" << psw << ends;
     CHAR buff64[256];
     base64nc(unp.str(),buff64, sizeof(buff64));
     unp.rdbuf()->freeze(0);
     ostrstream ost;
     ost << AuthCookie << "=\"" << buff64 << "\"; path=/" << ends;
     headerField("Set-Cookie",ost.str());
     ost.rdbuf()->freeze(0);
}

VOID
acthSession::clrUser()             // clear authentication cookie
{
     ostrstream ost;
     ost << AuthCookie << "=; Path=/; Max-Age=0";
     headerField("Set-Cookie",ost.str());
     ost.rdbuf()->freeze(0);
}

INT                                //   number of bytes actually sent
acthSession::sndrsp(               // send reply (portion) to Web browser (BIN)
const CHAR *data,                  //   data to send
INT nbytes)                        //   number of bytes
{
     return(rsp->sndrsp(data,nbytes));
}

INT                                //   number of bytes actually sent
acthSession::sndrsp(               // send reply (portion) to Web browser (BIN)
const CHAR *data)                  //   string to send ('\0' terminated)
{
     return(rsp->sndrsp(data));
}

GBOOL                              //   TRUE=begun FALSE="not found" reported
acthSession::sndfile(              // send file as entity body
const CHAR *fpath)                 //   path of file (type inferred from ext)
{
     return(rsp->sndfile(fpath));
}

GBOOL                              //   TRUE=begun FALSE="not found" reported
acthSession::sndfile(              // send file as entity body
const CHAR *fpath,                 //   path of file
const CHAR *mimetype)              //   MIME type of file (e.g. "text/html")
{
     return(rsp->sndfile(fpath,mimetype));
}

LONG
acthSession::sndfileCount()        // bytes sent by most recent sndfile()
{
     return(rsp->sndfileCount());
}

const CHAR *
acthSession::mainHost()            // server main domain (eg "www.gcomm.com")
{
     return(rsp->mainHost());
}

const CHAR *
acthSession::serverVer()           // server's version (eg "Worldgroup/2.1")
{
     return(rsp->serverVer());
}

acthUserID *
acthSession::getUser()             // get user information, or NULL=anonymous
{
     return(usr);
}

#if !defined(WEBCAST)

AUTHSTAT
acthSession::getUserAuthFailure()
{
     return(usrfail);
}

AUTHTYPE
acthSession::getUserAuthType() const // get auth type (browser/cookie)
{
     return(::SesDataHold.get(this)->AuthType);
}

AUTHMETH
acthSession::getUserAuthMeth() const // get authentication method (basic)
{
     return(::SesDataHold.get(this)->AuthMeth);
}

#endif // WEBCAST

GBOOL                              //   TRUE=found, FALSE=not found
acthSession::findAgent()           // find an agent who will claim to handle
{                                  //   (agt implicit output)

     if (pfxAgents == NULL) {
          return(FALSE);
     }
     AGENTVEC::iterator avi;
     GBOOL pfound=FALSE,ufound=FALSE;
     for (avi=pfxAgents->begin() ; avi != pfxAgents->end() ; avi++) {
          if ((*avi)->isUniversal()) {
               if ((*avi)->claim(this) && !ufound) {
                    ufound=TRUE;        // 1st universal agent to claim
                    agt=*avi;           // session, handles it
               }
          }
          else {
               if ((*avi)->claim(this) && !pfound && !ufound) {
                    pfound=TRUE;        // 1st prefix agent to claim session,
                    agt=*avi;           // if rejected by all univeral agents,
               }                        // handles it
          }
     }
     return(ufound || pfound);
}

VOID
acthSession::authCheck()           // check authentication
{
     ASSERT(usr == NULL);
     CHAR vbuf[256];
     SessionExtraData * pXInf=::SesDataHold.get(this);
     if (AuthTypeFlags&ATF_BROWSER) {
          if (header("Authorization",vbuf,sizeof(vbuf)) && sameto("Basic ",vbuf)) {
               authProcess(vbuf+6);
               if (usr != NULL) {
                    pXInf->AuthType=AT_BROWSER;
                    pXInf->AuthMeth=AM_BASIC;
               }
               return;
          }
     }
     if (AuthTypeFlags&ATF_COOKIE) {
          for (CHAR const * hdr=hdr1st() ; hdr != NULL ; hdr=hdrnxt()) {
               TRACE("Header line: %s\n",hdr);
               if (sameto("Cookie:", hdr)) {
                    string tmp(hdr+CSTRLEN("Cookie:"));
                    string::size_type bpos, epos;
                    for (bpos=0 ; bpos < tmp.length() ; bpos=epos+1) {
                         epos=tmp.find(';',bpos);
                         if (epos == string::npos) {
                              epos=tmp.length();
                         }
                         bpos=tmp.find_first_not_of(" \t",bpos);
                         TRACE("Cookie crumb: %s\n"
                              ,tmp.substr(bpos,epos-bpos).c_str());
                         CHAR CookieTest[MAXCOOKIE+1];
                         ::stlcpy(CookieTest,AuthCookie,sizeof(CookieTest));
                         ::stlcat(CookieTest,"=",sizeof(CookieTest));
                         if (bpos != string::npos
                          && sameto(CookieTest,tmp.c_str()+bpos)) {
                              bpos+=::strlen(CookieTest);
                              string auth(tmp,bpos,epos-bpos);
                              stlcpy(vbuf,auth.c_str(),sizeof(vbuf));
                              authProcess(vbuf);
                              if (usr != NULL) {
                                   pXInf->AuthType=AT_COOKIE;
                                   pXInf->AuthMeth=AM_BASIC;
                              }
                              return;
                         }
                    }
               }
          }
     }
}

VOID
acthSession::authProcess(
CHAR* buffer)
{
     CHAR ubuf[256];

     //   buffer may or may not be quoted (depends on browser)
     //   handle it anyway
     //
     if (sameto("\"",buffer)) {
          ++buffer;
     }
     if (samend(buffer,"\"")) {
          buffer[strlen(buffer)-1]='\0';
     }

     INT nans=base64dc(buffer,ubuf);
     ASSERT(nans < sizeof(ubuf));
     ubuf[nans]='\0';
     CHAR *pp=strchr(ubuf,':');
     if (pp == NULL) {
#ifndef WEBCAST
          usrfail=USERUNKNOWN;
#endif // WEBCAST
          return;
     }
     *pp++='\0';
     ASSERT(uidfactory != NULL);
     usr=(*uidfactory)(ubuf);
#ifdef WEBCAST
     if (!usr->pswAuthentic(pp)) {
#else
     if ((usrfail=usr->pswAuthentic(pp)) != USERAUTHENTIC) {
#endif // WEBCAST
          delete usr;
          usr=NULL;
     }
}


acthOstreambuf::acthOstreambuf(    // construct a stream to the browser
acthSession *_ses):
     ses(_ses),
     sizbuf(512)
{
     buffer=new CHAR[sizbuf];
     setp(buffer,buffer+sizbuf);
}

acthOstreambuf::~acthOstreambuf()  // destructor
{
     sync();
     delete[] buffer;
}

INT                                //   return 0=OK, EOF=fail
acthOstreambuf::sync()             // empty buffer contents
{
     INT nwant,nactual;

     if ((nwant=out_waiting()) > 0) {
          nactual=ses->sndrsp(pbase(),nwant);
          ASSERT(0 <= nactual && nactual <= nwant);

          if (nactual < nwant) {                               // sync failure
               movmem(pbase()+nactual,buffer,nwant-nactual);
          }

          setp(pbase(),epptr());
          pbump(nwant-nactual);
     }
     return(0);
}

INT                                //   return 0=OK, EOF=fail
acthOstreambuf::overflow(          // handle overflow of buffer
INT ch)                            //   extra character after full buffer
{
     sync();
     if (pptr() >= epptr()) {
          INT newBufSize=sizbuf+BUFBUMPSIZE;

          CHAR* newBuf=new CHAR[newBufSize];
          ASSERT(newBuf != NULL);
          movmem(pbase(),newBuf,sizbuf);

          delete [] buffer;
          buffer=newBuf;
          sizbuf=newBufSize;

          setp(buffer,buffer+sizbuf);
          pbump(sizbuf-BUFBUMPSIZE);
     }

     if (ch != EOF && pptr() < epptr()) {
          *pptr()=ch;
          pbump(1);                // sputc() with no chance of recursion
     }
     else {
          return(EOF);             // character loss
     }
     return(0);
}

ostream&
crlf(                              // manipulator, like endl but <CR><LF>
ostream& os)                       //   (and without a flush())
{
     os << "\r\n";
     return(os);
}

static ostream&
htmlBreaksInternal(                // htmlBreaks(str) manipulator's workhorse
ostream& os,                       //   output stream
CHAR *crlines)                     //   CR-terminated lines (temp modified)
{                                  //   (lines may also be CRLF-terminated)
     CHAR *bp,*ep;

     ASSERT(crlines != NULL);
     for (bp=crlines ; (ep=strchr(bp,'\r')) != NULL ; bp=ep+1) {
          *ep='\0';
          if (*bp == '\n') {
               os << (bp+1) << "<BR> \r\n";
          }
          else {
               os << bp << "<BR> \r";
          }
          *ep='\r';
     }
     return(os);
}

omanip<CHAR *> EXPORT
htmlBreaks(                        // manipulator, transl CR's to HTML <BR>'s
CHAR *crlines)                     //   CR-terminated lines (temp modified)
{                                  //   (lines may also be CRLF-terminated)
     return(omanip<CHAR *>(htmlBreaksInternal,crlines));
}

VOID
acthUserID::setUIDfactory(         // User-ID implementation declares factory
UIDFACTORY *uidf)                  //   function to call to make acthUserID's
{
     ASSERT(uidfactory == NULL);
     ASSERT(uidf != NULL);         // multiple security system not supported
     uidfactory=uidf;
}

acthUserID::acthUserID()           // acthUserID constructor
{
}

acthUserID::~acthUserID()          // acthUserID destructor
{
}


//////////////////////////////////////////////////////
// WEB SERVER'S CLASSES (acthRequest, acthResponse) //
//////////////////////////////////////////////////////

acthRequest::acthRequest()         // generic acthRequest constructor
{
}

acthRequest::~acthRequest()        // generic acthRequest destructor
{
}

INT
acthRequest::vernum()              // 0=HTTP/0.9, 100=HTTP/1.0, 101=HTTP/1.1
{
     INT mjr,mnr;
     INT retval=0;

     if (sscanf(version(),"HTTP/%u.%u",&mjr,&mnr) == 2) {
          retval=mjr*100+mnr;
     }
     return(retval);
}

VOID
acthRequest::freereq()             // free up request resources
{
}

acthResponse::acthResponse() :     // generic acthResponse constructor
     stage(ACTHPREHDR)
{
}

acthResponse::~acthResponse()      // generic acthResponse destructor
{
}

VOID
acthResponse::addlVersion(         // append product version to Server header
const CHAR *prodver)               //   product and version, e.g. "Zhoots/6.0"
{
     (VOID)prodver;
}

const CHAR *
acthResponse::serverVer()          // server's version (eg "Worldgroup/3.0")
{
     return(acthVersion);
}



////////////////////////////////////////////////
// AGENT'S CLASSES (acthAgent, acthSynthesis) //
////////////////////////////////////////////////

acthAgent::acthAgent()             // default constructor, never used
{
     // will never get here
}

acthAgent::acthAgent(              // prefix agent construction
const CHAR *apnam,                 //   application (eg "File Libraries")
const CHAR *upfx)                  //   URL prefix (eg "library")
{
     _appname=strcpy(new CHAR[strlen(apnam)+1],apnam);
     if (upfx == NULL) {                          // universal agents:
          _urlpfx=NULL;
     }
     else {                                       // prefix agents:
          if (upfx[0] == '/') {
               upfx++;                            // remove preceding slash
          }
          _urlpfx=new CHAR[strlen(upfx)+1+1];
          strcpy(_urlpfx,upfx);
          if (samend(_urlpfx,"/")) {
               _urlpfx[strlen(_urlpfx)-1]='\0';   // remove trailing slash
          }
          pfxlen=strlen(_urlpfx);
     }
}

acthAgent::~acthAgent()            // agent "destructor" (shutdown)
{                                  //   danger: never removed from list
     if (_urlpfx != NULL) {
          delete[] _urlpfx;
          _urlpfx=NULL;
     }
     if (_appname != NULL) {
          delete[] _appname;
          _appname=NULL;
     }
}

VOID
acthAgent::registerAgent(VOID)     // 1.0 register agent function
{
     versionError("ActiveHTML/1.0");
}

VOID
acthAgent::registerAgent(          // register agent with ACTH kernel
const CHAR *versionString)         //   kernel version expected by agent
{
     if (!::sameas(acthVersion,versionString)) {
          versionError(versionString);
          return;
     }
     if (_urlpfx == NULL) {
          if (unvAgents == NULL) {
               unvAgents=new AGENTVEC;
          }
          unvAgents->push_back(this);
     }
     else {
          if (pfxAgents == NULL) {
               pfxAgents=new AGENTVEC;
          }
          pfxAgents->push_back(this);
     }
}

VOID
acthAgent::versionError(           // report agent/kernel version conflict
const CHAR *moduleVersion)         //   kernel version expected by module
{
     shocst("ACTIVE HTML VERSION CONFLICT",
            "%s requires %s",_appname,moduleVersion);
}

AGTPOSITION                   //   position in the agent array
acthAgent::getFirstAgent()    // get the position of the first agent
{
     AGTPOSITION         agt=NULL;
     AGENTVEC::iterator  avi;

     if (pfxAgents == NULL) {
          return(NULL);
     }
     if ((avi=pfxAgents->begin()) != pfxAgents->end()) {
          agt=(AGTPOSITION)avi;
     }
     return(agt);
}

acthAgent*                    //   pointer to the next agent in the list
acthAgent::getNextAgent(      // get the next agent
AGTPOSITION& pos)             //   position structure
{
     acthAgent*          pAgent=NULL;
     AGENTVEC::iterator  avi;

     if (pfxAgents == NULL) {
          return(NULL);
     }
     avi=(AGENTVEC::iterator)pos;
     if (avi != pfxAgents->end()) {
          pAgent=*avi;
          avi++;
          pos=(AGTPOSITION)avi;
     }
     return(pAgent);
}

GBOOL                              //   TRUE=all requests passed to claim()
acthAgent::isUniversal()           // is agent Universal?
{
     return(_urlpfx == NULL);
}

GBOOL                              //   returns TRUE=will handle, FALSE=won't
acthAgent::claim(                  // check if an agent will handle resource
acthSession *ses)                  //   for inquiring about request
{
     if (_urlpfx == NULL) {
          return(FALSE);
     }
     const CHAR *urlpart=ses->url();
     if (*urlpart == '/') {
          urlpart++;
     }
     CHAR ch;
     return(sameto(_urlpfx,urlpart)
         && ((ch=urlpart[pfxlen]) == '\0') || ch == '/' || ch == '?');
}

const CHAR *
acthAgent::urlpfx(VOID)            // get URL prefix for agent
{
     return(_urlpfx);
}

const CHAR *
acthAgent::appname(VOID)           // get application name for agent
{
     return(_appname);
}

acthSynthesis::acthSynthesis(      // constructor, agent-spec, sess-spec stuff
acthSession *_ses) :               //   kernel's session info
     ses(_ses),
     bout(*_ses->boutp),
     _firsttime(TRUE)
{
}

acthSynthesis::~acthSynthesis()    // destructor
{
}

GBOOL
acthSynthesis::firsttime()         // TRUE=proceed()'s first call, FALSE=after
{
     return(_firsttime);
}

VOID
acthSynthesis::postproceed()       // Called by kernel after first proceed()
{
     _firsttime=FALSE;
}

VOID
acthSynthesis::abort()             // kernel -> agent, request died
{                                  //   (expect no more proceed() calls)
}


// MEDIA TYPE HANDLING

const CHAR * const noMediaType="application/octet-stream";
const CHAR * const noMediaExt="";

const CHAR *                       //   media type (eg "text/html")
acthMediaType(                     // get media type from a file extension
const CHAR *filext)                //   file extension (eg "htm") or full path
                                   //   returns noMediaType if filext unknown
{                                  //   never returns NULL
     const CHAR *retval;

     if ((retval=gmimctyp(filext)) == NULL) {
          retval=noMediaType;
     }
     return(retval);
}

const CHAR *                       //   file extension (eg "htm")
acthMediaFext(                     // get file extension from media type
const CHAR *type)                  //   media type (eg "text/html")
                                   //   returns "" if type unknown
{                                  //   never returns NULL
     (VOID)type;         // HACK
     return(noMediaExt); // HACK
}

