📄 buffio.cpp
字号:
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
/*--
Module Name: BUFFIO.CPP
Abstract: Buffer handling class & socket IO helpers
--*/
#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);
DEBUGMSG(ZONE_SOCKET, (L"HTTPD: Calling select(%x). Timeout=%d\r\n", sock, dwMillisecs));
int iRet = select(0, &set, NULL, NULL, ((dwMillisecs==INFINITE) ? NULL : (&t)));
DEBUGMSG(ZONE_SOCKET, (L"HTTPD: Select(%x) got %d\r\n", sock, iRet));
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);
DEBUGMSG(ZONE_REQUEST_VERBOSE, (L"HTTPD: New buffer (data=%d size=%d buf=0x%08x)\r\n", dwLen, dwAlloc, m_pszBuf));
m_iSize = dwAlloc;
}
else if((m_iSize-m_iNextIn) <= (int)dwLen) {
DWORD dwBytesToReAlloc;
if (! safeIntUAdd(dwAlloc,m_iSize,(UINT*)&dwBytesToReAlloc)) {
DEBUGMSG(ZONE_ERROR,(L"HTTPD: ERROR: Integer overflow on buffer alloc\r\n"));
DEBUGCHK(0);
return FALSE;
}
PSTR pszTemp = MyRgReAlloc(char, m_pszBuf, m_iSize, dwBytesToReAlloc);
if (!pszTemp) {
DEBUGMSG(ZONE_ERROR, (L"HTTPD: CBuffer:AllocMem(%d) failed. GLE=%d\r\n", dwLen, GetLastError()));
return FALSE;
}
m_pszBuf = pszTemp;
m_iSize = dwBytesToReAlloc;
DEBUGMSG(ZONE_REQUEST_VERBOSE, (L"HTTPD: Realloc buffer (datasize=%d oldsize=%d size=%d buf=0x%08x)\r\n", dwLen, m_iSize, dwAlloc+m_iSize, m_pszBuf));
}
if(!m_pszBuf) {
DEBUGMSG(ZONE_ERROR, (L"HTTPD: CBuffer:AllocMem(%d) failed. GLE=%d\r\n", dwLen, GetLastError()));
m_iNextDecrypt = m_iNextInFollow = m_iSize = m_iNextOut = m_iNextIn = 0;
m_chSaved = 0;
return FALSE;
}
return TRUE;
}
// Suck 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 an condition so we don't care about it.
// We don't read forever or else we set ourselves up for a DoS attack.
// If we get > MAX_WHITESPACE_READ characters then we give up.
#define MAX_WHITESPACE_READ 8
BOOL CBuffer::TrimWhiteSpace(BOOL *pfAbort) {
int i = 0, j = 0;
DEBUGCHK(*pfAbort == FALSE);
while (isspace(m_pszBuf[i]) && (i < m_iNextIn) && (i < MAX_WHITESPACE_READ)) {
i++;
}
if (i == MAX_WHITESPACE_READ) {
DEBUGMSG(ZONE_ERROR,(L"HTTPD: Web server has been sent greater than %d whitespace chars before HTTP headers begin. Refusing request\r\n",MAX_WHITESPACE_READ));
*pfAbort = TRUE;
return FALSE;
}
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;
DEBUGMSG(ZONE_PARSER,(L"HTTPD: TrimWhiteSpace removing first %d bytes from steam\r\n",i));
return TRUE;
}
// This function reads eithr 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
// Note: This function is more than just a generic receive, it takes into account
// HTTP headers + POST status of current request.
HRINPUT CBuffer::RecvToBuf(SOCKET sock, DWORD dwLength, BOOL fFromFilter, BOOL fFirstPostRead, CHttpRequest *pRequest, BOOL fSSLRenegotiate) {
DEBUG_CODE_INIT;
int iScan = 0;
HRINPUT ret = INPUT_ERROR;
BOOL fReadHeaders = (dwLength == (DWORD)-1);
BOOL fIsSecure = pRequest->IsSecure();
BOOL fScanHeadersForCRLF = fIsSecure ? FALSE : TRUE;
// fSSLSkipFirstRead = TRUE when we have data was read in as part of the SSL handshake
// that is actually part of the HTTP request. In this case decrypt what we have before calling recv(),
// it's possible the entire HTTP request has already been read in so a recv() would block.
BOOL fSSLSkipFirstRead = (fIsSecure && m_iNextIn && fReadHeaders) ? TRUE : FALSE;
DWORD dwBytesDecrypted = 0;
DWORD dwBytesRemainingToBeRead;
// dwSSLOffset is pointer to data that remains encrypted.
// It's possible we have unencrypted data from a previous HTTP request (but
// when fSSLSkipFirstRead=TRUE we use different code path, so set=0).
DWORD dwSSLOffset = !fSSLSkipFirstRead ? m_iNextIn - m_iNextDecrypt : 0;
// In cases web server requests a renegotiate *after* client has sent us all its POST
// data, we read POST data first but then have to keep reading to get renegotiate params.
// To do this we allocate extra data and keep listening.
BOOL fForceSSLRenegotiate;
// In typical case (i.e. reading HTTP headers) we read up to maximum header size.
// However when we force SSL renegotiation, we'll read up to max header size+max POST size
// as we've read the headers already.
DWORD dwMaxBufferSize = g_pVars->m_dwMaxHeaderReadSize;
if (!fReadHeaders)
dwMaxBufferSize += g_pVars->m_dwPostReadSize;
DEBUGMSG(ZONE_REQUEST_VERBOSE,(L"HTTPD: RecvToBuf() sock=%d,dwLength=%d,fFromFilter=%d,fFirstPostRead=%d,fSSLRenegotiate=%d,fReadHeaders=%d,fIsSecure=%d\r\n",
sock,dwLength,fFromFilter,fFirstPostRead,fSSLRenegotiate,fReadHeaders,fIsSecure));
WriteBufferToDebugOut(FALSE);
BufferConsistencyChecks();
// Some clients ignore the fact that we're an HTTP 1.0 server and send
// us multiple HTTP requests on the same packet. If we detect this situation when
// reading in data we make note of it at the time and clean things up when reading
// in the headers to the next request.
if (fReadHeaders && m_iNextRequestBegin) {
DEBUGCHK(m_pszBuf[m_iNextRequestBegin] == 0);
m_pszBuf[0] = m_chNextRequestSaved;
DEBUGMSG(ZONE_REQUEST_VERBOSE,(L"HTTPD: End Cleanup: m_iNextRequestSize=%d,m_iNextRequestBegin=%d\r\n",m_iNextRequestSize,m_iNextRequestBegin));
for (int i = 1; i < m_iNextRequestSize; i++)
m_pszBuf[i] = m_pszBuf[m_iNextRequestBegin+i];
m_iNextIn = m_iNextRequestSize;
m_iNextRequestBegin = m_iNextRequestSize = 0;
m_chNextRequestSaved = 0;
fScanHeadersForCRLF = TRUE;
}
// 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 (!fReadHeaders) {
if (!fSSLRenegotiate && !fFromFilter && ((m_iNextIn-m_iNextOut) >= (int) dwLength)) {
if (fFirstPostRead && (m_iNextIn-m_iNextOut) > (int) dwLength+2) {
DEBUGMSG(ZONE_ERROR,(L"HTTPD: WARNING: more than one HTTP messages have been sent in one packet! This is not valid HTTP 1.0! "
L"Web server is cleaning up message and will process normally now, FIX CLIENT to make it HTTP 1.0 (not only 1.1) aware\r\n"));
// client has sent us more than one HTTP request (or at least beginning of 2nd request)
// in the packet we read on initial header read-in. Save off information for next request.
DEBUGMSG(ZONE_REQUEST_VERBOSE,(L"HTTPD: Begin cleanup: m_iNextRequestBegin=%d, m_iNextRequestSize=%d, m_iNextIn=%d,m_iNextOut=%d,dwLength=%d",m_iNextRequestBegin,m_iNextRequestSize,m_iNextIn,m_iNextOut,dwLength));
m_iNextRequestBegin = m_iNextOut + dwLength;
m_iNextRequestSize = m_iNextIn - m_iNextRequestBegin; // length of buffer
m_iNextInFollow = m_iNextIn = m_iNextRequestBegin;
m_chNextRequestSaved = m_pszBuf[m_iNextIn];
myretleave(INPUT_NOCHANGE,0);
}
m_iNextInFollow = m_iNextIn = m_iNextOut + dwLength;
// Everything has been read into POST request already, no processing.
myretleave(INPUT_NOCHANGE,0);
}
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;
fForceSSLRenegotiate = (fSSLRenegotiate && (dwLength == 0));
for(;;) {
// see if we got the double CRLF for HTTP Headers.
if(fReadHeaders && fScanHeadersForCRLF) {
BOOL fScan = TRUE;
if (iScan == 0 && m_iNextIn) {
BOOL fAbort = FALSE;
fScan = TrimWhiteSpace(&fAbort);
if (fAbort) // too much white space was sent between keep-alive requests
myretleave(INPUT_ERROR,111);
}
if (fScan) {
while(iScan+3 < (fIsSecure ? (int) dwBytesDecrypted : m_iNextIn)) {
if(m_pszBuf[iScan]=='\r' && m_pszBuf[iScan+1]=='\n' && m_pszBuf[iScan+2]=='\r' && m_pszBuf[iScan+3]=='\n') {
DEBUGMSG(ZONE_REQUEST_VERBOSE,(L"HTTPD: RecvToBuf: found double CRLF to close headers\r\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((!fSSLRenegotiate || (fSSLRenegotiate && !fForceSSLRenegotiate)) &&
!fReadHeaders && (dwBytesRemainingToBeRead == 0)) {
DEBUGCHK(fIsSecure || ((int)dwLength + 2 == (m_iNextIn-m_iNextInFollow) ||
(int)dwLength == (m_iNextIn-m_iNextInFollow)));
DEBUGMSG(ZONE_REQUEST_VERBOSE,(L"HTTPD: RecvToBuf: we've got required # of bytes on POST!\r\n"));
// m_iNextIn = m_iNextInFollow+(int)dwLength; // don't copy trailing \r\n
myretleave(INPUT_OK,0);
}
if ((fReadHeaders || fForceSSLRenegotiate) && ((int)dwMaxBufferSize <= m_iNextIn)) {
DEBUGMSG(ZONE_ERROR,(L"HTTPD: Client has sent >= %d bytes in HTTP headers or SSL renegotiate, rejecting request!\r\n",dwMaxBufferSize));
myretleave(INPUT_ERROR,109);
}
// fSSLSkipFirstRead = TRUE in case where we have data already read in but haven't decrypted
// it yet - it's possible that the client will send no more data so select would timeout if we call it.
if (!fSSLSkipFirstRead) {
// check if we have input.
switch(MySelect(sock, g_pVars->m_dwConnectionTimeout)) {
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 = 0;
DWORD dwBytesToRecv = 0;
if (!fSSLSkipFirstRead) {
if(ioctlsocket(sock, FIONREAD, &dwAvailable))
myretleave(INPUT_ERROR, 102);
if (fReadHeaders || fForceSSLRenegotiate) {
// only read up to dwMaxBufferSize. If we don't get double CRLF on
// next pass through loop then we'll bomb out.
if (dwAvailable + m_iNextIn > dwMaxBufferSize)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -