/*++


   Copyright    (c)    1995-1996    Microsoft Corporation

   Module  Name :
       httphdr.cxx

   Abstract:
       This module defines the functions for handling the dictionary items.
       It contains custom implementation of dictionary for HTTP header parsing.

   Author:

       Murali R. Krishnan    ( MuraliK )     8-Nov-1996

   Environment:
       User Mode - Win32

   Project:

       Internet Server DLL

   Functions Exported:



   Revision History:

--*/


/************************************************************
 *     Include Headers
 ************************************************************/

# include "httphdr.hxx"
# include <iis64.h>


// NYI:  Temporary copy for now
struct NAME_COLLECTION {
 LPCSTR pszName;
 INT    cchName;
};

# define HfmHeader( HfmId, HfmString)  { HfmString, (sizeof( HfmString) - 1) },

NAME_COLLECTION   g_HttpHeaders[] = {

    ALL_HTTP_FAST_MAP_HEADERS()
    { NULL, NULL}
};

# undef HfmHeader


# define NUM_HTTP_HEADERS (sizeof( g_HttpHeaders)/sizeof( g_HttpHeaders[0]) - 1)


# define  _HTTP_HEADER_SIG_CHARS    ( 32)

// SigBits avoids the upcase-low-case troubles.
# define  SigBits( ch)    ( (ch) & 0x1F)

# define  SigBit_I        ( ('I') & 0x1F)  // SigBit of I
# define  SigBit_U        ( ('U') & 0x1F)  // SigBit of U

//
// This header hash is specifically tailored for HTTP Headers in
//  ALL_HTTP_FAST_MAP_HEADERS()
//

inline int _HTTP_HEADER_HASH( LPCSTR psz, DWORD cchLen, DWORD sigChar)
{
    register DWORD ch = SigBits( (DWORD ) *psz);

    return (( ch * (sigChar/2)) +
            //            ((( SigBits( *psz) == SigBit_I) && (cchLen > 6)) ?
            // ((cchLen > 6) ? SigBits(psz[6]) :
            ((ch == SigBit_I && cchLen>6) ? SigBits(psz[6]) :
             (((ch == SigBit_U)?
               (cchLen) :
               SigBits( psz[cchLen/2])))
             )
            );
} // _HTTP_HEADER_HASH()


// extract the case-insetive bits for US-ASCII character set.
# define IcaseBits(ch)         ( (ch) & 0xDF)

// emulate stricmp by disregarding bit 5
inline BOOL _HTTP_HEADER_CHAR_I_NOTEQUAL( CHAR ch1, CHAR ch2)
{   return ( (IcaseBits(ch1)) != ( IcaseBits(ch2))); }


/*++
  I tried using the cached case-insensitive name for comparison
  using the _HTTP_HEADER_CHAR_I_NORMAL_1()
  but that requires more instructions since the x86 generated
  some unwanted instructions for access to memory :(
  x86 is smart to execute the above function _HTTP_HEADER_CHAR_I_NOTEQUAL()
   very well.
  --*/

// same as func _HTTP_HEADER_CHAR_I_NOTEQUAL()
// except that ch2 is already normalized
inline BOOL _HTTP_HEADER_CHAR_I_NOTEQUAL_1( CHAR ch1, CHAR ch2)
{   return ( (IcaseBits(ch1)) != ( ch2)); }



#if COMPRESSED_HEADERS
//
// Lookup table for compressed headers.
//

HTTP_FAST_MAP_HEADERS
CHeaderLUT[] =
{
    HM_ACC,                 //#A  // Accept:
    HM_MAX,                 //#B
    HM_MAX,                 //#C
    HM_MAX,                 //#D
    HM_MAX,                 //#E
    HM_MAX,                 //#F
    HM_MAX,                 //#G
    HM_AUT,                 //#H  // Authorization:
    HM_MAX,                 //#I
    HM_CON,                 //#J  // Connection:
    HM_MAX,                 //#K
    HM_MAX,                 //#L
    HM_MAX,                 //#M
    HM_CLE,                 //#N  // Content-Length:
    HM_MAX,                 //#O
    HM_MAX,                 //#P
    HM_MAX,                 //#Q
    HM_CTY,                 //#R  // Content-Type:
    HM_MAX,                 //#S
    HM_MAX,                 //#T
    HM_MAX,                 //#U
    HM_VIA,                 //#V
    HM_HST,                 //#W  // Host:
    HM_IMS,                 //#X  // If-Modified-Since:
    HM_MAX,                 //#Y
    HM_MAX,                 //#Z
    HM_MAX,                 //#a
    HM_MAX,                 //#b
    HM_MAX,                 //#c
    HM_MAX,                 //#d
    HM_MAX,                 //#e
    HM_MAX,                 //#f
    HM_MAX,                 //#g
    HM_PRA,                 //#h  // Proxy-Authorization:
    HM_MAX,                 //#i
    HM_RNG,                 //#j  // Range:
    HM_MAX,                 //#k
    HM_MAX,                 //#l
    HM_MAX,                 //#m
    HM_TEC,                 //#n  // Transfer-Encoding:
    HM_MAX,                 //#o
    HM_MAX,                 //#p
    HM_MAX,                 //#q
    HM_MAX,                 //#r
    HM_MAX,                 //#s
    HM_MAX,                 //#t
    HM_UMS                  //#u  // Unless-Modified-Since:

};

#endif

/************************************************************
 *    Functions
 ************************************************************/


HTTP_HEADER_MAPPER::~HTTP_HEADER_MAPPER( VOID)
{
    if ( NULL != m_rgOnNameMapper) {
        delete [] m_rgOnNameMapper;
        m_rgOnNameMapper = NULL;
    }

} // HTTP_HEADER_MAPPER::~HTTP_HEADER_MAPPER()

BOOL
HTTP_HEADER_MAPPER::Initialize( VOID)
{
    DWORD i;

    m_rgOnNameMapper = new int[ SizeOfNameMapper()];

    if ( NULL == m_rgOnNameMapper) {
        IF_DEBUG(ERROR) {

            DBGPRINTF(( DBG_CONTEXT,
                        "Allocation of Name Mapper of size %d failed\n",
                        SizeOfNameMapper()));
        }
        return FALSE;
    }

    // initialize the array of indexes
    for ( i = 0 ; i < SizeOfNameMapper() ; ++i ) {
        m_rgOnNameMapper[i] = -1; // set to invalid index
    }

    NAME_COLLECTION * pnc = g_HttpHeaders;

    for( pnc = g_HttpHeaders; pnc->pszName != NULL; pnc++) {


        int iN = _HTTP_HEADER_HASH(pnc->pszName, pnc->cchName, m_nSigChars);

        IF_DEBUG( API_ENTRY) {

            DBGPRINTF(( DBG_CONTEXT,
                        " _HTTP_HEADER_HASH( %s, len=%d, %d) => %d\n",
                        pnc->pszName, pnc->cchName, m_nSigChars, iN));
        }


        // We are using a very strict Algorithm for generating the mapping.
        // If the following assert fails, then someone has broken the algo's
        //   assumption, by adding a new entry. We have to find a new algo.
        // Algo's assumption: 1st char and next to last char are unique.
        // If not, modify the algo to use another pair or a different method
        //   (different hash function).

        if ((iN < 0)                              ||
            (((DWORD ) iN) >= SizeOfNameMapper()) ||
            (m_rgOnNameMapper[ iN] != -1)) {
            IF_DEBUG( ERROR) {
                DBGPRINTF(( DBG_CONTEXT,
                            " %08x::Initialize() OnName Mapper failed."
                            " Item (%s) indexes to location %d=>%d with (%s).\n",
                            this, pnc->pszName, iN,
                            m_rgOnNameMapper[iN],
                            g_HttpHeaders[ m_rgOnNameMapper[iN]].pszName
                            ));
            }

            // DBG_ASSERT( m_rgOnNameMapper[iN] == -1 );
            return ( FALSE);
        }

        // store the index here
        m_rgOnNameMapper[iN] = DIFF(pnc - g_HttpHeaders);

    } // for

    m_nItems = DIFF(pnc - g_HttpHeaders);

    return (TRUE);
} // HTTP_HEADER_MAPPER::Initialize()


