windows-nt/Source/XPSP1/NT/net/upnp/host/upnphost/udhhttp/buffio.cpp

397 lines
12 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*--
Copyright (c) 1995-1998 Microsoft Corporation
Module Name: BUFFIO.CPP
Author: Arul Menezes
Abstract: Buffer handling class & socket IO helpers
--*/
#include "pch.h"
#pragma hdrstop
#include "httpd.h"
// Wait for input on socket with timeout
int MySelect(SOCKET sock, DWORD dwMillisecs)
{
fd_set set;
struct timeval t;
if (dwMillisecs != INFINITE)
{
t.tv_sec = (dwMillisecs / 1000);
t.tv_usec = (dwMillisecs % 1000)*1000;
}
FD_ZERO(&set);
FD_SET(sock, &set);
TraceTag(ttidWebServer, "Calling select(%x). Timeout=%d", sock, dwMillisecs);
int iRet = select(0, &set, NULL, NULL, ((dwMillisecs==INFINITE) ? NULL : (&t)));
TraceTag(ttidWebServer, "Select(%x) got %d", sock, iRet);
return iRet;
}
// Semi-blocking wait for input on a socket. This function will exit either
// when input is available or when the shutdown event has been set
//
int MySelect2(SOCKET sock, DWORD dwMillisecs)
{
HANDLE hEvent;
int iRet = 0;
HANDLE rgHandles[2];
hEvent = WSACreateEvent();
if (hEvent != WSA_INVALID_EVENT)
{
rgHandles[0] = hEvent;
rgHandles[1] = g_pVars->m_hEventShutdown;
iRet = WSAEventSelect(sock, hEvent, FD_READ);
if (!iRet)
{
DWORD dwRet;
dwRet = WaitForMultipleObjects(2, rgHandles, FALSE, dwMillisecs);
if (WAIT_OBJECT_0 == dwRet)
{
// iRet should be 1 if input is available
iRet = 1;
}
}
CloseHandle(hEvent);
}
return iRet;
}
// need space for iLen more data
BOOL CBuffer::AllocMem(DWORD dwLen)
{
// figure out buffer size
DWORD dwAlloc = max(MINBUFSIZE, dwLen);
// allocate or reallocate buffer
if (!m_pszBuf)
{
m_pszBuf = MyRgAllocZ(char, dwAlloc);
TraceTag(ttidWebServer, "New buffer (data=%d size=%d buf=0x%08x)", dwLen, dwAlloc, m_pszBuf);
m_iSize = dwAlloc;
}
else if ((m_iSize-m_iNextIn) <= (int)dwLen)
{
m_pszBuf = MyRgReAlloc(char, m_pszBuf, m_iSize, dwAlloc+m_iSize);
TraceTag(ttidWebServer, "Realloc buffer (datasize=%d oldsize=%d size=%d buf=0x%08x)", dwLen, m_iSize, dwAlloc+m_iSize, m_pszBuf);
m_iSize += dwAlloc;
}
if (!m_pszBuf)
{
TraceTag(ttidWebServer, "CBuffer:AllocMem(%d) failed. GLE=%d", dwLen, GetLastError());
m_iNextInFollow = m_iSize = m_iNextOut = m_iNextIn = 0;
m_chSaved = 0;
return FALSE;
}
return TRUE;
}
// Pull in all white space before a request. Note: We techinally should let
// the filter get this too, but too much work. Also note that we could read
// past a double CRLF if there was only white space before it, again this
// is a strange enough condition that we don't care about it.
BOOL CBuffer::TrimWhiteSpace()
{
int i = 0, j = 0;
while ( isspace(m_pszBuf[i]) && i < m_iNextIn)
{
i++;
}
if (i == 0)
return TRUE;
if (i == m_iNextIn)
return FALSE; // need to read more data, all white spaces so far.
for (j = 0; j < m_iNextIn - i; j++)
m_pszBuf[j] = m_pszBuf[j+i];
m_iNextIn -= i;
TraceTag(ttidWebServer, "HTTPD: TrimWhiteSpace removing first %d bytes from steam",i);
return TRUE;
}
// This function reads eitehr request-headers from the socket
// terminated by a double CRLF, OR reads a post-body from the socket
// terminated by having read the right number of bytes
//
// We are keeping the really simple--we read the entire header
// into one contigous buffer before we do anything.
//
// dwLength is -1 for reading headers, or Content-Length for reading body
// or 0 is content-length is unknown, in which case it reads until EOF
HRINPUT CBuffer::RecvToBuf(SOCKET sock, DWORD dwLength, DWORD dwTimeout, BOOL fFromFilter)
{
DEBUG_CODE_INIT;
int iScan = 0;
HRINPUT ret = INPUT_ERROR;
DWORD dwBytesRemainingToBeRead;
// Both IE and Netscape tack on a trailing \r\n to POST data but don't
// count it as part of the Content-length. IIS doesn't pass the \r\n
// to the script engine, so we don't either. To do this, we set
// the \r to \0. Also we reset m_iNextIn. This \r\n code is only
// relevant when RecvToBuf is called from HandleRequest, otherwise
// we assume it's a filter calling us and don't interfere.
if (dwLength != -1)
{
if (!fFromFilter && ((m_iNextIn-m_iNextOut) >= (int) dwLength))
{
if (((m_iNextIn-m_iNextOut) == (int) dwLength) ||
((m_iNextIn-m_iNextOut) == (int) dwLength+2))
{
m_iNextIn = m_iNextOut + dwLength;
// This is reachable from HandleRequest, and
myretleave(INPUT_NOCHANGE,0);
}
else
{
myretleave(INPUT_ERROR, 111);
}
}
if (!fFromFilter)
{
dwLength = dwLength - (m_iNextIn - m_iNextOut); // account for amount of POST data already in
}
m_iNextInFollow = m_iNextIn;
// allocate or reallocate buffer. Since we already know size we want, do it here rather than later.
if (!AllocMem(dwLength+1))
myretleave(INPUT_ERROR, 103);
}
dwBytesRemainingToBeRead = dwLength;
for (;;)
{
// see if we got the double CRLF for HTTP Headers.
if (dwLength == (DWORD)-1)
{
BOOL fScan = TRUE;
if (iScan == 0 && m_iNextIn)
{
fScan = TrimWhiteSpace();
}
if (fScan)
{
while (iScan+3 < m_iNextIn)
{
if (m_pszBuf[iScan]=='\r' && m_pszBuf[iScan+1]=='\n' && m_pszBuf[iScan+2]=='\r' && m_pszBuf[iScan+3]=='\n')
{
myretleave(INPUT_OK,0);
}
iScan++;
}
}
}
// else see if we have the number of bytes we want.
// Browsers sometimes tack an extra \r\n to very end of POST data, even
// though they don't include it in the Content-Length field. IIS
// never passes this extra \r\n to ISAPI extensions, neither do we.
else if ((m_iNextIn-m_iNextInFollow) >= (int)dwLength)
{
DEBUGCHK((int)dwLength + 2 == (m_iNextIn-m_iNextInFollow) ||
(int)dwLength == (m_iNextIn-m_iNextInFollow));
DEBUGCHK(dwBytesRemainingToBeRead == 0);
m_iNextIn = m_iNextInFollow+(int)dwLength; // don't copy trailing \r\n
myretleave(INPUT_OK,0);
}
// check if we have input. If we are waiting for subsequent input (i.e. not the start of a request)
// then drop the timeout value lower, and if we timeout return ERROR, not TIMEOUT
switch (MySelect2(sock, ((m_iNextIn ? RECVTIMEOUT : dwTimeout))))
{
case 0: myretleave((m_iNextIn ? INPUT_ERROR : INPUT_TIMEOUT),100);
case SOCKET_ERROR: myretleave(INPUT_ERROR, 101);
}
// check how much input is waiting
DWORD dwAvailable;
if (ioctlsocket(sock, FIONREAD, &dwAvailable))
myretleave(INPUT_ERROR, 102);
DWORD dwBytesToRecv;
if (dwLength == -1) // Read in as much http header as we have.
{
dwBytesToRecv = dwAvailable;
// allocate or reallocate buffer. For headers, have to do it each pass.
if (!AllocMem(dwAvailable+1))
myretleave(INPUT_ERROR, 103);
}
else // Read in only requested amount of POST
dwBytesToRecv = (dwAvailable < dwBytesRemainingToBeRead) ? dwAvailable : dwBytesRemainingToBeRead;
DEBUGCHK((m_iSize-m_iNextIn) >= (int)dwBytesToRecv);
DEBUGCHK(m_iNextIn >= m_iNextOut);
DEBUGCHK(m_iNextIn >= m_iNextInFollow);
// safe to call recv, because we know we have something. It will return immediately
int iRecv = recv(sock, m_pszBuf+m_iNextIn, dwBytesToRecv, 0);
TraceTag(ttidWebServer, "recv(%x) got %d", sock, iRecv);
if (iRecv == 0)
{
myretleave((m_iNextIn ? INPUT_OK : INPUT_TIMEOUT), 0);
} // got EOF. If we have any data return OK, else return TIMEOUT
else if (iRecv == SOCKET_ERROR)
{
myretleave(((GetLastError()==WSAECONNRESET) ? INPUT_TIMEOUT : INPUT_ERROR), 104);
}
m_iNextIn += iRecv;
dwBytesRemainingToBeRead -= iRecv;
DEBUGCHK(m_iSize >= m_iNextIn);
}
DebugBreak(); // no fall through
done:
// Always make this buffer into a null terminated string
if (m_pszBuf)
m_pszBuf[m_iNextIn] = 0;
TraceTag(ttidWebServer, "end RecvToBuf (ret=%d err=%d iGLE=%d)", ret, err, GLE(err));
return ret;
}
// tokenize the input stream: We always skip leading white-space
// once we're in the token, we stop on whitespace or EOL, depending
// on the fWS param
BOOL CBuffer::NextToken(PSTR* ppszTok, int* piLen, BOOL fWS, BOOL fColon /*=FALSE*/)
{
int i, j;
// restore saved char, if any
if (m_chSaved)
{
DEBUGCHK(m_pszBuf[m_iNextOut]==0);
m_pszBuf[m_iNextOut] = m_chSaved;
m_chSaved = 0;
}
for (i=m_iNextOut; i<m_iNextIn; i++)
{
// if not whitespace break
if (! (m_pszBuf[i]==' ' || m_pszBuf[i]=='\t') )
break;
}
for (j=i; j<m_iNextIn; j++)
{
// if we get an EOL, it's always end of token
if (m_pszBuf[j]=='\r' || m_pszBuf[j]=='\n')
break;
// if fWS==TRUE and we got white-space, then end of token
if (fWS && (m_pszBuf[j]==' ' || m_pszBuf[j]=='\t'))
break;
if (fColon && m_pszBuf[j]==':')
{
j++; // we want to return the colon
break;
}
}
m_iNextOut = j;
*piLen = (int)(INT_PTR)((j-i));
*ppszTok = &(m_pszBuf[i]);
if (i==j)
{
TraceTag(ttidWebServer, "Got NULL token");
return FALSE;
}
else
{
// save a char so we can null-terminate the current token
m_chSaved = m_pszBuf[m_iNextOut];
m_pszBuf[m_iNextOut] = 0;
TraceTag(ttidWebServer, "Got token (%s) Len %d", *ppszTok, (*piLen));
return TRUE;
}
}
// skip rest of current line and CRLF
BOOL CBuffer::NextLine()
{
int i, j;
// restore saved char, if any
if (m_chSaved)
{
DEBUGCHK(m_pszBuf[m_iNextOut]==0);
m_pszBuf[m_iNextOut] = m_chSaved;
m_chSaved = 0;
}
for (i=m_iNextOut, j=i+1; j<m_iNextIn; i++, j++)
{
if (m_pszBuf[i]=='\r' && m_pszBuf[j]=='\n')
{
m_iNextOut = j+1;
TraceTag(ttidWebServer, "NextLine: OK");
return TRUE;
}
}
TraceTag(ttidWebServer, "NextLine: error");
return FALSE;
}
// used only on output buffers by ASP
BOOL CBuffer::AppendData(PSTR pszData, int iLen)
{
// make sure we have enough memory
if (!AllocMem(iLen+1))
return FALSE;
DEBUGCHK((m_iSize-m_iNextIn) >= iLen);
memcpy(m_pszBuf+m_iNextIn, pszData, iLen);
m_iNextIn += iLen;
return TRUE;
}
BOOL CBuffer::SendBuffer(SOCKET sock, CHttpRequest *pRequest)
{
PSTR pszSendBuf = m_pszBuf; // use temp ptrs in case filter changes them
int cbSendBuf = m_iNextIn;
DWORD fRet = FALSE;
DEBUGCHK(m_iNextOut==0);
DEBUGCHK(m_chSaved==0);
if (!m_iNextIn)
{
TraceTag(ttidWebServer, "SendBuffer: empty");
return TRUE;
}
if (g_pVars->m_fFilters &&
! pRequest->CallFilter(SF_NOTIFY_SEND_RAW_DATA, &pszSendBuf, &cbSendBuf))
goto done;
if (cbSendBuf != send(sock, pszSendBuf, cbSendBuf, 0))
{
TraceTag(ttidWebServer, "SendBuffer FAILED. GLE=%d", GetLastError());
goto done;
}
fRet = TRUE;
done:
m_iNextIn = 0;
return fRet;
}