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