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