BOOL
HTTP_HEADER_MAPPER:: FindOrdinal(
   IN LPCSTR pszName,
   IN INT    cchName,
   OUT DWORD * pdwOrdinal) const
{
    DBG_ASSERT( m_rgOnNameMapper);

    if ( cchName > 2 ) {


#if COMPRESSED_HEADERS

        if (*pszHeader == '#')
        {
            HM_ID       i;
            CHAR        c;

            c = pszHeader[1];
            if (c >= 'A')
            {
                i = CHeaderLUT[c - ( !(c & 0x20) ? 'A' : ('a' - ('Z' - 'A') - 1) )];

                *FieldIndex = i;

                return (i != HM_MAX);
            }

            return FALSE;

        }
#endif

        int iHash = _HTTP_HEADER_HASH( pszName, cchName, m_nSigChars);
        DBG_ASSERT( iHash >= 0);

        if (((DWORD ) iHash) >= SizeOfNameMapper()) {

            //
            // Out of bounds index value received for the index into our
            // lookup table => our hash calculator indicates this is not
            // a fast-mappable header => fail the FindOrdinal() call
            //

            return ( FALSE);
        }

        int i = m_rgOnNameMapper[iHash];

        //
        // The value from the m_rgOnNameMapper should be
        //  -1, if the header is not a fast-map header at all
        //  < NUM_HTTP_HEADERS if this is a valid fast-map header thus
        //    giving the index of the header in the header-mapper structure.
        //

        DBG_ASSERT( (i== -1) || (i < NUM_HTTP_HEADERS));

        if ( (i != -1) && (cchName == g_HttpHeaders[i].cchName) ) {

            LPCSTR pszFN = g_HttpHeaders[i].pszName;

            // let us use stride 2 and be pipeline friendly
            if ((cchName & 0x1)) {
                // odd length => eliminate the first char
                cchName--;
                if ( _HTTP_HEADER_CHAR_I_NOTEQUAL(
                        pszName[cchName],
                        pszFN[cchName] ) )
                {
                    return FALSE;
                }
            }


            DBG_ASSERT( (cchName % 2) == 0);
            while ( (cchName-= 2) >= 0 ) {

                if ( _HTTP_HEADER_CHAR_I_NOTEQUAL( pszName[cchName],
                                                   pszFN[cchName] ) ||
                     _HTTP_HEADER_CHAR_I_NOTEQUAL( pszName[cchName + 1],
                                                   pszFN[cchName + 1] )
                     )
                {
                    return FALSE;
                }
            } // while

            *pdwOrdinal = (DWORD ) i;
            return TRUE;
        }
    }

    return FALSE;
} // HTTP_HEADER_MAPPER::FindOrdinal()


LPCSTR
HTTP_HEADER_MAPPER::FindName( IN DWORD dwOrdinal) const
{
    DBG_ASSERT( dwOrdinal < NUM_HTTP_HEADERS );
    return ( g_HttpHeaders[dwOrdinal].pszName);

} // HTTP_HEADER_MAPPER::FindName()



VOID
HTTP_HEADER_MAPPER::PrintToBuffer( IN CHAR * pchBuffer,
                                   IN OUT LPDWORD pcch) const
{
    DWORD cb;
    DWORD i;

    DBG_ASSERT( NULL != pchBuffer);

    // 0. Print the location of this object
    // 1. Print all the <Name, ordinal> pairs
    // 2. Print the OnNameMapper values

    cb = wsprintfA( pchBuffer,
                    "HTTP_HEADER_MAPPER (%08x). NumItems= %d. NameColl= %08x"
                    " NSigChars= %d\n\n",
                    this, m_nItems, g_HttpHeaders, m_nSigChars
                    );

    for ( i = 0; i < NUM_HTTP_HEADERS; i++) {

        if ( cb + 80 < *pcch) {
            cb += wsprintfA( pchBuffer + cb,
                             " [%2d] @%4d\tLen=%-4d %-25s\n",
                             i,
                             _HTTP_HEADER_HASH( g_HttpHeaders[i].pszName,
                                                g_HttpHeaders[i].cchName,
                                                m_nSigChars),
                             g_HttpHeaders[i].cchName,
                             g_HttpHeaders[i].pszName
                             );

        } else {
            cb += 80;
        }
    } // for

    if ( cb + 60 < *pcch) {
        cb += wsprintfA( pchBuffer + cb, "\n Sizeof OnNameMapper = %d\n\n",
                         SizeOfNameMapper()
                         );
    } else {
        cb += 60;
    }

    if ( NULL != m_rgOnNameMapper) {

        for( i = 0; i < SizeOfNameMapper(); i++) {

            if ( (i % 20) == 0) {
                pchBuffer[cb++] = '\n';
                pchBuffer[cb] = '\0';
            }

            if ( cb + 5 < *pcch) {
                cb += wsprintfA( pchBuffer + cb,
                                 "%4d", m_rgOnNameMapper[ i]
                                 );
            } else {
                cb += 5;
            }
        } // for
    }


    if ( cb + 80 < *pcch) {
        cb += wsprintfA( pchBuffer+cb,
                         "\n   %d items stored in %d buckets. Density = %5d\n",
                         NUM_HTTP_HEADERS, SizeOfNameMapper(),
                         ( 10000 * NUM_HTTP_HEADERS)/SizeOfNameMapper()
                         );
    } else {
        cb += 80;
    }

    *pcch = cb;
    return;
} // HTTP_HEADER_MAPPER::PrintToBuffer( )


VOID
HTTP_HEADER_MAPPER::Print( VOID) const
{
    CHAR  pchBuffer[ 20000];
    DWORD cb = sizeof( pchBuffer);

    PrintToBuffer( pchBuffer, &cb);
    DBG_ASSERT( cb < sizeof(pchBuffer));

    DBGDUMP(( DBG_CONTEXT, pchBuffer));

    return;
} // HTTP_HEADER_MAPPER::Print()



/************************************************************
 *   HTTP_HEADERS
 ************************************************************/

#ifdef _PRIVATE_HTTP_HEADERS_TEST
HTTP_HEADER_MAPPER *
HTTP_HEADERS::QueryHHMapper(void)
{
   return ( &sm_hhm);

} // HTTP_HEADERS::QueryHHMapper()
# endif //  _PRIVATE_HTTP_HEADERS_TEST

inline VOID
UpdatePointer( IN OUT LPCSTR * ppsz, IN const CHAR * pchOld,
               IN DWORD cchLen, IN const CHAR * pchNew)
{
    if ( (*ppsz >= pchOld) &&
         (*ppsz < (pchOld + cchLen))
         ){

        IF_DEBUG( ERROR) {
            DBGPRINTF(( DBG_CONTEXT,
                        " Updating pointer [%08x] from %08x to %08x\n",
                        ppsz, *ppsz,  ((*ppsz - pchOld) + pchNew)));
        }

        // update the pointer
        *ppsz = ((*ppsz - pchOld) + pchNew);
    }
}

