397 lines
12 KiB
C++
397 lines
12 KiB
C++
/*--
|
|
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;
|
|
}
|