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.
| Return Type | Function name | Arguments |
|---|---|---|
| hzEcode | hzHttpEvent::ProcessEvent | (hzChain&,) |
Declared in file: hzHttpServer.h
Defined in file : hzHttpServer.cpp
Function Logic:
Function body:
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 >&eq; ''A''&&*zi <&eq; ''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 >&eq; 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 ;
}