DWORD
NAME_VALUE_CHUNK::PrintToBuffer( IN CHAR * pchOut, IN OUT LPDWORD  pcchMax) const
{
    DBG_ASSERT( NULL != pchOut);
    DBG_ASSERT( NULL != pcchMax);

    NAME_VALUE_PAIR * pnv;
    DWORD cch = 0;

    if ( m_nPairs == 0) {
        *pcchMax = 0;
        return ( 0);
    }

    if ( 80 < *pcchMax) {
        cch = wsprintfA( pchOut, " NAME_VALUE_CHUNK: %08x;  NumPairs = %d\n",
                         this, m_nPairs);
    } else {
        cch = 80;
    }

    // Iterate over given pairs of name-value items and dump the output
    for ( pnv = (NAME_VALUE_PAIR *) m_rgNVP;
          pnv < ((NAME_VALUE_PAIR *) m_rgNVP) + m_nPairs;
          pnv++) {

        if ( pnv->pchName != NULL) {

            if ( (cch + pnv->cchName + pnv->cchValue + 3) < *pcchMax ) {
                pchOut[cch++] = '\t';
                CopyMemory( pchOut + cch, pnv->pchName, pnv->cchName);
                cch += pnv->cchName;
                pchOut[cch++] = '=';
                CopyMemory( pchOut + cch, pnv->pchValue, pnv->cchValue);
                cch += pnv->cchValue;
                pchOut[cch++] = '\n';
            } else {
                cch +=  pnv->cchName + pnv->cchValue + 3;
            }
        }
    } // for

    *pcchMax = cch;
    return (cch);
} // NAME_VALUE_CHUNK::PrintToBuffer()


BOOL
NAME_VALUE_CHUNK::AddEntry( IN const CHAR * pszHeader, IN DWORD cchHeader,
                            IN const CHAR * pszValue,  IN DWORD cchValue
                            )
{
    NAME_VALUE_PAIR  * pnp;

    DBG_ASSERT( IsSpaceAvailable());


    // Walk the array and pick the first location that is free.
    for ( pnp = (NAME_VALUE_PAIR * ) m_rgNVP;
          pnp < ((NAME_VALUE_PAIR * ) m_rgNVP + m_nPairs);
          pnp++) {

        if ( NULL == pnp->pchName) {

            // Found a blank one. Fill up the contents.
            // NOTE: We are not making copies of the contents,
            // We are just storing the pointers.
            pnp->pchName  = pszHeader;
            pnp->cchName  = cchHeader;
            pnp->pchValue = pszValue;
            pnp->cchValue = cchValue;

            return (TRUE);
        }
    } // for

    if ( m_nPairs < MAX_HEADERS_PER_CHUNK) {

        // store it at the next available location.
        pnp = (NAME_VALUE_PAIR * ) m_rgNVP + m_nPairs;
        m_nPairs++;
        pnp->pchName  = pszHeader;
        pnp->cchName  = cchHeader;
        pnp->pchValue = pszValue;
        pnp->cchValue = cchValue;
        return ( TRUE);
    }

    SetLastError( ERROR_INSUFFICIENT_BUFFER);
    return (FALSE);
} // NAME_VALUE_CHUNK::AddEntry()




NAME_VALUE_PAIR *
NAME_VALUE_CHUNK::FindEntry( IN const CHAR * pszHeader, IN DWORD cchHeader)
{
    NAME_VALUE_PAIR  * pnp;

    // Walk the array and pick the first location that is free.
    for ( pnp = (NAME_VALUE_PAIR * ) m_rgNVP;
          pnp < ((NAME_VALUE_PAIR * ) m_rgNVP + m_nPairs);
          pnp++) {

        DBG_ASSERT( (cchHeader != pnp->cchName) || (pnp->pchName != NULL) );

        if ( (cchHeader == pnp->cchName) &&
             !_strnicmp( pszHeader, pnp->pchName, cchHeader)
             ) {

            return ( pnp);
        }
    } // for

    SetLastError( ERROR_NO_MORE_ITEMS);
    return ( NULL);
} // NAME_VALUE_CHUNK::FindEntry()



NAME_VALUE_PAIR *
NAME_VALUE_CHUNK::FindMatchingOrFreeEntry( IN const CHAR * pszHeader,
                                           IN DWORD   cchHeader,
                                           IN LPBOOL  pfFound )
{
    NAME_VALUE_PAIR  * pnp;

    DBG_ASSERT( pszHeader != NULL);
    DBG_ASSERT( pfFound != NULL);

    // Walk the array and pick the first location that is free.
    for ( pnp = (NAME_VALUE_PAIR * ) m_rgNVP;
          pnp < ((NAME_VALUE_PAIR * ) m_rgNVP + m_nPairs);
          pnp++) {

        DBG_ASSERT( (cchHeader != pnp->cchName) || (pnp->pchName != NULL) );

        if ( (cchHeader == pnp->cchName) &&
             !_strnicmp( pszHeader, pnp->pchName, cchHeader)
             ) {

            *pfFound = TRUE;
            return ( pnp);
        }
    } // for

    if ( m_nPairs < MAX_HEADERS_PER_CHUNK) {

        // return the free entry
        DBG_ASSERT(((NAME_VALUE_PAIR * ) m_rgNVP + m_nPairs) == pnp);
        *pfFound = FALSE;
        return ( pnp);
    }

    SetLastError( ERROR_NO_MORE_ITEMS);
    return ( NULL);
} // NAME_VALUE_CHUNK::FindMatchingOrFreeEntry()


BOOL
NAME_VALUE_CHUNK::UpdatePointers(
    IN const CHAR * pchOld,
    IN DWORD cchLen,
    IN const CHAR * pchNew)
{
    if ( m_nPairs > 0) {

        NAME_VALUE_PAIR  * pnp;

        // Walk the array and pick the first location that is free.
        for ( pnp = (NAME_VALUE_PAIR * ) m_rgNVP;
              pnp < ((NAME_VALUE_PAIR * ) m_rgNVP + m_nPairs);
              pnp++) {

            UpdatePointer( &pnp->pchName, pchOld, cchLen, pchNew);
            UpdatePointer( &pnp->pchValue, pchOld, cchLen, pchNew);
        } // for
    }
    return (TRUE);
} // NAME_VALUE_CHUNK::UpdatePointers()




//
// Declare the header mapper. For the existing set of headers,
//  use of 14X14 hash bucket is sufficient.
//
HTTP_HEADER_MAPPER  HTTP_HEADERS::sm_hhm(14);

BOOL
HTTP_HEADERS::Initialize( VOID)
{
    sm_hhm.SetSigChars( 14);
    if ( !sm_hhm.Initialize()) {

        IF_DEBUG( ERROR) {
            DBGPRINTF(( DBG_CONTEXT,
                        " HTTP_HEADERS::Initialize() failed. \n"
                        ));
        }

        return ( FALSE);
    }

    return ( TRUE);
} // HTTP_HEADERS::Initialize()



VOID
HTTP_HEADERS::Cleanup( VOID)
{
    // Currently there is no function to cleanup sm_hmm :(

} // HTTP_HEADERS::Cleanup()



HTTP_HEADERS::HTTP_HEADERS(VOID)
    : m_chNull      ( '\0'),
      m_buffHeaders (),
      m_iMaxFastMapHeader ( 0),
      m_cchHeaders  ( 0),
      m_cchBuffHeaders  ( 0)
{
    InitializeListHead( &m_ActiveList);
    InitializeListHead( &m_FreeList);

    IF_DEBUG( INIT_CLEAN) {

        DBGPRINTF(( DBG_CONTEXT, "HTTP_HEADERS() => %08x\n", this));
    }

    Reset();

} // HTTP_HEADERS::HTTP_HEADERS()


