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