//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// // // $Header: $ // $NoKeywords: $ // // Serialization buffer //===========================================================================// #pragma warning (disable : 4514) #include "utlbuffer.h" #include #include #include #include #include #include "tier1/strtools.h" #include "tier1/characterset.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // Character conversions for C strings //----------------------------------------------------------------------------- class CUtlCStringConversion : public CUtlCharConversion { public: CUtlCStringConversion( char nEscapeChar, const char *pDelimiter, int nCount, ConversionArray_t *pArray ); // Finds a conversion for the passed-in string, returns length virtual char FindConversion( const char *pString, int *pLength ); private: char m_pConversion[256]; }; //----------------------------------------------------------------------------- // Character conversions for no-escape sequence strings //----------------------------------------------------------------------------- class CUtlNoEscConversion : public CUtlCharConversion { public: CUtlNoEscConversion( char nEscapeChar, const char *pDelimiter, int nCount, ConversionArray_t *pArray ) : CUtlCharConversion( nEscapeChar, pDelimiter, nCount, pArray ) {} // Finds a conversion for the passed-in string, returns length virtual char FindConversion( const char *pString, int *pLength ) { *pLength = 0; return 0; } }; //----------------------------------------------------------------------------- // List of character conversions //----------------------------------------------------------------------------- BEGIN_CUSTOM_CHAR_CONVERSION( CUtlCStringConversion, s_StringCharConversion, "\"", '\\' ) { '\n', "n" }, { '\t', "t" }, { '\v', "v" }, { '\b', "b" }, { '\r', "r" }, { '\f', "f" }, { '\a', "a" }, { '\\', "\\" }, { '\?', "\?" }, { '\'', "\'" }, { '\"', "\"" }, END_CUSTOM_CHAR_CONVERSION( CUtlCStringConversion, s_StringCharConversion, "\"", '\\' ) CUtlCharConversion *GetCStringCharConversion() { return &s_StringCharConversion; } BEGIN_CUSTOM_CHAR_CONVERSION( CUtlNoEscConversion, s_NoEscConversion, "\"", 0x7F ) { 0x7F, "" }, END_CUSTOM_CHAR_CONVERSION( CUtlNoEscConversion, s_NoEscConversion, "\"", 0x7F ) CUtlCharConversion *GetNoEscCharConversion() { return &s_NoEscConversion; } //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CUtlCStringConversion::CUtlCStringConversion( char nEscapeChar, const char *pDelimiter, int nCount, ConversionArray_t *pArray ) : CUtlCharConversion( nEscapeChar, pDelimiter, nCount, pArray ) { memset( m_pConversion, 0x0, sizeof(m_pConversion) ); for ( int i = 0; i < nCount; ++i ) { m_pConversion[ (unsigned char)(pArray[i].m_pReplacementString[0]) ] = pArray[i].m_nActualChar; } } // Finds a conversion for the passed-in string, returns length char CUtlCStringConversion::FindConversion( const char *pString, int *pLength ) { char c = m_pConversion[ (unsigned char)( pString[0] ) ]; *pLength = (c != '\0') ? 1 : 0; return c; } //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CUtlCharConversion::CUtlCharConversion( char nEscapeChar, const char *pDelimiter, int nCount, ConversionArray_t *pArray ) { m_nEscapeChar = nEscapeChar; m_pDelimiter = pDelimiter; m_nCount = nCount; m_nDelimiterLength = V_strlen( pDelimiter ); m_nMaxConversionLength = 0; memset( m_pReplacements, 0, sizeof(m_pReplacements) ); for ( int i = 0; i < nCount; ++i ) { m_pList[i] = pArray[i].m_nActualChar; ConversionInfo_t &info = m_pReplacements[ (unsigned char)( m_pList[i] ) ]; Assert( info.m_pReplacementString == 0 ); info.m_pReplacementString = pArray[i].m_pReplacementString; info.m_nLength = V_strlen( info.m_pReplacementString ); if ( info.m_nLength > m_nMaxConversionLength ) { m_nMaxConversionLength = info.m_nLength; } } } //----------------------------------------------------------------------------- // Escape character + delimiter //----------------------------------------------------------------------------- char CUtlCharConversion::GetEscapeChar() const { return m_nEscapeChar; } const char *CUtlCharConversion::GetDelimiter() const { return m_pDelimiter; } int CUtlCharConversion::GetDelimiterLength() const { return m_nDelimiterLength; } //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- const char *CUtlCharConversion::GetConversionString( char c ) const { return m_pReplacements[ (unsigned char)c ].m_pReplacementString; } int CUtlCharConversion::GetConversionLength( char c ) const { return m_pReplacements[ (unsigned char)c ].m_nLength; } int CUtlCharConversion::MaxConversionLength() const { return m_nMaxConversionLength; } //----------------------------------------------------------------------------- // Finds a conversion for the passed-in string, returns length //----------------------------------------------------------------------------- char CUtlCharConversion::FindConversion( const char *pString, int *pLength ) { for ( int i = 0; i < m_nCount; ++i ) { if ( !V_strcmp( pString, m_pReplacements[ (unsigned char)( m_pList[i] ) ].m_pReplacementString ) ) { *pLength = m_pReplacements[ (unsigned char)( m_pList[i] ) ].m_nLength; return m_pList[i]; } } *pLength = 0; return '\0'; } //----------------------------------------------------------------------------- // constructors //----------------------------------------------------------------------------- CUtlBuffer::CUtlBuffer( int growSize, int initSize, int nFlags ) : m_Error(0) { MEM_ALLOC_CREDIT(); m_Memory.Init( growSize, initSize ); m_Get = 0; m_Put = 0; m_nTab = 0; m_nOffset = 0; m_Flags = nFlags; if ( (initSize != 0) && !IsReadOnly() ) { m_nMaxPut = -1; AddNullTermination( m_Put ); } else { m_nMaxPut = 0; } SetOverflowFuncs( &CUtlBuffer::GetOverflow, &CUtlBuffer::PutOverflow ); } CUtlBuffer::CUtlBuffer( const void *pBuffer, int nSize, int nFlags ) : m_Memory( (unsigned char*)pBuffer, nSize ), m_Error(0) { Assert( nSize != 0 ); m_Get = 0; m_Put = 0; m_nTab = 0; m_nOffset = 0; m_Flags = nFlags; if ( IsReadOnly() ) { m_nMaxPut = m_Put = nSize; } else { m_nMaxPut = -1; AddNullTermination( m_Put ); } SetOverflowFuncs( &CUtlBuffer::GetOverflow, &CUtlBuffer::PutOverflow ); } CUtlBuffer::CUtlBuffer( const CUtlBuffer& copyFrom ) : m_Get( copyFrom.m_Get ) , m_Put( copyFrom.m_Put ) , m_Error( copyFrom.m_Error ) , m_Flags( copyFrom.m_Flags ) , m_Reserved( copyFrom.m_Reserved ) #if defined( _GAMECONSOLE ) , pad( copyFrom.pad ) #endif , m_nTab( copyFrom.m_nTab ) , m_nMaxPut( copyFrom.m_nMaxPut ) , m_nOffset( copyFrom.m_nOffset ) , m_GetOverflowFunc( copyFrom.m_GetOverflowFunc ) , m_PutOverflowFunc( copyFrom.m_PutOverflowFunc ) , m_Byteswap( copyFrom.m_Byteswap ) { if(copyFrom.m_Memory.Count() > 0) { Assert( false ); // This is a slow path, don't do this. // copy memory m_Memory.EnsureCapacity( copyFrom.m_Memory.Count() ); memcpy( m_Memory.Base(), copyFrom.m_Memory.Base(), copyFrom.m_Memory.Count() ); } } CUtlBuffer& CUtlBuffer::operator=( const CUtlBuffer& copyFrom ) { if ( copyFrom.m_Memory.Count() > 0 ) { Assert( false ); // This is a slow path, don't do this. if(this != ©From) { m_Memory.Purge(); m_Memory.EnsureCapacity( copyFrom.m_Memory.Count() ); memcpy( m_Memory.Base(), copyFrom.m_Memory.Base(), copyFrom.m_Memory.Count() ); } } m_Get = copyFrom.m_Get; m_Put = copyFrom.m_Put; m_Error = copyFrom.m_Error; m_Flags = copyFrom.m_Flags; m_Reserved = copyFrom.m_Reserved; #if defined( _GAMECONSOLE ) pad = copyFrom.pad; #endif m_nTab = copyFrom.m_nTab; m_nMaxPut = copyFrom.m_nMaxPut; m_nOffset = copyFrom.m_nOffset; m_GetOverflowFunc = copyFrom.m_GetOverflowFunc; m_PutOverflowFunc = copyFrom.m_PutOverflowFunc; m_Byteswap = copyFrom.m_Byteswap; return *this; } #if VALVE_CPP11 CUtlBuffer::CUtlBuffer( CUtlBuffer&& moveFrom ) // = default : m_Memory( Move( moveFrom.m_Memory ) ) , m_Get( Move( moveFrom.m_Get ) ) , m_Put( Move( moveFrom.m_Put ) ) , m_Error( Move( moveFrom.m_Error ) ) , m_Flags( Move( moveFrom.m_Flags ) ) , m_Reserved( Move( moveFrom.m_Reserved ) ) #if defined( _GAMECONSOLE ) , pad( Move( moveFrom.pad ) ) #endif , m_nTab( Move( moveFrom.m_nTab ) ) , m_nMaxPut( Move( moveFrom.m_nMaxPut ) ) , m_nOffset( Move( moveFrom.m_nOffset ) ) , m_GetOverflowFunc( Move( moveFrom.m_GetOverflowFunc ) ) , m_PutOverflowFunc( Move( moveFrom.m_PutOverflowFunc ) ) , m_Byteswap( Move( moveFrom.m_Byteswap ) ) {} CUtlBuffer& CUtlBuffer::operator=( CUtlBuffer&& moveFrom ) // = default { m_Memory = Move( moveFrom.m_Memory ); m_Get = Move( moveFrom.m_Get ); m_Put = Move( moveFrom.m_Put ); m_Error = Move( moveFrom.m_Error ); m_Flags = Move( moveFrom.m_Flags ); m_Reserved = Move( moveFrom.m_Reserved ); #if defined( _GAMECONSOLE ) pad = Move( moveFrom.pad ); #endif m_nTab = Move( moveFrom.m_nTab ); m_nMaxPut = Move( moveFrom.m_nMaxPut ); m_nOffset = Move( moveFrom.m_nOffset ); m_GetOverflowFunc = Move( moveFrom.m_GetOverflowFunc ); m_PutOverflowFunc = Move( moveFrom.m_PutOverflowFunc ); m_Byteswap = Move( moveFrom.m_Byteswap ); return *this; } #endif //----------------------------------------------------------------------------- // Modifies the buffer to be binary or text; Blows away the buffer and the CONTAINS_CRLF value. //----------------------------------------------------------------------------- void CUtlBuffer::SetBufferType( bool bIsText, bool bContainsCRLF ) { #ifdef _DEBUG // If the buffer is empty, there is no opportunity for this stuff to fail if ( TellMaxPut() != 0 ) { if ( IsText() ) { if ( bIsText ) { Assert( ContainsCRLF() == bContainsCRLF ); } else { Assert( ContainsCRLF() ); } } else { if ( bIsText ) { Assert( bContainsCRLF ); } } } #endif if ( bIsText ) { m_Flags |= TEXT_BUFFER; } else { m_Flags &= ~TEXT_BUFFER; } if ( bContainsCRLF ) { m_Flags |= CONTAINS_CRLF; } else { m_Flags &= ~CONTAINS_CRLF; } } //----------------------------------------------------------------------------- // Attaches the buffer to external memory.... //----------------------------------------------------------------------------- void CUtlBuffer::SetExternalBuffer( void* pMemory, int nSize, int nInitialPut, int nFlags ) { m_Memory.SetExternalBuffer( (unsigned char*)pMemory, nSize ); // Reset all indices; we just changed memory m_Get = 0; m_Put = nInitialPut; m_nTab = 0; m_Error = 0; m_nOffset = 0; m_Flags = nFlags; m_nMaxPut = -1; AddNullTermination( m_Put ); } //----------------------------------------------------------------------------- // Assumes an external buffer but manages its deletion //----------------------------------------------------------------------------- void CUtlBuffer::AssumeMemory( void *pMemory, int nSize, int nInitialPut, int nFlags ) { m_Memory.AssumeMemory( (unsigned char*) pMemory, nSize ); // Reset all indices; we just changed memory m_Get = 0; m_Put = nInitialPut; m_nTab = 0; m_Error = 0; m_nOffset = 0; m_Flags = nFlags; m_nMaxPut = -1; AddNullTermination( m_Put ); } //----------------------------------------------------------------------------- // Allows the caller to control memory //----------------------------------------------------------------------------- void* CUtlBuffer::DetachMemory() { // Reset all indices; we just changed memory m_Get = 0; m_Put = 0; m_nTab = 0; m_Error = 0; m_nOffset = 0; return m_Memory.DetachMemory( ); } //----------------------------------------------------------------------------- // Makes sure we've got at least this much memory //----------------------------------------------------------------------------- void CUtlBuffer::EnsureCapacity( int num ) { MEM_ALLOC_CREDIT(); // Add one extra for the null termination num += 1; if ( m_Memory.IsExternallyAllocated() ) { if ( IsGrowable() && ( m_Memory.NumAllocated() < num ) ) { m_Memory.ConvertToGrowableMemory( 0 ); } else { num -= 1; } } m_Memory.EnsureCapacity( num ); } //----------------------------------------------------------------------------- // Base get method from which all others derive //----------------------------------------------------------------------------- bool CUtlBuffer::Get( void* pMem, int size ) { if ( size > 0 && CheckGet( size ) ) { memcpy( pMem, &m_Memory[m_Get - m_nOffset], size ); m_Get += size; return true; } return false; } //----------------------------------------------------------------------------- // This will get at least 1 byte and up to nSize bytes. // It will return the number of bytes actually read. //----------------------------------------------------------------------------- int CUtlBuffer::GetUpTo( void *pMem, int nSize ) { if ( CheckArbitraryPeekGet( 0, nSize ) ) { memcpy( pMem, &m_Memory[m_Get - m_nOffset], nSize ); m_Get += nSize; return nSize; } return 0; } //----------------------------------------------------------------------------- // Eats whitespace //----------------------------------------------------------------------------- void CUtlBuffer::EatWhiteSpace() { if ( IsText() && IsValid() ) { while ( CheckGet( sizeof(char) ) ) { if ( !V_isspace( *(const unsigned char*)PeekGet() ) ) break; m_Get += sizeof(char); } } } //----------------------------------------------------------------------------- // Eats C++ style comments //----------------------------------------------------------------------------- bool CUtlBuffer::EatCPPComment() { if ( IsText() && IsValid() ) { // If we don't have a a c++ style comment next, we're done const char *pPeek = (const char *)PeekGet( 2 * sizeof(char), 0 ); if ( !pPeek || ( pPeek[0] != '/' ) || ( pPeek[1] != '/' ) ) return false; // Deal with c++ style comments m_Get += 2; // read complete line for ( char c = GetChar(); IsValid(); c = GetChar() ) { if ( c == '\n' ) break; } return true; } return false; } //----------------------------------------------------------------------------- // Peeks how much whitespace to eat //----------------------------------------------------------------------------- int CUtlBuffer::PeekWhiteSpace( int nOffset ) { if ( !IsText() || !IsValid() ) return 0; while ( CheckPeekGet( nOffset, sizeof(char) ) ) { if ( !V_isspace( *(unsigned char*)PeekGet( nOffset ) ) ) break; nOffset += sizeof(char); } return nOffset; } //----------------------------------------------------------------------------- // Peek size of sting to come, check memory bound //----------------------------------------------------------------------------- int CUtlBuffer::PeekStringLength() { if ( !IsValid() ) return 0; // Eat preceeding whitespace int nOffset = 0; if ( IsText() ) { nOffset = PeekWhiteSpace( nOffset ); } int nStartingOffset = nOffset; do { int nPeekAmount = 128; // NOTE: Add 1 for the terminating zero! if ( !CheckArbitraryPeekGet( nOffset, nPeekAmount ) ) { if ( nOffset == nStartingOffset ) return 0; return nOffset - nStartingOffset + 1; } const char *pTest = (const char *)PeekGet( nOffset ); if ( !IsText() ) { for ( int i = 0; i < nPeekAmount; ++i ) { // The +1 here is so we eat the terminating 0 if ( pTest[i] == 0 ) return (i + nOffset - nStartingOffset + 1); } } else { for ( int i = 0; i < nPeekAmount; ++i ) { // The +1 here is so we eat the terminating 0 if ( V_isspace((unsigned char)pTest[i]) || (pTest[i] == 0) ) return (i + nOffset - nStartingOffset + 1); } } nOffset += nPeekAmount; } while ( true ); } //----------------------------------------------------------------------------- // Peek size of line to come, check memory bound //----------------------------------------------------------------------------- int CUtlBuffer::PeekLineLength() { if ( !IsValid() ) return 0; int nOffset = 0; int nStartingOffset = nOffset; do { int nPeekAmount = 128; // NOTE: Add 1 for the terminating zero! if ( !CheckArbitraryPeekGet( nOffset, nPeekAmount ) ) { if ( nOffset == nStartingOffset ) return 0; return nOffset - nStartingOffset + 1; } const char *pTest = (const char *)PeekGet( nOffset ); for ( int i = 0; i < nPeekAmount; ++i ) { // The +2 here is so we eat the terminating '\n' and 0 if ( pTest[i] == '\n' || pTest[i] == '\r' ) return (i + nOffset - nStartingOffset + 2); // The +1 here is so we eat the terminating 0 if ( pTest[i] == 0 ) return (i + nOffset - nStartingOffset + 1); } nOffset += nPeekAmount; } while ( true ); } //----------------------------------------------------------------------------- // Does the next bytes of the buffer match a pattern? //----------------------------------------------------------------------------- bool CUtlBuffer::PeekStringMatch( int nOffset, const char *pString, int nLen ) { if ( !CheckPeekGet( nOffset, nLen ) ) return false; return !V_strncmp( (const char*)PeekGet(nOffset), pString, nLen ); } //----------------------------------------------------------------------------- // This version of PeekStringLength converts \" to \\ and " to \, etc. // It also reads a " at the beginning and end of the string //----------------------------------------------------------------------------- int CUtlBuffer::PeekDelimitedStringLength( CUtlCharConversion *pConv, bool bActualSize ) { if ( !IsText() || !pConv ) return PeekStringLength(); // Eat preceeding whitespace int nOffset = 0; if ( IsText() ) { nOffset = PeekWhiteSpace( nOffset ); } if ( !PeekStringMatch( nOffset, pConv->GetDelimiter(), pConv->GetDelimiterLength() ) ) return 0; // Try to read ending ", but don't accept \" int nActualStart = nOffset; nOffset += pConv->GetDelimiterLength(); int nLen = 1; // Starts at 1 for the '\0' termination do { if ( PeekStringMatch( nOffset, pConv->GetDelimiter(), pConv->GetDelimiterLength() ) ) break; if ( !CheckPeekGet( nOffset, 1 ) ) break; char c = *(const char*)PeekGet( nOffset ); ++nLen; ++nOffset; if ( c == pConv->GetEscapeChar() ) { int nLength = pConv->MaxConversionLength(); if ( !CheckArbitraryPeekGet( nOffset, nLength ) ) break; pConv->FindConversion( (const char*)PeekGet(nOffset), &nLength ); nOffset += nLength; } } while (true); return bActualSize ? nLen : nOffset - nActualStart + pConv->GetDelimiterLength() + 1; } //----------------------------------------------------------------------------- // Reads a null-terminated string //----------------------------------------------------------------------------- void CUtlBuffer::GetString( char* pString, int nMaxChars ) { if (!IsValid()) { *pString = 0; return; } Assert( nMaxChars > 0 ); if ( nMaxChars <= 0 ) { return; } // Remember, this *includes* the null character // It will be 0, however, if the buffer is empty. int nLen = PeekStringLength(); if ( IsText() ) { EatWhiteSpace(); } if ( nLen <= 0 ) { *pString = 0; m_Error |= GET_OVERFLOW; return; } const int nCharsToRead = Min( nLen, nMaxChars ) - 1; Get( pString, nCharsToRead ); pString[ nCharsToRead ] = 0; if ( nLen > ( nCharsToRead + 1 ) ) { SeekGet( SEEK_CURRENT, nLen - ( nCharsToRead + 1 ) ); } // Read the terminating NULL in binary formats if ( !IsText() ) { VerifyEquals( GetChar(), 0 ); } } //----------------------------------------------------------------------------- // Reads up to and including the first \n //----------------------------------------------------------------------------- void CUtlBuffer::GetLine( char* pLine, int nMaxChars ) { //Assert( IsText() && !ContainsCRLF() ); if ( !IsValid() ) { *pLine = 0; return; } if ( nMaxChars == 0 ) { nMaxChars = INT_MAX; } // Remember, this *includes* the null character // It will be 0, however, if the buffer is empty. int nLen = PeekLineLength(); if ( nLen == 0 ) { *pLine = 0; m_Error |= GET_OVERFLOW; return; } // Strip off the terminating NULL if ( nLen <= nMaxChars ) { Get( pLine, nLen - 1 ); pLine[ nLen - 1 ] = 0; } else { Get( pLine, nMaxChars - 1 ); pLine[ nMaxChars - 1 ] = 0; SeekGet( SEEK_CURRENT, nLen - 1 - nMaxChars ); } } //----------------------------------------------------------------------------- // This version of GetString converts \ to \\ and " to \", etc. // It also places " at the beginning and end of the string //----------------------------------------------------------------------------- char CUtlBuffer::GetDelimitedCharInternal( CUtlCharConversion *pConv ) { char c = GetChar(); if ( c == pConv->GetEscapeChar() ) { int nLength = pConv->MaxConversionLength(); if ( !CheckArbitraryPeekGet( 0, nLength ) ) return '\0'; c = pConv->FindConversion( (const char *)PeekGet(), &nLength ); SeekGet( SEEK_CURRENT, nLength ); } return c; } char CUtlBuffer::GetDelimitedChar( CUtlCharConversion *pConv ) { if ( !IsText() || !pConv ) return GetChar( ); return GetDelimitedCharInternal( pConv ); } void CUtlBuffer::GetDelimitedString( CUtlCharConversion *pConv, char *pString, int nMaxChars ) { if ( !IsText() || !pConv ) { GetString( pString, nMaxChars ); return; } if (!IsValid()) { *pString = 0; return; } if ( nMaxChars == 0 ) { nMaxChars = INT_MAX; } EatWhiteSpace(); if ( !PeekStringMatch( 0, pConv->GetDelimiter(), pConv->GetDelimiterLength() ) ) return; // Pull off the starting delimiter SeekGet( SEEK_CURRENT, pConv->GetDelimiterLength() ); int nRead = 0; while ( IsValid() ) { if ( PeekStringMatch( 0, pConv->GetDelimiter(), pConv->GetDelimiterLength() ) ) { SeekGet( SEEK_CURRENT, pConv->GetDelimiterLength() ); break; } char c = GetDelimitedCharInternal( pConv ); if ( nRead < nMaxChars ) { pString[nRead] = c; ++nRead; } } if ( nRead >= nMaxChars ) { nRead = nMaxChars - 1; } pString[nRead] = '\0'; } //----------------------------------------------------------------------------- // Checks if a get is ok //----------------------------------------------------------------------------- bool CUtlBuffer::CheckGet( int nSize ) { if ( m_Error & GET_OVERFLOW ) return false; if ( TellMaxPut() < m_Get + nSize ) { m_Error |= GET_OVERFLOW; return false; } if ( ( m_Get < m_nOffset ) || ( m_Memory.NumAllocated() < m_Get - m_nOffset + nSize ) ) { if ( !OnGetOverflow( nSize ) ) { m_Error |= GET_OVERFLOW; return false; } } return true; } //----------------------------------------------------------------------------- // Checks if a peek get is ok //----------------------------------------------------------------------------- bool CUtlBuffer::CheckPeekGet( int nOffset, int nSize ) { if ( m_Error & GET_OVERFLOW ) return false; // Checking for peek can't set the overflow flag bool bOk = CheckGet( nOffset + nSize ); m_Error &= ~GET_OVERFLOW; return bOk; } //----------------------------------------------------------------------------- // Call this to peek arbitrarily long into memory. It doesn't fail unless // it can't read *anything* new //----------------------------------------------------------------------------- bool CUtlBuffer::CheckArbitraryPeekGet( int nOffset, int &nIncrement ) { if ( TellGet() + nOffset >= TellMaxPut() ) { nIncrement = 0; return false; } if ( TellGet() + nOffset + nIncrement > TellMaxPut() ) { nIncrement = TellMaxPut() - TellGet() - nOffset; } // NOTE: CheckPeekGet could modify TellMaxPut for streaming files // We have to call TellMaxPut again here CheckPeekGet( nOffset, nIncrement ); int nMaxGet = TellMaxPut() - TellGet(); if ( nMaxGet < nIncrement ) { nIncrement = nMaxGet; } return (nIncrement != 0); } //----------------------------------------------------------------------------- // Peek part of the butt //----------------------------------------------------------------------------- const void* CUtlBuffer::PeekGet( int nMaxSize, int nOffset ) { if ( !CheckPeekGet( nOffset, nMaxSize ) ) return NULL; return &m_Memory[ m_Get + nOffset - m_nOffset ]; } //----------------------------------------------------------------------------- // Change where I'm reading //----------------------------------------------------------------------------- void CUtlBuffer::SeekGet( SeekType_t type, int offset ) { switch( type ) { case SEEK_HEAD: m_Get = offset; break; case SEEK_CURRENT: m_Get += offset; break; case SEEK_TAIL: m_Get = m_nMaxPut - offset; break; } if ( m_Get > m_nMaxPut ) { m_Error |= GET_OVERFLOW; } else { m_Error &= ~GET_OVERFLOW; if ( m_Get < m_nOffset || m_Get >= m_nOffset + Size() ) { OnGetOverflow( -1 ); } } } //----------------------------------------------------------------------------- // Parse... //----------------------------------------------------------------------------- #pragma warning ( disable : 4706 ) int CUtlBuffer::VaScanf( const char* pFmt, va_list list ) { Assert( pFmt ); if ( m_Error || !IsText() ) return 0; int numScanned = 0; char c; while ( c = *pFmt++ ) { // Stop if we hit the end of the buffer if ( m_Get >= TellMaxPut() ) { m_Error |= GET_OVERFLOW; break; } switch (c) { case ' ': // eat all whitespace EatWhiteSpace(); break; case '%': { // Conversion character... try to convert baby! char type = *pFmt++; if (type == 0) return numScanned; switch(type) { case 'c': { char* ch = va_arg( list, char * ); if ( CheckPeekGet( 0, sizeof(char) ) ) { *ch = *(const char*)PeekGet(); ++m_Get; } else { *ch = 0; return numScanned; } } break; case 'h': { if ( *pFmt == 'd' || *pFmt == 'i' ) { if ( !GetTypeText( *va_arg( list, int16 * ) ) ) return numScanned; // only support short ints, don't bother with hex } else if ( *pFmt == 'u' ) { if ( !GetTypeText( *va_arg( list, uint16 * ) ) ) return numScanned; } else return numScanned; ++pFmt; } break; case 'I': { if ( *pFmt++ != '6' || *pFmt++ != '4' ) return numScanned; // only support "I64d" and "I64u" if ( *pFmt == 'd' ) { if ( !GetTypeText( *va_arg( list, int64 * ) ) ) return numScanned; } else if ( *pFmt == 'u' ) { if ( !GetTypeText( *va_arg( list, uint64 * ) ) ) return numScanned; } else { return numScanned; } ++pFmt; } break; case 'i': case 'd': { int32 *pArg = va_arg( list, int32 * ); if ( !GetTypeText( *pArg ) ) return numScanned; } break; case 'x': { uint32 *pArg = va_arg( list, uint32 * ); if ( !GetTypeText( *pArg, 16 ) ) return numScanned; } break; case 'u': { uint32 *pArg = va_arg( list, uint32 * ); if ( !GetTypeText( *pArg ) ) return numScanned; } break; case 'l': { // we currently support %lf and %lld if ( *pFmt == 'f' ) { if ( !GetTypeText( *va_arg( list, double * ) ) ) return numScanned; } else if ( *pFmt == 'l' && *++pFmt == 'd' ) { if ( !GetTypeText( *va_arg( list, int64 * ) ) ) return numScanned; } else return numScanned; } break; case 'f': { float *pArg = va_arg( list, float * ); if ( !GetTypeText( *pArg ) ) return numScanned; } break; case 's': { char* s = va_arg( list, char * ); GetString( s, 64 ); // [SECURITY EXPLOIT: Scanf %s should be deprecated as malicious data can overrun stack buffers! Here we'd assume that at least 64 bytes are available on the stack, and even if not this shouldn't give attracker much room for code execution] } break; default: { // unimplemented scanf type Assert(0); return numScanned; } break; } ++numScanned; } break; default: { // Here we have to match the format string character // against what's in the buffer or we're done. if ( !CheckPeekGet( 0, sizeof(char) ) ) return numScanned; if ( c != *(const char*)PeekGet() ) return numScanned; ++m_Get; } } } return numScanned; } #pragma warning ( default : 4706 ) int CUtlBuffer::Scanf( const char* pFmt, ... ) { va_list args; va_start( args, pFmt ); int count = VaScanf( pFmt, args ); va_end( args ); return count; } //----------------------------------------------------------------------------- // Advance the get index until after the particular string is found // Do not eat whitespace before starting. Return false if it failed //----------------------------------------------------------------------------- bool CUtlBuffer::GetToken( const char *pToken ) { Assert( pToken ); // Look for the token int nLen = V_strlen( pToken ); // First time through on streaming, check what we already have loaded // if we have enough loaded to do the check int nMaxSize = Size() - ( TellGet() - m_nOffset ); if ( nMaxSize <= nLen ) { nMaxSize = Size(); } int nSizeRemaining = TellMaxPut() - TellGet(); int nGet = TellGet(); while ( nSizeRemaining >= nLen ) { bool bOverFlow = ( nSizeRemaining > nMaxSize ); int nSizeToCheck = bOverFlow ? nMaxSize : nSizeRemaining; if ( !CheckPeekGet( 0, nSizeToCheck ) ) break; const char *pBufStart = (const char*)PeekGet(); const char *pFoundEnd = V_strnistr( pBufStart, pToken, nSizeToCheck ); // Time to be careful: if we are in a state of overflow // (namely, there's more of the buffer beyond the current window) // we could be looking for 'foo' for example, and find 'foobar' // if 'foo' happens to be the last 3 characters of the current window size_t nOffset = (size_t)pFoundEnd - (size_t)pBufStart; bool bPotentialMismatch = ( bOverFlow && ( (int)nOffset == Size() - nLen ) ); if ( !pFoundEnd || bPotentialMismatch ) { nSizeRemaining -= nSizeToCheck; if ( !pFoundEnd && ( nSizeRemaining < nLen ) ) break; // Second time through, stream as much in as possible // But keep the last portion of the current buffer // since we couldn't check it against stuff outside the window nSizeRemaining += nLen; nMaxSize = Size(); SeekGet( CUtlBuffer::SEEK_CURRENT, nSizeToCheck - nLen ); continue; } // Seek past the end of the found string SeekGet( CUtlBuffer::SEEK_CURRENT, (int)( nOffset + nLen ) ); return true; } // Didn't find a match, leave the get index where it was to start with SeekGet( CUtlBuffer::SEEK_HEAD, nGet ); return false; } //----------------------------------------------------------------------------- // (For text buffers only) // Parse a token from the buffer: // Grab all text that lies between a starting delimiter + ending delimiter // (skipping whitespace that leads + trails both delimiters). // Note the delimiter checks are case-insensitive. // If successful, the get index is advanced and the function returns true, // otherwise the index is not advanced and the function returns false. //----------------------------------------------------------------------------- bool CUtlBuffer::ParseToken( const char *pStartingDelim, const char *pEndingDelim, char* pString, int nMaxLen ) { int nCharsToCopy = 0; int nCurrentGet = 0; size_t nEndingDelimLen; // Starting delimiter is optional char emptyBuf = '\0'; if ( !pStartingDelim ) { pStartingDelim = &emptyBuf; } // Ending delimiter is not Assert( pEndingDelim && pEndingDelim[0] ); nEndingDelimLen = V_strlen( pEndingDelim ); int nStartGet = TellGet(); char nCurrChar; int nTokenStart = -1; EatWhiteSpace( ); while ( *pStartingDelim ) { nCurrChar = *pStartingDelim++; if ( !V_isspace((unsigned char)nCurrChar) ) { if ( tolower( GetChar() ) != tolower( nCurrChar ) ) goto parseFailed; } else { EatWhiteSpace(); } } EatWhiteSpace(); nTokenStart = TellGet(); if ( !GetToken( pEndingDelim ) ) goto parseFailed; nCurrentGet = TellGet(); nCharsToCopy = (int)( (nCurrentGet - nEndingDelimLen) - nTokenStart ); if ( nCharsToCopy >= nMaxLen ) { nCharsToCopy = nMaxLen - 1; } if ( nCharsToCopy > 0 ) { SeekGet( CUtlBuffer::SEEK_HEAD, nTokenStart ); Get( pString, nCharsToCopy ); if ( !IsValid() ) goto parseFailed; // Eat trailing whitespace for ( ; nCharsToCopy > 0; --nCharsToCopy ) { if ( !V_isspace( (unsigned char)pString[ nCharsToCopy-1 ] ) ) break; } } pString[ nCharsToCopy ] = '\0'; // Advance the Get index SeekGet( CUtlBuffer::SEEK_HEAD, nCurrentGet ); return true; parseFailed: // Revert the get index SeekGet( SEEK_HEAD, nStartGet ); pString[0] = '\0'; return false; } //----------------------------------------------------------------------------- // Parses the next token, given a set of character breaks to stop at //----------------------------------------------------------------------------- int CUtlBuffer::ParseToken( characterset_t *pBreaks, char *pTokenBuf, int nMaxLen, bool bParseComments ) { Assert( nMaxLen > 0 ); pTokenBuf[0] = 0; // skip whitespace + comments while ( true ) { if ( !IsValid() ) return -1; EatWhiteSpace(); if ( bParseComments ) { if ( !EatCPPComment() ) break; } else { break; } } char c = GetChar(); // End of buffer if ( c == 0 ) return -1; // handle quoted strings specially if ( c == '\"' ) { int nLen = 0; while( IsValid() ) { c = GetChar(); if ( c == '\"' || !c ) { pTokenBuf[nLen] = 0; return nLen; } pTokenBuf[nLen] = c; if ( ++nLen == nMaxLen ) { pTokenBuf[nLen-1] = 0; return nMaxLen; } } // In this case, we hit the end of the buffer before hitting the end qoute pTokenBuf[nLen] = 0; return nLen; } // parse single characters if ( IN_CHARACTERSET( *pBreaks, c ) ) { pTokenBuf[0] = c; pTokenBuf[1] = 0; return 1; } // parse a regular word int nLen = 0; while ( true ) { pTokenBuf[nLen] = c; if ( ++nLen == nMaxLen ) { pTokenBuf[nLen-1] = 0; return nMaxLen; } c = GetChar(); if ( !IsValid() ) break; if ( IN_CHARACTERSET( *pBreaks, c ) || c == '\"' || c <= ' ' ) { SeekGet( SEEK_CURRENT, -1 ); break; } } pTokenBuf[nLen] = 0; return nLen; } //----------------------------------------------------------------------------- // Serialization //----------------------------------------------------------------------------- void CUtlBuffer::Put( const void *pMem, int size ) { if ( size && CheckPut( size ) ) { memcpy( &m_Memory[m_Put - m_nOffset], pMem, size ); m_Put += size; AddNullTermination( m_Put ); } } //----------------------------------------------------------------------------- // Writes a null-terminated string //----------------------------------------------------------------------------- void CUtlBuffer::PutString( const char* pString ) { if (!IsText()) { if ( pString ) { // Not text? append a null at the end. int nLen = (int)V_strlen( pString ) + 1; Put( pString, nLen * sizeof(char) ); return; } else { PutTypeBin( 0 ); } } else if (pString) { int nTabCount = ( m_Flags & AUTO_TABS_DISABLED ) ? 0 : m_nTab; if ( nTabCount > 0 ) { if ( WasLastCharacterCR() ) { PutTabs(); } const char* pEndl = strchr( pString, '\n' ); while ( pEndl ) { size_t nSize = (size_t)pEndl - (size_t)pString + sizeof(char); Put( pString, (int)nSize ); pString = pEndl + 1; if ( *pString ) { PutTabs(); pEndl = strchr( pString, '\n' ); } else { pEndl = NULL; } } } int nLen = (int)V_strlen( pString ); if ( nLen ) { Put( pString, nLen * sizeof(char) ); } } } //----------------------------------------------------------------------------- // This version of PutString converts \ to \\ and " to \", etc. // It also places " at the beginning and end of the string //----------------------------------------------------------------------------- inline void CUtlBuffer::PutDelimitedCharInternal( CUtlCharConversion *pConv, char c ) { int l = pConv->GetConversionLength( c ); if ( l == 0 ) { PutChar( c ); } else { PutChar( pConv->GetEscapeChar() ); Put( pConv->GetConversionString( c ), l ); } } void CUtlBuffer::PutDelimitedChar( CUtlCharConversion *pConv, char c ) { if ( !IsText() || !pConv ) { PutChar( c ); return; } PutDelimitedCharInternal( pConv, c ); } void CUtlBuffer::PutDelimitedString( CUtlCharConversion *pConv, const char *pString ) { if ( !IsText() || !pConv ) { PutString( pString ); return; } if ( WasLastCharacterCR() ) { PutTabs(); } Put( pConv->GetDelimiter(), pConv->GetDelimiterLength() ); int nLen = pString ? V_strlen( pString ) : 0; for ( int i = 0; i < nLen; ++i ) { PutDelimitedCharInternal( pConv, pString[i] ); } if ( WasLastCharacterCR() ) { PutTabs(); } Put( pConv->GetDelimiter(), pConv->GetDelimiterLength() ); } void CUtlBuffer::VaPrintf( const char* pFmt, va_list list ) { char temp[8192]; int nLen = V_vsnprintf( temp, sizeof( temp ), pFmt, list ); ErrorIfNot( nLen < sizeof( temp ), ( "CUtlBuffer::VaPrintf: String overflowed buffer [%d]\n", sizeof( temp ) ) ); PutString( temp ); } void CUtlBuffer::Printf( const char* pFmt, ... ) { va_list args; va_start( args, pFmt ); VaPrintf( pFmt, args ); va_end( args ); } //----------------------------------------------------------------------------- // Calls the overflow functions //----------------------------------------------------------------------------- void CUtlBuffer::SetOverflowFuncs( UtlBufferOverflowFunc_t getFunc, UtlBufferOverflowFunc_t putFunc ) { m_GetOverflowFunc = getFunc; m_PutOverflowFunc = putFunc; } //----------------------------------------------------------------------------- // Calls the overflow functions //----------------------------------------------------------------------------- bool CUtlBuffer::OnPutOverflow( int nSize ) { return (this->*m_PutOverflowFunc)( nSize ); } bool CUtlBuffer::OnGetOverflow( int nSize ) { return (this->*m_GetOverflowFunc)( nSize ); } //----------------------------------------------------------------------------- // Checks if a put is ok //----------------------------------------------------------------------------- bool CUtlBuffer::PutOverflow( int nSize ) { MEM_ALLOC_CREDIT(); if ( m_Memory.IsExternallyAllocated() ) { if ( !IsGrowable() ) return false; m_Memory.ConvertToGrowableMemory( 0 ); } while( Size() < m_Put - m_nOffset + nSize ) { m_Memory.Grow(); } return true; } bool CUtlBuffer::GetOverflow( int nSize ) { return false; } //----------------------------------------------------------------------------- // Checks if a put is ok //----------------------------------------------------------------------------- bool CUtlBuffer::CheckPut( int nSize ) { if ( ( m_Error & PUT_OVERFLOW ) || IsReadOnly() ) return false; if ( ( m_Put < m_nOffset ) || ( m_Memory.NumAllocated() < m_Put - m_nOffset + nSize ) ) { if ( !OnPutOverflow( nSize ) ) { m_Error |= PUT_OVERFLOW; return false; } } return true; } void CUtlBuffer::SeekPut( SeekType_t type, int offset ) { int nNextPut = m_Put; switch( type ) { case SEEK_HEAD: nNextPut = offset; break; case SEEK_CURRENT: nNextPut += offset; break; case SEEK_TAIL: nNextPut = m_nMaxPut - offset; break; } // Force a write of the data // FIXME: We could make this more optimal potentially by writing out // the entire buffer if you seek outside the current range // NOTE: This call will write and will also seek the file to nNextPut. OnPutOverflow( -nNextPut-1 ); m_Put = nNextPut; AddNullTermination( m_Put ); } void CUtlBuffer::ActivateByteSwapping( bool bActivate ) { m_Byteswap.ActivateByteSwapping( bActivate ); } void CUtlBuffer::SetBigEndian( bool bigEndian ) { m_Byteswap.SetTargetBigEndian( bigEndian ); } bool CUtlBuffer::IsBigEndian( void ) { return m_Byteswap.IsTargetBigEndian(); } //----------------------------------------------------------------------------- // null terminate the buffer // NOTE: Pass in nPut here even though it is just a copy of m_Put. This is almost always called immediately // after modifying m_Put and this lets it stay in a register and avoid LHS on PPC. //----------------------------------------------------------------------------- void CUtlBuffer::AddNullTermination( int nPut ) { if ( nPut > m_nMaxPut ) { if ( !IsReadOnly() && ((m_Error & PUT_OVERFLOW) == 0) ) { // Add null termination value if ( CheckPut( 1 ) ) { m_Memory[nPut - m_nOffset] = 0; } else { // Restore the overflow state, it was valid before... m_Error &= ~PUT_OVERFLOW; } } m_nMaxPut = nPut; } } //----------------------------------------------------------------------------- // Converts a buffer from a CRLF buffer to a CR buffer (and back) // Returns false if no conversion was necessary (and outBuf is left untouched) // If the conversion occurs, outBuf will be cleared. //----------------------------------------------------------------------------- bool CUtlBuffer::ConvertCRLF( CUtlBuffer &outBuf ) { if ( !IsText() || !outBuf.IsText() ) return false; if ( ContainsCRLF() == outBuf.ContainsCRLF() ) return false; int nInCount = TellMaxPut(); outBuf.Purge(); outBuf.EnsureCapacity( nInCount ); bool bFromCRLF = ContainsCRLF(); // Start reading from the beginning int nGet = TellGet(); int nPut = TellPut(); int nGetDelta = 0; int nPutDelta = 0; const char *pBase = (const char*)Base(); intp nCurrGet = 0; while ( nCurrGet < nInCount ) { const char *pCurr = &pBase[nCurrGet]; if ( bFromCRLF ) { const char *pNext = V_strnistr( pCurr, "\r\n", nInCount - nCurrGet ); if ( !pNext ) { outBuf.Put( pCurr, nInCount - nCurrGet ); break; } intp nBytes = (intp)pNext - (intp)pCurr; outBuf.Put( pCurr, (int)nBytes ); outBuf.PutChar( '\n' ); nCurrGet += nBytes + 2; if ( nGet >= nCurrGet - 1 ) { --nGetDelta; } if ( nPut >= nCurrGet - 1 ) { --nPutDelta; } } else { const char *pNext = V_strnchr( pCurr, '\n', nInCount - nCurrGet ); if ( !pNext ) { outBuf.Put( pCurr, nInCount - nCurrGet ); break; } intp nBytes = (intp)pNext - (intp)pCurr; outBuf.Put( pCurr, (int)nBytes ); outBuf.PutChar( '\r' ); outBuf.PutChar( '\n' ); nCurrGet += nBytes + 1; if ( nGet >= nCurrGet ) { ++nGetDelta; } if ( nPut >= nCurrGet ) { ++nPutDelta; } } } Assert( nPut + nPutDelta <= outBuf.TellMaxPut() ); outBuf.SeekGet( SEEK_HEAD, nGet + nGetDelta ); outBuf.SeekPut( SEEK_HEAD, nPut + nPutDelta ); return true; } //----------------------------------------------------------------------------- // Fast swap //----------------------------------------------------------------------------- void CUtlBuffer::Swap( CUtlBuffer &buf ) { V_swap( m_Get, buf.m_Get ); V_swap( m_Put, buf.m_Put ); V_swap( m_nMaxPut, buf.m_nMaxPut ); V_swap( m_Error, buf.m_Error ); m_Memory.Swap( buf.m_Memory ); } //----------------------------------------------------------------------------- // Fast swap w/ a CUtlMemory. //----------------------------------------------------------------------------- void CUtlBuffer::Swap( CUtlMemory &mem ) { m_Get = 0; m_Put = mem.Count(); m_nMaxPut = mem.Count(); m_Error = 0; m_Memory.Swap( mem ); } //--------------------------------------------------------------------------- // Implementation of CUtlInplaceBuffer //--------------------------------------------------------------------------- CUtlInplaceBuffer::CUtlInplaceBuffer( int growSize /* = 0 */, int initSize /* = 0 */, int nFlags /* = 0 */ ) : CUtlBuffer( growSize, initSize, nFlags ) { } bool CUtlInplaceBuffer::InplaceGetLinePtr( char **ppszInBufferPtr, int *pnLineLength ) { Assert( IsText() && !ContainsCRLF() ); int nLineLen = PeekLineLength(); if ( nLineLen <= 1 ) { SeekGet( SEEK_TAIL, 0 ); return false; } -- nLineLen; // because it accounts for putting a terminating null-character char *pszLine = ( char * ) const_cast< void * >( PeekGet() ); SeekGet( SEEK_CURRENT, nLineLen ); // Set the out args if ( ppszInBufferPtr ) *ppszInBufferPtr = pszLine; if ( pnLineLength ) *pnLineLength = nLineLen; return true; } char * CUtlInplaceBuffer::InplaceGetLinePtr( void ) { char *pszLine = NULL; int nLineLen = 0; if ( InplaceGetLinePtr( &pszLine, &nLineLen ) ) { Assert( nLineLen >= 1 ); switch ( pszLine[ nLineLen - 1 ] ) { case '\n': case '\r': pszLine[ nLineLen - 1 ] = 0; if ( -- nLineLen ) { switch ( pszLine[ nLineLen - 1 ] ) { case '\n': case '\r': pszLine[ nLineLen - 1 ] = 0; break; } } break; default: Assert( pszLine[ nLineLen ] == 0 ); break; } } return pszLine; }