HTTP_HEADERS::~HTTP_HEADERS( VOID)
{
    NAME_VALUE_CHUNK *      pNVC = NULL;
    
    while ( !IsListEmpty( &m_FreeList ) )
    {
        pNVC = CONTAINING_RECORD( m_FreeList.Flink,
                                  NAME_VALUE_CHUNK,
                                  m_listEntry );
        RemoveEntryList( &pNVC->m_listEntry );
        delete pNVC;
    }

    while ( !IsListEmpty( &m_ActiveList ) )
    {
        pNVC = CONTAINING_RECORD( m_ActiveList.Flink,
                                  NAME_VALUE_CHUNK,
                                  m_listEntry );
        RemoveEntryList( &pNVC->m_listEntry );
        delete pNVC;
    }

    IF_DEBUG( INIT_CLEAN) {

        DBGPRINTF(( DBG_CONTEXT, "deleted HTTP_HEADERS %08x\n", this));
    }

} // HTTP_HEADERS::~HTTP_HEADERS()


VOID
HTTP_HEADERS::Reset( VOID)
{
    m_cchHeaders = 0;
    m_rcInlinedHeader[0] = '\0';

    m_cchBuffHeaders = 0;
    m_buffHeaders.Resize( HH_MIN);

    m_iMaxFastMapHeader = 0;
    ZeroMemory( m_rgpszHeaders, sizeof( m_rgpszHeaders));
    // We will skip setting the m_rgpszHeaders to be NULL.
    // the iMaxFastMapHeader does the necessary job for the same

    //
    // Move the Name-Value chunks from active list to the free-list.
    //
    while ( !IsListEmpty( &m_ActiveList)) {

        PLIST_ENTRY pl = m_ActiveList.Flink;
        RemoveEntryList( pl);
        InsertTailList( &m_FreeList, pl);
    } // while

    DBG_ASSERT( IsListEmpty( &m_ActiveList));
    DBG_CODE( InitializeListHead( &m_ActiveList)); // just paranoid!

    return;
} // HTTP_HEADERS::Reset()

VOID
HTTP_HEADERS::CancelHeader( IN LPCSTR    pszName)
{
    HTTP_FAST_MAP_HEADERS iField;
    DWORD cchName = strlen( pszName);

    // Find and store the header and value
    if ( sm_hhm.FindOrdinal( pszName, cchName, (LPDWORD ) &iField ) ) {

        FastMapCancel( iField);
    }  else {
        CancelHeaderInChunks( pszName, cchName);
    }

} // HTTP_HEADERS::CancelHeader()



BOOL
HTTP_HEADERS::StoreHeader(IN const CHAR * pszHeader, IN DWORD cchHeader,
                          IN const CHAR * pszValue,  IN DWORD cchValue
                          )
/*++
  This function is used to copy the header values instead of just
  storing the pointer values. This function can be used by filters to set/reset
  headers.
--*/
{
    HTTP_FAST_MAP_HEADERS iField;

    // Find and store the header and value
    if ( sm_hhm.FindOrdinal( pszHeader, cchHeader, (LPDWORD ) &iField ) ) {

        return ( FastMapStoreWithConcat( iField, pszValue, cchValue) );
    }
    else
    {
    return ( AddEntryToChunks( pszHeader, cchHeader, pszValue,  cchValue, TRUE));
    }
} // HTTP_HEADERS::StoreHeader()

BOOL
HTTP_HEADERS::StoreHeader(IN const CHAR * pszHeader,
                          IN const CHAR * pszValue
                          )
{
    return ( StoreHeader( pszHeader, strlen( pszHeader),
                          pszValue,  strlen( pszValue)
                          )
             );
} // HTTP_HEADERS::StoreHeader()


BOOL
HTTP_HEADERS::FastMapStoreWithConcat( IN HTTP_FAST_MAP_HEADERS hfm,
                                      IN LPCSTR pszValue, IN DWORD cchValue)
{
    // NYI: Following storage introduces fragmentation,
    //  which we do not care about for now :(

    return (ConcatToHolder( m_rgpszHeaders + hfm, pszValue, cchValue));

} // FastMapStoreWithConcat()



VOID
HTTP_HEADERS::PrintToBuffer( IN CHAR * pchBuffer, IN OUT LPDWORD pcchMax) const
{
    DWORD cb;
    PLIST_ENTRY pl;

    DBG_ASSERT( pchBuffer != NULL);
    DBG_ASSERT( pcchMax != NULL);

    // 0. Print the summary of the object
    // 1. Print all the Fast Map headers
    // 2. Print the rest of the headers

    if( 100 < *pcchMax) {
        cb = wsprintfA( pchBuffer,
                        "\nHTTP_HEADERS (%08x). cchHeaders = %d (buff = %d/%d)\n"
                        " Fast-Map headers:  MaxFastMapHeaders = %d\n"
                        ,
                        this, m_cchHeaders,
                        m_cchBuffHeaders, m_buffHeaders.QuerySize(),
                        FastMapMaxIndex()
                        );
    } else {
        cb = 100;
    }


    for ( DWORD i = 0; i < FastMapMaxIndex(); i++) {

        if ( m_rgpszHeaders[i] != NULL) {
            if ( cb + 200 < *pcchMax) {
                cb += wsprintfA( pchBuffer + cb,
                                 "\t%s = %s\n",
                                 sm_hhm.FindName( i),
                                 m_rgpszHeaders[i]
                                 );
            } else {
                cb += 200;
            }
        }
    } // for

    // Print all other headers starting with the cached 1st chunk
    DWORD cb1 = (cb < *pcchMax) ? *pcchMax - cb : 0;

    for ( pl =  m_ActiveList.Flink; pl != &m_ActiveList; pl = pl->Flink) {

        NAME_VALUE_CHUNK * pnvc =
            CONTAINING_RECORD( pl, NAME_VALUE_CHUNK, m_listEntry);

        cb1 = (cb < *pcchMax) ? *pcchMax - cb : 0;
        cb += pnvc->PrintToBuffer ( pchBuffer + cb, &cb1);
    } // for

    pchBuffer[cb] = '\0';

    //
    // Print the Raw header from buffer ... NYI
    //

    if ( cb + 2 < *pcchMax ) {
        lstrcat( pchBuffer + cb, "\n\n");
        cb += 2;
    } else {
        cb += 2;
    }

    *pcchMax = cb;

    return;
} // HTTP_HEADERS::PrintToBuffer()


BOOL
HTTP_HEADERS::UpdatePointers(
    IN const CHAR * pchOld,
    IN DWORD cchLen,
    IN const CHAR * pchNew)
{

    // REMOVE
    IF_DEBUG( ERROR) {
        DBGPRINTF(( DBG_CONTEXT,
                    "%08x::UpdatePointers( %08x, %d, %08x) - is costly\n",
                    this, pchOld, cchLen, pchNew));
    }

    DBG_ASSERT( pchOld != pchNew); // if this is true why call this function?

    // 1. Update the fast map pointers.
    LPCSTR * ppsz;
    for ( ppsz = m_rgpszHeaders;
          ppsz < (m_rgpszHeaders +  MAX_HTTP_FAST_MAP_HEADERS);
          ppsz++) {

        UpdatePointer( ppsz, pchOld, cchLen, pchNew);
    } // for

    // 3. Update pointers in the name-value chunk list
    PLIST_ENTRY pl;
    for ( pl =  m_ActiveList.Flink; pl != &m_ActiveList; pl = pl->Flink) {

        NAME_VALUE_CHUNK * pnvc =
            CONTAINING_RECORD( pl, NAME_VALUE_CHUNK, m_listEntry);

        // REMOVE
        DBGPRINTF(( DBG_CONTEXT,
                    "HH(%08x)::UpdatePointers( %08x, %d, %08x)"
                    " for the NVC %08x (pl = %08x)\n",
                    this, pchOld, cchLen, pchNew, pnvc, pl));

        pnvc->UpdatePointers( pchOld, cchLen, pchNew);
    } // for

    return ( TRUE);
} // HTTP_HEADERS::UpdatePointers()


BOOL
HTTP_HEADERS::MakeRoomInBuffer( IN DWORD cchReqd, IN LPCSTR * ppszVal)
{

    // REMOVE
    DBGPRINTF(( DBG_CONTEXT, "%08x:: MakeRoomInBuffer( %d, %08x). buff=%08x. size=%d\n",
                this, cchReqd, ppszVal,&m_buffHeaders, m_buffHeaders.QuerySize()));

    //
    // Bug 136637 : Because of the way we move our headers around when we get a new header
    // value for an existing header, it's really easy to chew up lots of memory rather 
    // quickly, so we'll artificially limit the size of the buffer to avoid denial-of-service
    // attacks. 
    // 
    if ( cchReqd > HH_MAX )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "Reached max buffer size (%d), refusing request to resize buffer to %d bytes\n",
                   HH_MAX,
                   cchReqd));
        SetLastError( ERROR_OUTOFMEMORY );
        return FALSE;
    }

    if ( cchReqd > m_buffHeaders.QuerySize()) {

        // cache old pointer to update the other pointers properly
        LPSTR pszOld = (LPSTR ) m_buffHeaders.QueryPtr();

        if ( !m_buffHeaders.Resize( cchReqd, HH_GROW_BY)) {

            IF_DEBUG( ERROR) {
                DBGPRINTF(( DBG_CONTEXT, "%08x::Unable to allocate %d bytes\n",
                            this, cchReqd));
            }
            return ( FALSE);
        }

        DBG_ASSERT( cchReqd <= m_buffHeaders.QuerySize());
        LPSTR pszNew = (LPSTR ) m_buffHeaders.QueryPtr();
        if ( pszNew != pszOld) {

            // Trouble starts.
            // I have to update all the guys pointing inside the old blob
            // especially the pointers in
            //   the range (pszOld  to pszOld + m_cchBuffHeaders)

            UpdatePointer(ppszVal, pszOld, m_cchBuffHeaders, pszNew);

            // REMOVE
            DBGPRINTF(( DBG_CONTEXT, "%08x:: MakeRoomInBuffer( %d, %08x). buff=%08x. size=%d\n",
                        this, cchReqd, ppszVal,&m_buffHeaders, m_buffHeaders.QuerySize()));

            return ( UpdatePointers( pszOld, m_cchBuffHeaders, pszNew));
        }

        // We are just lucky to be able to have reallocated at same place.
    }

    return ( TRUE);
} // HTTP_HEADERS::MakeRoomInBuffer()


VOID
HTTP_HEADERS::Print( VOID)  const
{
    CHAR  pchBuffer[ 20000];
    DWORD cchMax = sizeof( pchBuffer);

    PrintToBuffer( pchBuffer, &cchMax);

    DBGDUMP(( DBG_CONTEXT, pchBuffer));

} // HTTP_HEADERS::Print()


CHAR *
HTTP_HEADERS::FindValue( IN LPCSTR pszName, OUT LPDWORD pcchValue)
{
    DWORD cchName = strlen( pszName);
    HTTP_FAST_MAP_HEADERS iField;

    //
    //  1. Lookup in the fast map for this item
    //

    if ( sm_hhm.FindOrdinal( pszName, cchName, (LPDWORD ) &iField)) {

        // found in the fast-map.
        CHAR * pszValue = (CHAR * ) FastMapQueryValue( iField);

        if ( pcchValue != NULL) {
            *pcchValue = (( pszValue != NULL) ? strlen( pszValue) : 0);
        }

        return ( pszValue);
    }

    // 2. Search in the slow list - name-value-chunks
    NAME_VALUE_PAIR * pnp = FindValueInChunks( pszName, cchName);

    if ( pnp != NULL) {

        if ( pcchValue != NULL) {
            DBG_ASSERT( pnp->pchValue != NULL);
            *pcchValue = pnp->cchValue;
        }

        return ( (CHAR *) pnp->pchValue);
    }

    return ( NULL);
} // HTTP_HEADERS::FindValue()


NAME_VALUE_PAIR *
HTTP_HEADERS::FindValueInChunks( IN LPCSTR  pszName, IN DWORD cchName)
{
    PLIST_ENTRY pl;
    NAME_VALUE_PAIR * pnp = NULL;

    // find a Name-value-pair/chunk that holds this entry.
    for ( pl = m_ActiveList.Flink;
          (pl != &m_ActiveList);
          pl = pl->Flink) {

        NAME_VALUE_CHUNK *
            pnc = CONTAINING_RECORD( pl, NAME_VALUE_CHUNK, m_listEntry);

        pnp = pnc->FindEntry( pszName, cchName);

        if ( NULL != pnp) {
            // we found the required name-value-pair.stop searching
            break;
        }
    } // for

    return ( pnp);
} // HTTP_HEADERS::FindValueInChunks()


VOID
HTTP_HEADERS::CancelHeaderInChunks( IN LPCSTR pszName, IN DWORD cchName)
{
    PLIST_ENTRY pl;
    NAME_VALUE_PAIR * pnp = NULL;
    NAME_VALUE_CHUNK * pnc;

    // NYI:  This function can benefit from better implementation
    //  instead of moving memory around.
    // Since the freq. of use of this func is less, we will not optimize :(

    // find the Name-value-pair/chunk that holds this entry.
    for ( pl = m_ActiveList.Flink;
          (pnp == NULL) && (pl != &m_ActiveList);
          pl = pl->Flink) {

            pnc = CONTAINING_RECORD( pl, NAME_VALUE_CHUNK, m_listEntry);

        pnp = pnc->FindEntry( pszName, cchName);
    } // for

    if ( pnp != NULL) {

        // pnp - current item
        // pnc - the current chunk

        // to cancel the item, just left-shift the array of
        //  NAME_VALUE_PAIRS in the chunk and reset the m_nPairs value

        DBG_ASSERT( (pnp >= pnc->m_rgNVP) &&
                    (pnp < pnc->m_rgNVP + pnc->m_nPairs));

        DBG_ASSERT( (pnc->m_nPairs  - (pnp - pnc->m_rgNVP)) >= 1 );
        MoveMemory( pnp, (pnp + 1),
                    ((pnc->m_nPairs - 1 - (pnp - pnc->m_rgNVP)) *
                     sizeof( NAME_VALUE_PAIR))
                    );
        pnc->m_nPairs--;

        // NYI:  if pnc->m_nPairs == 0,
        // we can move this away from the active list
    }

    return;
} // CancelHeaderInChunks()



BOOL
HTTP_HEADERS::NextPair( IN OUT HH_ITERATOR *   phi,
                        OUT NAME_VALUE_PAIR ** ppnp
                        )
{
    DBG_ASSERT( phi );
    DBG_ASSERT( ppnp );

    if ( phi->dwOrdinal < FastMapMaxIndex()) {

        // Iterate over the FastMap headers ...
        for ( DWORD i = phi->dwOrdinal; i < FastMapMaxIndex(); i++ ) {
            if ( m_rgpszHeaders[i] != NULL) {

                NAME_VALUE_PAIR * pnp = &phi->np;
                // found a non-NULL value.

                pnp->pchValue = m_rgpszHeaders[i];
                pnp->pchName  = sm_hhm.FindName( i);

                // NYI: It will be nice to get the length directly :(
                pnp->cchName = strlen( pnp->pchName);
                pnp->cchValue= strlen( pnp->pchValue);
                phi->dwOrdinal = i + 1;
                *ppnp = pnp;
                return ( TRUE);
            }
        } // for

        // we exhausted the fast-map. Fall through after updating Ordinal
        phi->dwOrdinal = (i);
    }

    //
    // Find the pair in the chunk
    //

    return ( NextPairInChunks( phi, ppnp));
} // HTTP_HEADERS::NextPair()


BOOL
HTTP_HEADERS::NextPairInChunks( IN OUT HH_ITERATOR *   phi,
                                OUT NAME_VALUE_PAIR ** ppnp
                                )
{
    DBG_ASSERT( phi);
    DBG_ASSERT( ppnp);
    DBG_ASSERT( phi->dwOrdinal >= FastMapMaxIndex());
    PLIST_ENTRY pl;

    do {

        PLIST_ENTRY pl = (PLIST_ENTRY ) phi->pChunk;
        if ( pl == &m_ActiveList) {
            break;
        }

        NAME_VALUE_CHUNK * pnc =
            (NAME_VALUE_CHUNK *) CONTAINING_RECORD( pl, NAME_VALUE_CHUNK,
                                                    m_listEntry);
        while ( phi->dwPair < pnc->m_nPairs) {

            // extract the current pair, update pair pointer and return
            *ppnp = (NAME_VALUE_PAIR *) (pnc->m_rgNVP + phi->dwPair);
            phi->dwPair++;

            if ( (*ppnp)->pchName ) {
                return ( TRUE);
            }
        }

        // we could not find any in the current chunk. Move to next chunk.
        phi->pChunk = (PVOID)pnc->m_listEntry.Flink;
        phi->dwPair = 0;  // pair # within the chunk
    } while ( TRUE);

    SetLastError( ERROR_NO_MORE_ITEMS);
    return ( FALSE);
} // HTTP_HEADERS::NextPairInChunks()



BOOL
HTTP_HEADERS::AddEntryToChunks(
    IN const CHAR * pszHeader,
    IN DWORD        cchHeader,
    IN const CHAR * pszValue,
    IN DWORD        cchValue,
    BOOL            fCopyValue
    )
/*++
  This function stores the <header, value> pair for headers not found
  in the fast-map. It checks to see if the header already exists
  with some value. If it does, then the new value is just concatenated
  to the old one. Else the new value is stored separately in the first
  available free chunk.

  If there is no free chunk available, this function also allocates a free
  chunk and stores the data in the new chunk.
--*/
{
    // Store the header that is not part of the Fast Map

    PLIST_ENTRY pl;
    NAME_VALUE_CHUNK * pnc;
    NAME_VALUE_PAIR  * pnp;
    NAME_VALUE_CHUNK * pncFirst = NULL;
    NAME_VALUE_PAIR  * pnpFirst = NULL;
    BOOL fRet = FALSE;

    // find a Name-value-pair/chunk that can hold this entry.
    for ( pl = m_ActiveList.Flink;
          (pl != &m_ActiveList);
          pl = pl->Flink)
    {
        BOOL fFound = FALSE;

        pnc = CONTAINING_RECORD( pl, NAME_VALUE_CHUNK, m_listEntry);

        pnp = pnc->FindMatchingOrFreeEntry( pszHeader, cchHeader, &fFound);

        //
        // Found a matching entry, so update
        //
    
        if ( fFound )
        {
            DBG_ASSERT( pnp != NULL);

            // pnc points to the chunk containing the matched item
            // pnp points to the exact pair that matched up

            DBG_ASSERT( (pnp->cchName == cchHeader) &&
                        (!_strnicmp( pnp->pchName, pszHeader, cchHeader))
                        );

            IF_DEBUG( ERROR)
            {
                DBGPRINTF(( DBG_CONTEXT, "Match For (%s) found at PNP=%08x\n",
                            pszHeader, pnp));
            }

            // Concat the given value to the existing value element.
            // Nothing more needs to be done
        
            fRet = ConcatToHolder( &pnp->pchValue, pszValue, cchValue);
            if  ( fRet)
            {
                // update the length of the datum.
                pnp->cchValue += (1 + cchValue);  // 1 for the ',' concat sign.
            }
            return ( fRet);
        }
        else if ( pnp != NULL && pncFirst == NULL)
        {
            // cache it for later use, if header is never found
            pncFirst = pnc;
            pnpFirst = pnp;
        }
    } // for

    if (pncFirst == NULL )
    {
        // No match found. No free chunk is available.
        // Pull a new one from free list or create one
        if ( IsListEmpty( &m_FreeList))
        {
            pncFirst = new NAME_VALUE_CHUNK();
            if ( NULL == pncFirst)
            {
                SetLastError( ERROR_NOT_ENOUGH_MEMORY);
                return ( FALSE);
            }
        }
        else
        {
            // pull one from the free list and use it.
            pl = m_FreeList.Flink;
            RemoveEntryList( pl);
            pncFirst = CONTAINING_RECORD( pl, NAME_VALUE_CHUNK, m_listEntry);
            pncFirst->Reset();
        }

        InsertTailList( &m_ActiveList, &pncFirst->m_listEntry);
        DBG_ASSERT( pncFirst->m_nPairs == 0);
        pnpFirst =  ((NAME_VALUE_PAIR * ) pncFirst->m_rgNVP);
    }

    //
    // At this point, we know it's a new header, so store the new <header, value> pair
    // in pnp and increment count of pairs.
    //

    DBG_ASSERT( NULL != pncFirst);
    DBG_ASSERT( NULL != pnpFirst);

    //
    // Sometimes, we need to make a copy of the header eg when the header to be added
    // comes from a filter. At other times, we can just store a copy of the pointer,
    // because we know it'll be valid for the duration of the use of the data structure
    //

    if (fCopyValue)
    {
        //
        // Copy actual values : ConcatToHolder assumes first parameter points to NULL
        // if it's a new value to be stored
        //

        //
        // Copy the header name
        //
        pnpFirst->pchName = NULL;
        fRet = ConcatToHolder(&pnpFirst->pchName, pszHeader, cchHeader);

        if (fRet)
        {
            pnpFirst->cchName  = cchHeader;
        }
        else
        {
            return FALSE;
        }

        //
        //Copy the header value
        //
        pnpFirst->pchValue = NULL;
        fRet = ConcatToHolder(&pnpFirst->pchValue, pszValue, cchValue);

        if (fRet)
        {
            pnpFirst->cchValue = cchValue;

            if ( pnpFirst == (NAME_VALUE_PAIR * ) pncFirst->m_rgNVP + pncFirst->m_nPairs )
            {
                pncFirst->m_nPairs++;
            }
        }
        else
        {
            pnpFirst->pchName = NULL;
            return FALSE;
        }
    }
    else
    {
        //
        // Just copy pointer to value
        //
        pnpFirst->pchName = pszHeader;
        pnpFirst->cchName = cchHeader;
        pnpFirst->pchValue = pszValue;
        pnpFirst->cchValue = cchValue;

        if ( pnpFirst == (NAME_VALUE_PAIR * ) pncFirst->m_rgNVP + pncFirst->m_nPairs )
        {
            pncFirst->m_nPairs++;
        }
        fRet = TRUE;
    }

    DBG_ASSERT( pncFirst->m_nPairs <= MAX_HEADERS_PER_CHUNK);

    return ( fRet );
} // HTTP_HEADERS::AddEntryToChunks()



BOOL
HTTP_HEADERS::ConcatToHolder( IN LPCSTR * ppsz,
                              IN LPCSTR pszNew,
                              IN DWORD  cchNew
                              )
/*++

  Given an internal pointer ppsz of the HTTP_HEADERS object,
   this function appends the new value to the old value present
   using ',' as the concatenation character.

  It automatically allocates room and grows buffers, updates pointers, etc
   if need be.

--*/
{
    BOOL fRet = TRUE;
    LPCSTR pszOld = *ppsz;
    DWORD  cchOld = pszOld ? strlen( pszOld) : 0;
    DWORD  cchReqd = cchOld + cchNew + 2;

    // Find if we have enough space in the inlined buffer
    if ( ( m_cchHeaders + cchReqd < sizeof( m_rcInlinedHeader))
         ) {

        // Aha we are lucky. Make a copy at the end and form concatenated result
        *ppsz = m_rcInlinedHeader + m_cchHeaders;
        m_cchHeaders += cchReqd;
    } else {

        // Clearly we do not have room in the Inlined Header,
        //  store the stuff in the aux buffer area.

        // Find if space is sufficient.
        //  This will automatically alloc and update pointers
        if ( MakeRoomInBuffer( (m_cchBuffHeaders + cchReqd), &pszNew)
             ){

            pszOld = *ppsz;  // get the new pointer (since it could have moved)
            LPSTR pszBuf = (LPSTR ) m_buffHeaders.QueryPtr();

            // we have space at the end of the buffer here. Use this space.
            *ppsz = pszBuf + m_cchBuffHeaders;
            m_cchBuffHeaders += cchReqd;
        } else {

            DBGPRINTF(( DBG_CONTEXT,
                        "Unable to create room for %d characters \n",
                        m_cchBuffHeaders + cchOld + cchNew + 3));
            return ( FALSE);
        }
    }

    if ( !cchOld )
    {
        CopyMemory( (PVOID) (*ppsz), pszNew, cchNew + 1 );
    }
    else
    {
        // Format the value as :=  <old> ',' <new>
        CopyMemory( (PVOID ) *ppsz, pszOld, cchOld);
        ((CHAR *) *ppsz)[cchOld] = ','; // concat character
        CopyMemory( (PVOID ) (*ppsz + cchOld + 1), pszNew, cchNew + 1);
    }

    DBG_ASSERT( fRet == TRUE);
    return ( fRet);
} // HTTP_HEADERS::ConcatToHolder()


/**************************************************
 *  PARSER for the HTTP_HEADERS
 **************************************************/

inline const CHAR *
SkipLeadingWhiteSpace( IN const CHAR * pchStart, IN DWORD cchLen)
{
    const CHAR * pchScan;

    for ( pchScan = pchStart;
          ((pchScan < (pchStart + cchLen)) && isspace( (UCHAR)(*pchScan)));
          pchScan++)
        ;

    return ( pchScan);
} // SkipLeadingWhiteSpace()

BOOL
HTTP_HEADERS::ParseHeaderFirstLine( IN const CHAR * pchFirstLine,
                                    IN CHAR * pchScan,
                                    IN DWORD cchFirstLine)
/*++
  Description:
    Extract the HTTP method, URL, & HTTP-version strings from the first
    line of header block.
    Input:  <Method> <URL> HTTP/<Version>
    Use <blank> or <tab> as the delimiter.
    NYI:  What about other delimiters?

    We will do only forward scans, because
     1) the header can be malformed
     2) HTTP/0.9 requests do not contain the version string
     3) there may be more than 3 parameters on the first line.
    In all the above cases reverse scans will cause trouble.

    Note that a line termination can be by using \r\n or just \n :(

  Arguments:
    pchFirstLine - pointer to character stream containing the
                   first char of the line
    pchScan      - points to the end of the first line.
    cchFirstLine - count of characters on the first line

  Returns:
    TRUE on success & FALSE for failure.
    Use GetLastError() to get correct Win32 error code on failure.
--*/
{
    LPSTR      pszScan2;
    pszScan2 = (LPSTR ) memchr ( pchFirstLine, ' ', cchFirstLine);

    if ( NULL != pszScan2) {

        LPSTR pszScan3;

        *pszScan2++ = '\0';  // replace the blank with a null char

        while ( _HTTP_IS_LINEAR_SPACE( *pszScan2 ) )
        {
            ++pszScan2;
        }

        //
        //  pszScan2 now points to the URL sent
        //

        DWORD cReq = DIFF(pchScan - pszScan2);
        pszScan3 = (LPSTR ) memchr( pszScan2, ' ', cReq);

        if ( NULL != pszScan3 ) {

            *pszScan3++ = '\0';

            while ( _HTTP_IS_LINEAR_SPACE( *pszScan3 ) )
            {
                ++pszScan3;
            }

            // pszScan3 should be now pointing to start of version info

            // Note that we are not removing spaces between the version
            // and the line delimiter

            pchScan[(( (pchScan > pszScan3) && (pchScan[-1] == '\r')) ?
                     -1: 0)] = '\0';
        } else {

            // only 2 parameters. No version is present.
            //  Point pszScan3 to the null-part of pszScan2)

            pszScan3 =
                pchScan + ((pchScan[-1] == '\r') ? -1 : 0);
            *pszScan3 = '\0';
        }

        //
        // We know that we are parsing a new request.
        // So we are not checking for concat during fast-map store here.
        //
        FastMapStore( HM_VER, pszScan3);
        FastMapStore( HM_URL, pszScan2 );
        FastMapStore( HM_MET, pchFirstLine);
    } else {

        IF_DEBUG( ERROR) {
            DBGPRINTF(( DBG_CONTEXT,
                        " The scan for fields where they are not separated "
                        " with space is intentionally left out!\n"
                        ));
        }
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    //
    // everything is happy here ... return success
    //
    return ( TRUE);

} // HTTP_HEADERS::ParseHeaderFirstLine()



BOOL
HTTP_HEADERS::ParseInput( IN const CHAR * pchHeaderBlob,
                          IN DWORD        cchLen,
                          OUT DWORD *     pcbExtraData
                          )
/*++
  Description:
    This function parses the input string and extracts the HTTP headers
    for storage inside the http header structure. Fast-map headers are stored
    in the dedicated fast-map storage area. Non-standard headers (that are
    not part of) fast-map are stored inside the Name-value chunks.

    The Grammar for incoming HTTP headers is:

       <HTTP-verb>  <URL>  <HTTP-version>
       {<Header-Name>:<Header-Value> { {\r\n} | {\n}}}*

     where,
       <HTTP-verb>     == A+
       <URL>           == /[A|D]*
       <HTTP-version>  == HTTP/D+.D+
       <Header-Name>   == A[A|D]*
       <Header-Value>  == [A|D]*
       <CRLF>          == \r\n
       A               == any ascii character except ' ' '\t' '\n' '\r'
       D               == 0-9

  Arguments:
    pchHeaderBlob - pointer to header containing the header blob (+ maybe
                      the body of the request).
    cchLen        - length of the header block alone.
    pcbExtraData  - pointer to a DWORD which on return will contain the
                      number of extra characters of data present in the
                      blob given.

  Returns:
    TRUE on successfully parsing and splitting the headers apart.
    FALSE if there is any failure.
--*/
{
    CHAR *    pchScan;
    CHAR *    pchRequest;
    DWORD     cReq;

    IF_DEBUG( INIT_CLEAN)
    {
        DBGPRINTF(( DBG_CONTEXT, "%08x::ParseInput( %08x:, %d) \n"
                    "Input Headers:\n%s\n",
                    this, pchHeaderBlob, cchLen, pchHeaderBlob));
    }

    //
    // 1. Skip all the leading spaces and ignore them all.
    //   We do not need these fields
    //

    pchScan = (CHAR *) SkipLeadingWhiteSpace( pchHeaderBlob, cchLen);
    cchLen -= DIFF(pchScan - pchHeaderBlob);

    //
    // 2. Make a copy of the incoming header blob so that we can own the
    //    input headers and munge it in our own fashion
    //  NYI: One can optimize this by selectively copying segments that
    //    are worth using (values), but that will be costly due to
    //    multiple small CopyMemory() operations.
    //
    if ( cchLen < sizeof( m_rcInlinedHeader)) {

        pchRequest = (CHAR * ) m_rcInlinedHeader;
        m_cchHeaders = cchLen;

    } else {

        if ( !m_buffHeaders.Resize( cchLen + 4, HH_GROW_BY)) {
            return ( FALSE);
        }

        pchRequest = (CHAR * ) m_buffHeaders.QueryPtr();
        m_cchBuffHeaders = cchLen;
    }

    // 2a. copy the header to the buffer
    CopyMemory( (PVOID ) pchRequest, pchScan, cchLen);

    //
    // pchRequest points to the local copy of the request headers
    //

    DBG_ASSERT( (pchRequest == m_rcInlinedHeader) ||
                (pchRequest == m_buffHeaders.QueryPtr())
                );

    //
    // 3. Get the first line and extract the method string
    //
    pchScan = (CHAR * ) memchr( pchRequest, '\n', cchLen);

    if ( pchScan == NULL ) {

        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    //
    // 4. Extract the Method, URL and Version Number
    //

    if ( !ParseHeaderFirstLine( pchRequest, pchScan, DIFF(pchScan - pchRequest))) {

        return ( FALSE);
    }

    //
    // 5. Extract all other headers
    //

    LPSTR pszHeader = pchScan + 1;
    cReq = DIFF(pchRequest + cchLen - pszHeader);
    LPSTR pchEnd = pszHeader + cReq;
    LPSTR pszEol, pszValue;

    //
    // pszHeader ( and thus pszEnd & pszEol ) are relative to pchRequest.
    // This needs to be preserved in this loop.
    //

    for ( ; (*pszHeader != '\r' || (cReq > 1 && *(pszHeader + 1) != '\n')) &&
            (*pszHeader != '\n') &&
            (pszValue = (LPSTR)memchr( pszHeader, ':', cReq ));
          pszHeader = (pszEol + 1), cReq = DIFF(pchEnd - pszHeader)
          )
    {
        UINT chN = (UINT)(*(PBYTE)++pszValue);

        //            *pszValue = '\0';
        int cchHeader = DIFF(pszValue - pszHeader);

        pszEol = (LPSTR ) memchr( pszValue, '\n', cReq - cchHeader);

        if ( NULL == pszEol ) {

            //
            // Aha! we found a completely malformed header block here.
            // Let us return a failure to the caller.
            //

            IF_DEBUG( ERROR) {
                DBGPRINTF(( DBG_CONTEXT,
                            " The scan for header-value blocks found a line"
                            " not properly terminated with \'\\n\'\n"
                            ));
            }
            SetLastError( ERROR_INVALID_PARAMETER );
            return FALSE;
        }

        DBG_ASSERT( NULL != pszEol );

        DWORD  cchValue;
        DWORD  iField;

        // skip spaces if any.
        if ( _HTTP_IS_LINEAR_SPACE( (CHAR)chN ) )
        {
            while ( _HTTP_IS_LINEAR_SPACE( (CHAR)(*(PBYTE)++pszValue) ) )
                ;
        }

        // Terminate the value string
        if ( (pszEol > pszValue) && (pszEol[-1] == '\r') )
        {

            pszEol[-1] = '\0';
            cchValue = DIFF(pszEol - pszValue - 1);

        }
        else
        {

            pszEol[0] = '\0';
            cchValue = DIFF(pszEol - pszValue);
        }

        IF_DEBUG( INIT_CLEAN )
        {
            DBGPRINTF((DBG_CONTEXT, "\t[%s] = %s\n", pszHeader, pszValue ));
        }

        {  // Store the header - inline to reduce cost

            HTTP_FAST_MAP_HEADERS iField;
            BOOL  fRet = TRUE;

            // Find and store the header and value
            if ( sm_hhm.FindOrdinal( pszHeader, cchHeader,
                    (LPDWORD ) &iField ) )
            {

                if ( FastMapQueryValue(iField) == NULL )
                {

                // Store the item and continue scan for next header
                FastMapStore( iField, pszValue);
                continue;

                }
                else
                {
                    // FastMapStoreWithConcat can resize the header
                    // buffer. If the headers we're scanning are
                    // coming out of the header buffer, we'll need
                    // to update pszHeader later, so save the current
                    // pointer just in case.

                    CHAR        *pOldBuff = (CHAR *)m_buffHeaders.QueryPtr();

                    fRet =  FastMapStoreWithConcat( iField, pszValue,
                                                            cchValue);

                    if ( !fRet)
                    {

                        IF_DEBUG( ERROR)
                        {

                            DBGPRINTF(( DBG_CONTEXT, "Failed to StoreHeader %s"
                                "in fast map with concat\n",
                                pszHeader));
                        }

                        return ( FALSE);
                    }

                    // See if the buffer has changed. If it has,
                    // update the end pointer and the eol pointer. pszHeader
                    // gets updated at the end of the loop from pszEol. Be
                    // careful if you try and use any other pointers between
                    // here and the end of the loop.
                    // We update these pointers only if they were already pointing
                    // in alloced buffer space ( i.e. not using the m_rcInlinedHeader buffer )

                    if (pOldBuff != (CHAR *)m_buffHeaders.QueryPtr() &&
                        pOldBuff == pchRequest )
                    {
                        // Buffer got resized, fix up appropriate pointers.

                        pchEnd = (CHAR *)m_buffHeaders.QueryPtr() +
                                (pchEnd - pchRequest);
                        pszEol = (CHAR *)m_buffHeaders.QueryPtr() +
                                (pszEol - pchRequest);
                        pchRequest = (CHAR *)m_buffHeaders.QueryPtr();
                    }
                }
            }
            else
            {
                // AddEntry to chunks also has resizing buffer issues. See
                // comments above.

                CHAR        *pOldBuff = (CHAR *)m_buffHeaders.QueryPtr();

                fRet = AddEntryToChunks( pszHeader, cchHeader, pszValue,
                                            cchValue);
                if ( !fRet)
                {
                    IF_DEBUG( ERROR)
                    {

                    DBGPRINTF(( DBG_CONTEXT,
                        "Failed to StoreHeader %s in chunks\n",
                        pszHeader));
                    }
                    return ( FALSE);
                }

                if (pOldBuff != (CHAR *)m_buffHeaders.QueryPtr() &&
                    pOldBuff == pchRequest )
                {
                    // Buffer got resized, fix up appropriate pointers.

                    pchEnd = (CHAR *)m_buffHeaders.QueryPtr() +
                            (pchEnd - pchRequest);
                    pszEol = (CHAR *)m_buffHeaders.QueryPtr() +
                            (pszEol - pchRequest);
                    pchRequest = (CHAR *)m_buffHeaders.QueryPtr();
                }
            }
        }

    } // for()

    if ( (*pszHeader == '\r') || (*pszHeader == '\n') )
    {

        // blank header line - end of the parsing
        while ( cReq )
        {
            --cReq;
            if ( *pszHeader++ == '\n' )
            {
                break;
            }
        }
    }

    *pcbExtraData = DIFF(pchEnd - pszHeader);

    return ( TRUE);

} // HTTP_HEADERS::ParseInput()


/************************ End of File ***********************/