diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /gcsdk/webapi_response.cpp | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'gcsdk/webapi_response.cpp')
| -rw-r--r-- | gcsdk/webapi_response.cpp | 2562 |
1 files changed, 2562 insertions, 0 deletions
diff --git a/gcsdk/webapi_response.cpp b/gcsdk/webapi_response.cpp new file mode 100644 index 0000000..82f1105 --- /dev/null +++ b/gcsdk/webapi_response.cpp @@ -0,0 +1,2562 @@ +//========= Copyright � 1996-2010, Valve LLC, All rights reserved. ============ +// +// Purpose: Implementation for CWebAPIResponse objects +// +//============================================================================= + +#include "stdafx.h" +#include "thirdparty/JSON_parser/JSON_parser.h" + +using namespace GCSDK; + +#include "tier0/memdbgoff.h" + +// !FIXME! DOTAMERGE +//IMPLEMENT_CLASS_MEMPOOL_MT( CWebAPIValues, 1000, UTLMEMORYPOOL_GROW_SLOW ); + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: Helper for emitting properly escaped json string values +//----------------------------------------------------------------------------- +void EmitJSONString( CUtlBuffer &outputBuffer, const char *pchValue ) +{ + outputBuffer.PutChar( '"' ); + + if ( pchValue ) + { + int i = 0; + while( pchValue[i] ) + { + switch ( pchValue[i] ) + { + case '"': + outputBuffer.Put( "\\\"", 2 ); + break; + case '\\': + outputBuffer.Put( "\\\\", 2 ); + break; + case '\n': + outputBuffer.Put( "\\n", 2 ); + break; + case '\r': + outputBuffer.Put( "\\r", 2 ); + break; + case '\t': + outputBuffer.Put( "\\t", 2 ); + break; + default: + if ( (uint8) pchValue[i] < 32 ) + { + outputBuffer.Put( "\\u00", 4 ); + outputBuffer.PutChar( ( pchValue[i] & 16 ) ? '1' : '0' ); + outputBuffer.PutChar( "0123456789abcdef"[ pchValue[i] & 0xF ] ); + } + else + { + outputBuffer.PutChar( pchValue[i] ); + } + } + ++i; + } + } + + outputBuffer.PutChar( '"' ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Helper for emitting properly escaped XML string values, we always use UTF8, +// so we only really need to encode & ' " < > +//----------------------------------------------------------------------------- +void EmitXMLString( CUtlBuffer &outputBuffer, const char *pchValue ) +{ + if ( pchValue ) + { + int i = 0; + while( pchValue[i] ) + { + switch ( pchValue[i] ) + { + case '&': + outputBuffer.Put( "&", 5 ); + break; + case '\'': + outputBuffer.Put( "'", 6 ); + break; + case '"': + outputBuffer.Put( """, 6 ); + break; + case '<': + outputBuffer.Put( "<", 4 ); + break; + case '>': + outputBuffer.Put( ">", 4 ); + break; + default: + outputBuffer.PutChar( pchValue[i] ); + } + ++i; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Helper for emitting properly escaped VDF string values, we always use UTF8, +// and we escape only " and \ +//----------------------------------------------------------------------------- +void EmitVDFString( CUtlBuffer &outputBuffer, const char *pchValue ) +{ + outputBuffer.PutChar( '"' ); + + if ( pchValue ) + { + int i = 0; + while( pchValue[i] ) + { + switch ( pchValue[i] ) + { + case '\\': + outputBuffer.Put( "\\\\", 2 ); + break; + case '"': + outputBuffer.Put( "\\\"", 2 ); + break; + default: + outputBuffer.PutChar( pchValue[i] ); + } + ++i; + } + } + + outputBuffer.PutChar( '"' ); +} + +namespace GCSDK +{ + +enum { k_LineBreakEveryNGroups = 18 }; // line break every 18 groups of 4 characters (every 72 characters) + +uint32 Base64EncodeMaxOutput( const uint32 cubData, const char *pszLineBreak ) +{ + // terminating null + 4 chars per 3-byte group + line break after every 18 groups (72 output chars) + final line break + uint32 nGroups = (cubData+2)/3; + uint32 cchRequired = 1 + nGroups*4 + ( pszLineBreak ? Q_strlen(pszLineBreak)*(1+(nGroups-1)/k_LineBreakEveryNGroups) : 0 ); + return cchRequired; +} + +bool Base64Encode( const uint8 *pubData, uint32 cubData, char *pchEncodedData, uint32 *pcchEncodedData, const char *pszLineBreak ) +{ + if ( pchEncodedData == NULL ) + { + AssertMsg( *pcchEncodedData == 0, "NULL output buffer with non-zero size passed to Base64Encode" ); + *pcchEncodedData = Base64EncodeMaxOutput( cubData, pszLineBreak ); + return true; + } + + const uint8 *pubDataEnd = pubData + cubData; + char *pchEncodedDataStart = pchEncodedData; + uint32 unLineBreakLen = pszLineBreak ? Q_strlen( pszLineBreak ) : 0; + int nNextLineBreak = unLineBreakLen ? k_LineBreakEveryNGroups : INT_MAX; + + const char * const pszBase64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + uint32 cchEncodedData = *pcchEncodedData; + if ( cchEncodedData == 0 ) + goto out_of_space; + + --cchEncodedData; // pre-decrement for the terminating null so we don't forget about it + + // input 3 x 8-bit, output 4 x 6-bit + while ( pubDataEnd - pubData >= 3 ) + { + if ( cchEncodedData < 4 + unLineBreakLen ) + goto out_of_space; + + if ( nNextLineBreak == 0 ) + { + memcpy( pchEncodedData, pszLineBreak, unLineBreakLen ); + pchEncodedData += unLineBreakLen; + cchEncodedData -= unLineBreakLen; + nNextLineBreak = k_LineBreakEveryNGroups; + } + + uint32 un24BitsData; + un24BitsData = (uint32) pubData[0] << 16; + un24BitsData |= (uint32) pubData[1] << 8; + un24BitsData |= (uint32) pubData[2]; + pubData += 3; + + pchEncodedData[0] = pszBase64Chars[ (un24BitsData >> 18) & 63 ]; + pchEncodedData[1] = pszBase64Chars[ (un24BitsData >> 12) & 63 ]; + pchEncodedData[2] = pszBase64Chars[ (un24BitsData >> 6) & 63 ]; + pchEncodedData[3] = pszBase64Chars[ (un24BitsData ) & 63 ]; + pchEncodedData += 4; + cchEncodedData -= 4; + --nNextLineBreak; + } + + // Clean up remaining 1 or 2 bytes of input, pad output with '=' + if ( pubData != pubDataEnd ) + { + if ( cchEncodedData < 4 + unLineBreakLen ) + goto out_of_space; + + if ( nNextLineBreak == 0 ) + { + memcpy( pchEncodedData, pszLineBreak, unLineBreakLen ); + pchEncodedData += unLineBreakLen; + cchEncodedData -= unLineBreakLen; + } + + uint32 un24BitsData; + un24BitsData = (uint32) pubData[0] << 16; + if ( pubData+1 != pubDataEnd ) + { + un24BitsData |= (uint32) pubData[1] << 8; + } + pchEncodedData[0] = pszBase64Chars[ (un24BitsData >> 18) & 63 ]; + pchEncodedData[1] = pszBase64Chars[ (un24BitsData >> 12) & 63 ]; + pchEncodedData[2] = pubData+1 != pubDataEnd ? pszBase64Chars[ (un24BitsData >> 6) & 63 ] : '='; + pchEncodedData[3] = '='; + pchEncodedData += 4; + cchEncodedData -= 4; + } + + if ( unLineBreakLen ) + { + if ( cchEncodedData < unLineBreakLen ) + goto out_of_space; + memcpy( pchEncodedData, pszLineBreak, unLineBreakLen ); + pchEncodedData += unLineBreakLen; + cchEncodedData -= unLineBreakLen; + } + + *pchEncodedData = 0; + *pcchEncodedData = pchEncodedData - pchEncodedDataStart; + return true; + +out_of_space: + *pchEncodedData = 0; + *pcchEncodedData = Base64EncodeMaxOutput( cubData, pszLineBreak ); + AssertMsg( false, "CCrypto::Base64Encode: insufficient output buffer (up to n*4/3+5 bytes required, plus linebreaks)" ); + return false; +} + +bool Base64Decode( const char *pchData, uint32 cchDataMax, uint8 *pubDecodedData, uint32 *pcubDecodedData, bool bIgnoreInvalidCharacters ) +{ + uint32 cubDecodedData = *pcubDecodedData; + uint32 cubDecodedDataOrig = cubDecodedData; + + if ( pubDecodedData == NULL ) + { + AssertMsg( *pcubDecodedData == 0, "NULL output buffer with non-zero size passed to Base64Decode" ); + cubDecodedDataOrig = cubDecodedData = ~0u; + } + + // valid base64 character range: '+' (0x2B) to 'z' (0x7A) + // table entries are 0-63, -1 for invalid entries, -2 for '=' + static const char rgchInvBase64[] = { + 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + -1, -1, -1, -2, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51 + }; + COMPILE_TIME_ASSERT( Q_ARRAYSIZE(rgchInvBase64) == 0x7A - 0x2B + 1 ); + + uint32 un24BitsWithSentinel = 1; + while ( cchDataMax-- > 0 ) + { + char c = *pchData++; + + if ( (uint8)(c - 0x2B) >= Q_ARRAYSIZE( rgchInvBase64 ) ) + { + if ( c == '\0' ) + break; + + if ( !bIgnoreInvalidCharacters && !( c == '\r' || c == '\n' || c == '\t' || c == ' ' ) ) + goto decode_failed; + else + continue; + } + + c = rgchInvBase64[(uint8)(c - 0x2B)]; + if ( c < 0 ) + { + if ( c == -2 ) // -2 -> terminating '=' + break; + + if ( !bIgnoreInvalidCharacters ) + goto decode_failed; + else + continue; + } + + un24BitsWithSentinel <<= 6; + un24BitsWithSentinel |= c; + if ( un24BitsWithSentinel & (1<<24) ) + { + if ( cubDecodedData < 3 ) // out of space? go to final write logic + break; + if ( pubDecodedData ) + { + pubDecodedData[0] = (uint8)( un24BitsWithSentinel >> 16 ); + pubDecodedData[1] = (uint8)( un24BitsWithSentinel >> 8); + pubDecodedData[2] = (uint8)( un24BitsWithSentinel ); + pubDecodedData += 3; + } + cubDecodedData -= 3; + un24BitsWithSentinel = 1; + } + } + + // If un24BitsWithSentinel contains data, output the remaining full bytes + if ( un24BitsWithSentinel >= (1<<6) ) + { + // Possibilities are 3, 2, 1, or 0 full output bytes. + int nWriteBytes = 3; + while ( un24BitsWithSentinel < (1<<24) ) + { + nWriteBytes--; + un24BitsWithSentinel <<= 6; + } + + // Write completed bytes to output + while ( nWriteBytes-- > 0 ) + { + if ( cubDecodedData == 0 ) + { + AssertMsg( false, "CCrypto::Base64Decode: insufficient output buffer (up to n*3/4+2 bytes required)" ); + goto decode_failed; + } + if ( pubDecodedData ) + { + *pubDecodedData++ = (uint8)(un24BitsWithSentinel >> 16); + } + --cubDecodedData; + un24BitsWithSentinel <<= 8; + } + } + + *pcubDecodedData = cubDecodedDataOrig - cubDecodedData; + return true; + +decode_failed: + *pcubDecodedData = cubDecodedDataOrig - cubDecodedData; + return false; +} + +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CWebAPIResponse::CWebAPIResponse() +{ + m_pValues = NULL; + m_unExpirationSeconds = 0; + m_rtLastModified = 0; + m_bExtendedArrays = false; + m_bJSONAnonymousRootNode = false; + m_eStatusCode = k_EHTTPStatusCode500InternalServerError; +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CWebAPIResponse::~CWebAPIResponse() +{ + if ( m_pValues) + delete m_pValues; + m_pValues = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Outputs formatted data to buffer +//----------------------------------------------------------------------------- +bool CWebAPIResponse::BEmitFormattedOutput( EWebAPIOutputFormat eFormat, CUtlBuffer &outputBuffer, size_t unMaxResultSize ) +{ + VPROF_BUDGET( "CWebAPIResponse::BEmitFormattedOutput", VPROF_BUDGETGROUP_STEAM ); + outputBuffer.Clear(); + + switch( eFormat ) + { + case k_EWebAPIOutputFormat_JSON: + return BEmitJSON( outputBuffer, unMaxResultSize ); + case k_EWebAPIOutputFormat_XML: + return BEmitXML( outputBuffer, unMaxResultSize ); + case k_EWebAPIOutputFormat_VDF: + return BEmitVDF( outputBuffer, unMaxResultSize ); + case k_EWebAPIOutputFormat_ParameterEncoding: + return BEmitParameterEncoding( outputBuffer ); + default: + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Emits JSON formatted representation of response +//----------------------------------------------------------------------------- +bool CWebAPIResponse::BEmitJSON( CUtlBuffer &outputBuffer, size_t unMaxResultSize ) +{ + outputBuffer.PutChar( '{' ); + outputBuffer.PutChar( '\n' ); + + CWebAPIValues *pValues = m_pValues; + + //if we have an anonymous root, get the first child instead of the root itself + if ( m_bJSONAnonymousRootNode && m_pValues ) + { + pValues = m_pValues->GetFirstChild(); + } + + if ( pValues ) + { + if( !CWebAPIValues::BEmitJSONRecursive( pValues, outputBuffer, 1, unMaxResultSize, m_bExtendedArrays ) ) + return false; + } + + outputBuffer.PutChar( '\n' ); + outputBuffer.PutChar( '}' ); + if ( !outputBuffer.IsValid() ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Emits KeyValues .vdf style formatted representation of response +//----------------------------------------------------------------------------- +bool CWebAPIResponse::BEmitVDF( CUtlBuffer &outputBuffer, size_t unMaxResultSize ) +{ + if ( m_pValues ) + if( !CWebAPIValues::BEmitVDFRecursive( m_pValues, outputBuffer, 0, 0, unMaxResultSize, m_bExtendedArrays ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Emits XML formatted representation of response +//----------------------------------------------------------------------------- +bool CWebAPIResponse::BEmitXML( CUtlBuffer &outputBuffer, size_t unMaxResultSize ) +{ + const char *pchProlog = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + outputBuffer.Put( pchProlog, Q_strlen( pchProlog ) ); + + outputBuffer.Put( "<!DOCTYPE ", Q_strlen( "<!DOCTYPE " ) ); + if ( m_pValues && m_pValues->GetName() ) + EmitXMLString( outputBuffer, m_pValues->GetName() ); + outputBuffer.PutChar('>'); + outputBuffer.PutChar('\n'); + + if ( m_pValues ) + if( !CWebAPIValues::BEmitXMLRecursive( m_pValues, outputBuffer, 0, unMaxResultSize ) ) + return false; + + if ( !outputBuffer.IsValid() ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Emits Parameter Encoding formatted representation of response +//----------------------------------------------------------------------------- +bool CWebAPIResponse::BEmitParameterEncoding( CUtlBuffer &outputBuffer ) +{ + if ( !m_pValues ) + return true; + + CWebAPIValues *pValue = m_pValues->GetFirstChild(); + while ( pValue != NULL ) + { + outputBuffer.Put( pValue->GetName(), Q_strlen( pValue->GetName() ) ); + outputBuffer.Put( "=", 1 ); + + CUtlString sValue; + switch ( pValue->GetType() ) + { + case k_EWebAPIValueType_Object: + Assert( false ); + return false; // no cursive values + case k_EWebAPIValueType_NumericArray: + Assert( false ); + return false; // no arrays + case k_EWebAPIValueType_BinaryBlob: + Assert( false ); + return false; // no binary + + case k_EWebAPIValueType_Int32: + sValue = CNumStr( pValue->GetInt32Value() ); + break; + case k_EWebAPIValueType_Int64: + sValue = CNumStr( pValue->GetInt64Value() ); + break; + case k_EWebAPIValueType_UInt32: + sValue = CNumStr( pValue->GetUInt32Value() ); + break; + case k_EWebAPIValueType_UInt64: + sValue = CNumStr( pValue->GetUInt64Value() ); + break; + case k_EWebAPIValueType_Double: + sValue = CNumStr( pValue->GetDoubleValue() ); + break; + case k_EWebAPIValueType_String: + pValue->GetStringValue( sValue ); + break; + case k_EWebAPIValueType_Bool: + sValue = CNumStr( pValue->GetBoolValue() ); + break; + } + outputBuffer.Put( sValue, sValue.Length() ); + + pValue = pValue->GetNextChild(); + if ( pValue ) + outputBuffer.Put( "&", 1 ); + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Resets the response to be empty +//----------------------------------------------------------------------------- +void CWebAPIResponse::Clear() +{ + if ( m_pValues ) + delete m_pValues; + + m_pValues = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Access the root value element in the response +//----------------------------------------------------------------------------- +CWebAPIValues *CWebAPIResponse::CreateRootValue( const char *pchName ) +{ + if ( m_pValues ) + { + AssertMsg( false, "CWwebAPIResponse::CreateRootValue called while root already existed." ); + Clear(); + } + + m_pValues = new CWebAPIValues( pchName ); + return m_pValues; +} + + +//---------------------------------------------------------------------------- +// Purpose: Gets a singleton buffer pool for webapi values +//---------------------------------------------------------------------------- +#ifdef GC +static GCConVar webapi_values_max_pool_size_mb( "webapi_values_max_pool_size_mb", "10", "Maximum size in bytes of the WebAPIValues buffer pool" ); +static GCConVar webapi_values_init_buffer_size( "webapi_values_init_buffer_size", "65536", "Initial buffer size for buffers in the WebAPIValues buffer pool" ); +/*static*/ CBufferPoolMT &CWebAPIValues::GetBufferPool() +{ + static CBufferPoolMT s_bufferPool( "WebAPIValues", webapi_values_max_pool_size_mb, webapi_values_init_buffer_size ); + return s_bufferPool; +} +#endif + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CWebAPIValues::CWebAPIValues( CWebAPIValues *pParent, const char *pchName, EWebAPIValueType eValueType, const char *pchArrayElementNames ) +{ + InitInternal( pParent, -1, eValueType, pchArrayElementNames ); + SetName( pchName ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CWebAPIValues::CWebAPIValues( const char *pchName ) +{ + InitInternal( NULL, -1, k_EWebAPIValueType_Object, NULL ); + SetName( pchName ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CWebAPIValues::CWebAPIValues( const char *pchName, const char *pchArrayElementNames ) +{ + InitInternal( NULL, -1, k_EWebAPIValueType_NumericArray, pchArrayElementNames ); + SetName( pchName ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CWebAPIValues::CWebAPIValues( CWebAPIValues *pParent, int nNamePos, EWebAPIValueType eValueType, const char *pchArrayElementNames ) +{ + InitInternal( pParent, nNamePos, eValueType, pchArrayElementNames ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +void CWebAPIValues::InitInternal( CWebAPIValues *pParent, int nNamePos, EWebAPIValueType eValueType, const char *pchArrayElementNames ) +{ + if ( NULL == pParent ) + { +#ifdef GC + m_pStringBuffer = GetBufferPool().GetBuffer(); +#else + m_pStringBuffer = new CUtlBuffer; +#endif + } + else + { + m_pStringBuffer = pParent->m_pStringBuffer; + } + + m_nNamePos = nNamePos; + + m_eValueType = eValueType; + if ( m_eValueType == k_EWebAPIValueType_NumericArray ) + { + Assert( pchArrayElementNames ); + m_nArrayChildElementNamePos = m_pStringBuffer->TellPut(); + m_pStringBuffer->PutString( pchArrayElementNames ); + } + + m_pFirstChild = NULL; + m_pLastChild = NULL; + m_pNextPeer = NULL; + m_pParent = pParent; +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CWebAPIValues::~CWebAPIValues() +{ + ClearValue(); + + CWebAPIValues *pChild = m_pFirstChild; + while( pChild ) + { + CWebAPIValues *pDelete = pChild; + pChild = pChild->m_pNextPeer; + delete pDelete; + } + m_pFirstChild = NULL; + m_pNextPeer = NULL; + + if ( NULL == m_pParent ) + { +#ifdef GC + GetBufferPool().ReturnBuffer( m_pStringBuffer ); +#else + delete m_pStringBuffer; +#endif + } + + // This two ptrs are just for optimized traversal at runtime, deleting just + // our first child and next peer will lead to the full tree being deleted correctly. + m_pLastChild = NULL; + m_pParent = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the name of the values node +//----------------------------------------------------------------------------- +void CWebAPIValues::SetName( const char * pchName ) +{ + if ( pchName == NULL ) + { + AssertMsg( false, "CWebAPIValues constructed with NULL name, breaks some output serialization. Shouldn't do this." ); + m_nNamePos = -1; + } + else + { + // Shouldn't use ', ", &, <, or > in names since they can't output well in XML. : is no good since it implies namespacing in XML. + // Assert about it so we don't end up with responses that are badly formed in XML output. + int unLen = 0; + while ( pchName[unLen] != 0 ) + { + if ( pchName[unLen] == '\'' || pchName[unLen] == '"' || pchName[unLen] == '&' + || pchName[unLen] == '>' || pchName[unLen] == '>' || pchName[unLen] == ':' ) + { + AssertMsg( false, "Shouldn't use any of '\"&<>: in CWebAPIValues node names, you used %s", pchName ); + break; + } + ++unLen; + } + + m_nNamePos = m_pStringBuffer->TellPut(); + m_pStringBuffer->PutString( pchName ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Assert that we don't have any child nodes, this is used when setting a +// native type value. We don't support having both our own value and children. You +// are either an array of more values, or you are a value yourself. +//----------------------------------------------------------------------------- +void CWebAPIValues::AssertNoChildren() +{ + AssertMsg( m_pFirstChild == NULL, "CWebAPIValues has child nodes, but you are trying to set a direct value for it. Can't have both children and your own value." ); +} + +//----------------------------------------------------------------------------- +// Purpose: Clears any existing value, freeing memory if needed +//----------------------------------------------------------------------------- +void CWebAPIValues::ClearValue() +{ + m_eValueType = k_EWebAPIValueType_Object; +} + + +//----------------------------------------------------------------------------- +// Purpose: Setter +//----------------------------------------------------------------------------- +void CWebAPIValues::SetStringValue( const char *pchValue ) +{ + ClearValue(); + AssertNoChildren(); + m_eValueType = k_EWebAPIValueType_String; + if ( pchValue == NULL ) + { + m_nStrValuePos = -1; + } + else + { + m_nStrValuePos = m_pStringBuffer->TellPut(); + m_pStringBuffer->PutString( pchValue ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Setter +//----------------------------------------------------------------------------- +void CWebAPIValues::SetInt32Value( int32 nValue ) +{ + ClearValue(); + AssertNoChildren(); + m_eValueType = k_EWebAPIValueType_Int32; + m_nValue = nValue; +} + + +//----------------------------------------------------------------------------- +// Purpose: Setter +//----------------------------------------------------------------------------- +void CWebAPIValues::SetUInt32Value( uint32 unValue ) +{ + ClearValue(); + AssertNoChildren(); + m_eValueType = k_EWebAPIValueType_UInt32; + m_unValue = unValue; +} + + +//----------------------------------------------------------------------------- +// Purpose: Setter +//----------------------------------------------------------------------------- +void CWebAPIValues::SetInt64Value ( int64 lValue ) +{ + ClearValue(); + AssertNoChildren(); + m_eValueType = k_EWebAPIValueType_Int64; + m_lValue = lValue; +} + + +//----------------------------------------------------------------------------- +// Purpose: Setter +//----------------------------------------------------------------------------- +void CWebAPIValues::SetUInt64Value( uint64 ulValue ) +{ + ClearValue(); + AssertNoChildren(); + m_eValueType = k_EWebAPIValueType_UInt64; + m_ulValue = ulValue; +} + + +//----------------------------------------------------------------------------- +// Purpose: Setter +//----------------------------------------------------------------------------- +void CWebAPIValues::SetDoubleValue( double flValue ) +{ + ClearValue(); + AssertNoChildren(); + m_eValueType = k_EWebAPIValueType_Double; + m_flValue = flValue; +} + + +//----------------------------------------------------------------------------- +// Purpose: Setter +//----------------------------------------------------------------------------- +void CWebAPIValues::SetBoolValue( bool bValue ) +{ + ClearValue(); + AssertNoChildren(); + m_eValueType = k_EWebAPIValueType_Bool; + m_bValue = bValue; +} + + +//----------------------------------------------------------------------------- +// Purpose: Setter +//----------------------------------------------------------------------------- +void CWebAPIValues::SetNullValue() +{ + ClearValue(); + AssertNoChildren(); + m_eValueType = k_EWebAPIValueType_Null; +} + + +//----------------------------------------------------------------------------- +// Purpose: Setter +//----------------------------------------------------------------------------- +void CWebAPIValues::SetBinaryValue( const uint8 *pValue, uint32 unBytes ) +{ + ClearValue(); + AssertNoChildren(); + m_eValueType = k_EWebAPIValueType_BinaryBlob; + if ( pValue == NULL || unBytes < 1 ) + { + m_BinaryValue.m_nDataPos = 0; + m_BinaryValue.m_unBytes = 0; + } + else + { + m_BinaryValue.m_unBytes = unBytes; + m_BinaryValue.m_nDataPos = m_pStringBuffer->TellPut(); + m_pStringBuffer->Put( pValue, unBytes ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the type currently held by the node +//----------------------------------------------------------------------------- +EWebAPIValueType CWebAPIValues::GetType() const +{ + return m_eValueType; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get int32 value +//----------------------------------------------------------------------------- +int32 CWebAPIValues::GetInt32Value() const +{ + // we can read uint64 values this way too + switch ( m_eValueType ) + { + case k_EWebAPIValueType_Int32: + return m_nValue; + case k_EWebAPIValueType_UInt32: // because we can't tell the type of an int when we parse this node might have different type + case k_EWebAPIValueType_UInt64: + case k_EWebAPIValueType_String: + { + uint32 uiVal = GetUInt32Value(); + AssertMsg( uiVal < INT_MAX, "GetInt32Value called on node with %u, which is too big to fit in an Int32", uiVal ); + return (int32)uiVal; + } + } + + AssertMsg( false, "Shouldn't call CWebAPIValues::GetInt32Value unless value type is int32, uint32, uint64, or string. %d does not.", m_eValueType ); + return 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get uint32 value +//----------------------------------------------------------------------------- +uint32 CWebAPIValues::GetUInt32Value() const +{ + // we can read uint64 values this way too + switch ( m_eValueType ) + { + case k_EWebAPIValueType_UInt32: + return m_unValue; + case k_EWebAPIValueType_UInt64: + case k_EWebAPIValueType_String: + { + uint64 uiVal = GetUInt64Value(); + AssertMsg( uiVal <= UINT_MAX, "GetUInt32Value called on node with %llu, which is too big to fit in an UInt32", uiVal ); + return (uint32)uiVal; + } + } + + AssertMsg( false, "Shouldn't call CWebAPIValues::GetUInt32Value unless value type is uint32, uint64, or string. %d does not", m_eValueType ); + return 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get int64 value +//----------------------------------------------------------------------------- +int64 CWebAPIValues::GetInt64Value() const +{ + // we can read int32 values this way too + switch ( m_eValueType ) + { + case k_EWebAPIValueType_Int32: + return GetInt32Value(); + case k_EWebAPIValueType_Int64: + return m_lValue; + case k_EWebAPIValueType_String: + if ( m_nStrValuePos < 0 ) + { + return 0ull; + } + else + { +#if defined(_PS3) || defined(POSIX) + return strtoll( (const char *)m_pStringBuffer->Base() + m_nStrValuePos, NULL, 10); +#else + return _strtoi64( (const char *)m_pStringBuffer->Base() + m_nStrValuePos, NULL, 10); +#endif + } + default: + AssertMsg1( false, "Shouldn't call CWebAPIValues::GetInt64Value unless value type matches. %d does not", m_eValueType ); + return 0; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Get uint64 value +//----------------------------------------------------------------------------- +uint64 CWebAPIValues::GetUInt64Value() const +{ + // we can read uint32 values this way too + switch ( m_eValueType ) + { + case k_EWebAPIValueType_UInt32: + return GetUInt32Value(); + case k_EWebAPIValueType_UInt64: + return m_ulValue; + case k_EWebAPIValueType_String: + if ( m_nStrValuePos < 0 ) + { + return 0ull; + } + else + { +#if defined(_PS3) || defined(POSIX) + return strtoull( (const char *)m_pStringBuffer->Base() + m_nStrValuePos, NULL, 10); +#else + return _strtoui64( (const char *)m_pStringBuffer->Base() + m_nStrValuePos, NULL, 10); +#endif + } + default: + AssertMsg1( false, "Shouldn't call CWebAPIValues::GetUInt64Value unless value type matches. %d does not", m_eValueType ); + return 0; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Get double value +//----------------------------------------------------------------------------- +double CWebAPIValues::GetDoubleValue() const +{ + switch ( m_eValueType ) + { + case k_EWebAPIValueType_Int32: + return (double)m_nValue; + case k_EWebAPIValueType_UInt32: + return (double)m_unValue; + case k_EWebAPIValueType_Int64: + return (double)m_lValue; + case k_EWebAPIValueType_UInt64: + return (double)m_ulValue; + case k_EWebAPIValueType_Double: + return m_flValue; + default: + AssertMsg1( false, "Shouldn't call CWebAPIValues::GetDoubleValue unless value type matches. %d does not", m_eValueType ); + return 0.0f; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Get bool value +//----------------------------------------------------------------------------- +bool CWebAPIValues::GetBoolValue() const +{ + if ( m_eValueType != k_EWebAPIValueType_Bool ) + { + AssertMsg( false, "Shouldn't call CWebAPIValues::GetBoolValue unless value type matches" ); + return false; + } + + return m_bValue; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Get string value +//----------------------------------------------------------------------------- +void CWebAPIValues::GetStringValue( CUtlString &stringOut ) const +{ + switch ( m_eValueType ) + { + case k_EWebAPIValueType_String: + if ( m_nStrValuePos < 0 ) + { + stringOut.Clear(); + } + else + { + stringOut = (const char *)m_pStringBuffer->Base() + m_nStrValuePos; + } + + return; + case k_EWebAPIValueType_Int32: + stringOut = CNumStr( m_nValue ).String(); + return; + case k_EWebAPIValueType_Int64: + stringOut = CNumStr( m_lValue ).String(); + return; + case k_EWebAPIValueType_UInt32: + stringOut = CNumStr( m_unValue ).String(); + return; + case k_EWebAPIValueType_UInt64: + stringOut = CNumStr( m_ulValue ).String(); + return; + case k_EWebAPIValueType_Double: + stringOut = CNumStr( m_flValue ).String(); + return; + case k_EWebAPIValueType_Bool: + stringOut = m_bValue ? "true" : "false"; + return; + default: + AssertMsg1( false, "CWebAPIValues::GetStringValue(), unable to convert data type %d to string", m_eValueType ); + stringOut = ""; + return; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Get binary blob value +//----------------------------------------------------------------------------- +void CWebAPIValues::GetBinaryValue( CUtlBuffer &bufferOut ) const +{ + if ( m_eValueType != k_EWebAPIValueType_BinaryBlob ) + { + AssertMsg( false, "Shouldn't call CWebAPIValues::GetBinaryValue unless value type matches" ); + bufferOut.Clear(); + return; + } + + bufferOut.Clear(); + bufferOut.EnsureCapacity( m_BinaryValue.m_unBytes ); + if ( m_BinaryValue.m_unBytes ) + bufferOut.Put( (byte*)m_pStringBuffer->Base() + m_BinaryValue.m_nDataPos, m_BinaryValue.m_unBytes ); + + return; +} + + +//----------------------------------------------------------------------------- +// Purpose: Create a child array of this node. Note that array nodes can only +// have un-named children, in XML the pchArrayElementNames value will be used +// as the element name for each of the children of the array, in JSON it will simply +// be a numerically indexed [] array. +//----------------------------------------------------------------------------- +CWebAPIValues * CWebAPIValues::CreateChildArray( const char *pchName, const char *pchArrayElementNames ) +{ + return CreateChildInternal( pchName, k_EWebAPIValueType_NumericArray, pchArrayElementNames ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Create a child of this node. Note, it's possible to create multiple, +// children with the same name, but you really don't want to. We'll assert about it +// in debug builds to detect, but not in release. If you do create duplicates you'll +// have broken JSON output. +//----------------------------------------------------------------------------- +CWebAPIValues * CWebAPIValues::CreateChildObject( const char *pchName ) +{ + return CreateChildInternal( pchName, k_EWebAPIValueType_Object ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return an existing child object - otherwise create one and return that. +//----------------------------------------------------------------------------- +CWebAPIValues *CWebAPIValues::FindOrCreateChildObject( const char *pchName ) +{ + CWebAPIValues *pChild = FindChild( pchName ); + if ( pChild ) + { + return pChild; + } + + return CreateChildObject( pchName ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a child object to the array, this should only be called on objects that are of the array type +//----------------------------------------------------------------------------- +CWebAPIValues * CWebAPIValues::AddChildObjectToArray() +{ + if ( m_eValueType != k_EWebAPIValueType_NumericArray ) + { + AssertMsg( m_eValueType == k_EWebAPIValueType_NumericArray, "Can't add array elements to CWebAPIVAlues unless type is of numeric array." ); + return NULL; + } + + // Use child element array name as name of all children of arrays + return CreateChildInternal( NULL, k_EWebAPIValueType_Object ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Add a child array to the array, this should only be called on objects that are of the array type +//----------------------------------------------------------------------------- +CWebAPIValues * CWebAPIValues::AddChildArrayToArray( const char * pchArrayElementNames ) +{ + if ( m_eValueType != k_EWebAPIValueType_NumericArray ) + { + AssertMsg( m_eValueType == k_EWebAPIValueType_NumericArray, "Can't add array elements to CWebAPIVAlues unless type is of numeric array." ); + return NULL; + } + + // Use child element array name as name of all children of arrays + return CreateChildInternal( NULL, k_EWebAPIValueType_NumericArray, pchArrayElementNames ); +} + +//----------------------------------------------------------------------------- +// Purpose: Internal helper for creating children +//----------------------------------------------------------------------------- +CWebAPIValues * CWebAPIValues::CreateChildInternal( const char *pchName, EWebAPIValueType eValueType, const char *pchArrayElementNames ) +{ + // Shouldn't create children if you have a direct value. You are either an object or array of children, + // or a native value type. Not both. + + AssertMsg( m_eValueType == k_EWebAPIValueType_Object || m_eValueType == k_EWebAPIValueType_NumericArray, "You are trying to create a child node of a CWebAPIValues object, but it has a direct value already. Can't have children and a value." ); + if ( m_eValueType != k_EWebAPIValueType_Object && m_eValueType != k_EWebAPIValueType_NumericArray ) + ClearValue(); + + // Shouldn't create named children if you are a numeric array + CWebAPIValues *pNewNode; + if ( m_eValueType == k_EWebAPIValueType_NumericArray ) + { + if ( pchName ) + { + AssertMsg( false, "Can't create named child of CWebAPIValues object of type NumericArray. Should call AddArrayElement instead of CreateChild*." ); + } + // Force name to match what all items in the array should use + pNewNode = new CWebAPIValues( this, m_nArrayChildElementNamePos, eValueType, pchArrayElementNames ); + } + else + { + pNewNode = new CWebAPIValues( this, pchName, eValueType, pchArrayElementNames ); + } + + if ( eValueType == k_EWebAPIValueType_NumericArray ) + { + Assert( pchArrayElementNames ); + } + + if ( !m_pFirstChild ) + { + m_pLastChild = m_pFirstChild = pNewNode; + return m_pFirstChild; + } + else + { + + CWebAPIValues *pCurLastChild = m_pLastChild; + +#ifdef _DEBUG + // In debug, traverse all children so we can check for duplicate names, which will break JSON output! + pCurLastChild = m_pFirstChild; + if ( m_eValueType != k_EWebAPIValueType_NumericArray ) + { + if ( Q_stricmp( pCurLastChild->GetName(), pchName ) == 0 ) + { + AssertMsg( false, "Trying to create CWebAPIValues child with name %s that conflicts with existing child. Breaks JSON output!", pchName ); + } + } + + while ( pCurLastChild->m_pNextPeer ) + { + pCurLastChild = pCurLastChild->m_pNextPeer; + if ( m_eValueType != k_EWebAPIValueType_NumericArray ) + { + if ( Q_stricmp( pCurLastChild->GetName(), pchName ) == 0 ) + { + AssertMsg( false, "Trying to create CWebAPIValues child with name %s that conflicts with existing child. Breaks JSON output!", pchName ); + } + } + } + // Also, in debug assert last child ptr looks correct + Assert( m_pLastChild == pCurLastChild ); +#endif + + + m_pLastChild = pCurLastChild->m_pNextPeer = pNewNode; + return m_pLastChild; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Set a child node's string value +//----------------------------------------------------------------------------- +void CWebAPIValues::SetChildStringValue( const char *pchChildName, const char *pchValue ) +{ + CreateChildObject( pchChildName )->SetStringValue( pchValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set a child node's int32 value +//----------------------------------------------------------------------------- +void CWebAPIValues::SetChildInt32Value( const char *pchChildName, int32 nValue ) +{ + CreateChildObject( pchChildName )->SetInt32Value( nValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set a child node's uint32 value +//----------------------------------------------------------------------------- +void CWebAPIValues::SetChildUInt32Value( const char *pchChildName, uint32 unValue ) +{ + CreateChildObject( pchChildName )->SetUInt32Value( unValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set a child node's int64 value +//----------------------------------------------------------------------------- +void CWebAPIValues::SetChildInt64Value ( const char *pchChildName, int64 lValue ) +{ + CreateChildObject( pchChildName )->SetInt64Value( lValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set a child node's uint64 value +//----------------------------------------------------------------------------- +void CWebAPIValues::SetChildUInt64Value( const char *pchChildName, uint64 ulValue ) +{ + CreateChildObject( pchChildName )->SetUInt64Value( ulValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set a child node's double value +//----------------------------------------------------------------------------- +void CWebAPIValues::SetChildDoubleValue( const char *pchChildName, double flValue ) +{ + CreateChildObject( pchChildName )->SetDoubleValue( flValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set a child node's binary blob value +//----------------------------------------------------------------------------- +void CWebAPIValues::SetChildBinaryValue( const char *pchChildName, const uint8 *pValue, uint32 unBytes ) +{ + CreateChildObject( pchChildName )->SetBinaryValue( pValue, unBytes ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set a child node's boolean value +//----------------------------------------------------------------------------- +void CWebAPIValues::SetChildBoolValue( const char *pchChildName, bool bValue ) +{ + CreateChildObject( pchChildName )->SetBoolValue( bValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set a child node's boolean value +//----------------------------------------------------------------------------- +void CWebAPIValues::SetChildNullValue( const char *pchChildName ) +{ + CreateChildObject( pchChildName )->SetNullValue(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Get a child node's int32 value or return the default if the node doesn't exist +//----------------------------------------------------------------------------- +int32 CWebAPIValues::GetChildInt32Value( const char *pchChildName, int32 nDefault ) const +{ + const CWebAPIValues *pChild = FindChild( pchChildName ); + if( pChild ) + return pChild->GetInt32Value(); + else + return nDefault; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get a child node's uint32 value or return the default if the node doesn't exist +//----------------------------------------------------------------------------- +uint32 CWebAPIValues::GetChildUInt32Value( const char *pchChildName, uint32 unDefault ) const +{ + const CWebAPIValues *pChild = FindChild( pchChildName ); + if( pChild ) + return pChild->GetUInt32Value(); + else + return unDefault; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Get a child node's int64 value or return the default if the node doesn't exist +//----------------------------------------------------------------------------- +int64 CWebAPIValues::GetChildInt64Value( const char *pchChildName, int64 lDefault ) const +{ + const CWebAPIValues *pChild = FindChild( pchChildName ); + if( pChild ) + return pChild->GetInt64Value(); + else + return lDefault; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Get a child node's uint64 value or return the default if the node doesn't exist +//----------------------------------------------------------------------------- +uint64 CWebAPIValues::GetChildUInt64Value( const char *pchChildName, uint64 ulDefault ) const +{ + const CWebAPIValues *pChild = FindChild( pchChildName ); + if( pChild ) + return pChild->GetUInt64Value(); + else + return ulDefault; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Get a child node's double value or return the default if the node doesn't exist +//----------------------------------------------------------------------------- +double CWebAPIValues::GetChildDoubleValue( const char *pchChildName, double flDefault ) const +{ + const CWebAPIValues *pChild = FindChild( pchChildName ); + if( pChild ) + return pChild->GetDoubleValue(); + else + return flDefault; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Get a child node's string value or return the default if the node doesn't exist +//----------------------------------------------------------------------------- +void CWebAPIValues::GetChildStringValue( CUtlString &stringOut, const char *pchChildName, const char *pchDefault ) const +{ + const CWebAPIValues *pChild = FindChild( pchChildName ); + if( pChild ) + { + pChild->GetStringValue( stringOut ); + } + else + { + stringOut = pchDefault; + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Get a child node's binary blob value (returns false if the child wasn't found) +//----------------------------------------------------------------------------- +bool CWebAPIValues::BGetChildBinaryValue( CUtlBuffer &bufferOut, const char *pchChildName ) const +{ + const CWebAPIValues *pChild = FindChild( pchChildName ); + if( pChild ) + { + pChild->GetBinaryValue( bufferOut ); + return true; + } + else + { + return false; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Get a child node's binary blob value (returns false if the child wasn't found) +//----------------------------------------------------------------------------- +bool CWebAPIValues::IsChildNullValue( const char *pchChildName ) const +{ + const CWebAPIValues *pChild = FindChild( pchChildName ); + if( pChild ) + return pChild->IsNullValue(); + else + return false; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Get a child node's bool value or return the default if the node doesn't exist +//----------------------------------------------------------------------------- +bool CWebAPIValues::GetChildBoolValue( const char *pchChildName, bool bDefault ) const +{ + const CWebAPIValues *pChild = FindChild( pchChildName ); + if( pChild ) + return pChild->GetBoolValue(); + else + return bDefault; +} + + +//----------------------------------------------------------------------------- +// Purpose: Find first matching child by name, O(N) on number of children, this class isn't designed for searching +//----------------------------------------------------------------------------- +CWebAPIValues * CWebAPIValues::FindChild( const char *pchName ) +{ + CWebAPIValues *pCurLastChild = m_pFirstChild; + + while ( pCurLastChild ) + { + if ( Q_stricmp( pCurLastChild->GetName(), pchName ) == 0 ) + return pCurLastChild; + + pCurLastChild = pCurLastChild->m_pNextPeer; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the first child of this node +//----------------------------------------------------------------------------- +CWebAPIValues * CWebAPIValues::GetFirstChild() +{ + return m_pFirstChild; +} + + +//----------------------------------------------------------------------------- +// Purpose: Call this on the returned value from GetFirstChild() or a previous GetNextChild() call to +// proceed to the next child of the parent GetFirstChild() was originally called on. +//----------------------------------------------------------------------------- +CWebAPIValues * CWebAPIValues::GetNextChild() +{ + return m_pNextPeer; +} + + +//----------------------------------------------------------------------------- +// Purpose: Call this on any node to return the parent of that node +//----------------------------------------------------------------------------- +CWebAPIValues * CWebAPIValues::GetParent() +{ + return m_pParent; +} + +//----------------------------------------------------------------------------- +// Purpose: Deletes a child node by name +//----------------------------------------------------------------------------- +void CWebAPIValues::DeleteChild( const char *pchName ) +{ + CWebAPIValues *pChild = NULL; // child we're examining, could be NULL at exit if we don't find it + CWebAPIValues *pPrev = NULL; // previous sibling, or NULL + + for ( pChild = m_pFirstChild; pChild != NULL; pPrev = pChild, pChild = pChild->m_pNextPeer ) + { + if ( !Q_stricmp( pChild->GetName(), pchName ) ) + { + if ( pChild == m_pFirstChild ) + { + // first child, fixup parent's pointer to take pChild out + Assert( pPrev == NULL ); + m_pFirstChild = pChild->m_pNextPeer; + } + else + { + // not first child, fixup sibling's pointer to take pChild out + Assert( pPrev != NULL ); + pPrev->m_pNextPeer = pChild->m_pNextPeer; + } + + // clean up next ptr on child so we don't double free + pChild->m_pNextPeer = NULL; + + // fixup last child pointer if pChild is the last child + if ( pChild == m_pLastChild ) + { + m_pLastChild = pPrev; + } + + break; + } + } + + // debug only, check that there is no child by the specified name any more and that it was excised OK + AssertMsg( FindChild( pchName ) == NULL, "cwebapivalues deleted child is still lurking" ); + Assert( pChild == NULL || pChild->m_pNextPeer == NULL ); + + // now take the removed child out of the heap + delete pChild; +} + +//----------------------------------------------------------------------------- +// Purpose: Emits JSON formatted representation of values +// +// when bEmitOldStyleArrays is true, arrays are emitted as a child of a singlet object, and any empty arrays +// will be emitted as having a single null member. +// +// when bEmitOldStyleArrays is false, arrays are emitted bare (no subobject) and empty arrays +// are emitted empty. +//----------------------------------------------------------------------------- +bool CWebAPIValues::BEmitJSONRecursive( const CWebAPIValues *pCurrent, CUtlBuffer &outputBuffer, int nTabLevel, size_t unMaxResultSize, bool bEmitOldStyleArrays ) +{ + bool bSuccess = true; + + + while( pCurrent ) + { + // don't let the buffer grow until it consumes all available memory + if( unMaxResultSize && (size_t)outputBuffer.TellMaxPut() > unMaxResultSize ) + { + return false; + } + + // Can't emit nameless nodes in JSON. Nodes should always have a name. + Assert( pCurrent->GetName() ); + if ( pCurrent->GetName() ) + { + for( int i=0; i < nTabLevel; ++i ) + outputBuffer.PutChar ( '\t' ); + + if ( !pCurrent->m_pParent || pCurrent->m_pParent->GetType() != k_EWebAPIValueType_NumericArray ) + { + EmitJSONString( outputBuffer, pCurrent->GetName() ); + outputBuffer.PutChar( ':' ); + outputBuffer.PutChar( ' ' ); + } + + if ( pCurrent->m_eValueType == k_EWebAPIValueType_Object || pCurrent->m_eValueType == k_EWebAPIValueType_NumericArray ) + { + + if( bEmitOldStyleArrays || pCurrent->m_eValueType == k_EWebAPIValueType_Object ) + { + outputBuffer.PutChar( '{' ); + outputBuffer.PutChar( '\n' ); + } + + if ( pCurrent->m_eValueType == k_EWebAPIValueType_NumericArray ) + { + if( bEmitOldStyleArrays ) + { + for( int i=0; i < nTabLevel+1; ++i ) + outputBuffer.PutChar ( '\t' ); + + EmitJSONString( outputBuffer, (const char *)pCurrent->m_pStringBuffer->Base() + pCurrent->m_nArrayChildElementNamePos ); + outputBuffer.PutChar( ':' ); + outputBuffer.PutChar( ' ' ); + } + + outputBuffer.PutChar( '[' ); + outputBuffer.PutChar( '\n' ); + } + + // First add any children + if ( pCurrent->m_pFirstChild ) + { + int nChildTabLevel = nTabLevel+1; + if ( pCurrent->m_eValueType == k_EWebAPIValueType_NumericArray && bEmitOldStyleArrays ) + ++nChildTabLevel; + + bSuccess = BEmitJSONRecursive( pCurrent->m_pFirstChild, outputBuffer, nChildTabLevel, unMaxResultSize, bEmitOldStyleArrays ); + if ( !bSuccess ) + return false; + } + else if ( bEmitOldStyleArrays ) + { + for( int i=0; i < nTabLevel + 1; ++i ) + outputBuffer.PutChar ( '\t' ); + + outputBuffer.Put( "null", 4 ); + } + + outputBuffer.PutChar( '\n' ); + + for( int i=0; i < nTabLevel; ++i ) + outputBuffer.PutChar ( '\t' ); + + if ( pCurrent->m_eValueType == k_EWebAPIValueType_NumericArray ) + { + if( bEmitOldStyleArrays ) + outputBuffer.PutChar( '\t' ); + outputBuffer.PutChar( ']' ); + outputBuffer.PutChar( '\n' ); + + for( int i=0; i < nTabLevel; ++i ) + outputBuffer.PutChar ( '\t' ); + } + + if( bEmitOldStyleArrays || pCurrent->m_eValueType == k_EWebAPIValueType_Object ) + { + outputBuffer.PutChar( '}' ); + } + } + else + { + + switch ( pCurrent->m_eValueType ) + { + case k_EWebAPIValueType_Int32: + { + CNumStr numStr( pCurrent->m_nValue ); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + case k_EWebAPIValueType_Int64: + { + CNumStr numStr( pCurrent->m_lValue ); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + case k_EWebAPIValueType_UInt32: + { + CNumStr numStr( pCurrent->m_unValue ); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + case k_EWebAPIValueType_UInt64: + { + CNumStr numStr( pCurrent->m_ulValue ); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + case k_EWebAPIValueType_Double: + { + CNumStr numStr( pCurrent->m_flValue ); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + + case k_EWebAPIValueType_String: + { + if ( pCurrent->m_nStrValuePos < 0 ) + outputBuffer.Put( "null", 4 ); + else + EmitJSONString( outputBuffer, (const char *)pCurrent->m_pStringBuffer->Base() + pCurrent->m_nStrValuePos ); + } + break; + + case k_EWebAPIValueType_Bool: + { + if ( !pCurrent->m_bValue ) + outputBuffer.Put( "false", 5 ); + else + outputBuffer.Put( "true", 4 ); + } + break; + + case k_EWebAPIValueType_Null: + { + outputBuffer.Put( "null", 4 ); + } + break; + + case k_EWebAPIValueType_BinaryBlob: + { + if ( pCurrent->m_BinaryValue.m_unBytes == 0 ) + outputBuffer.Put( "null", 4 ); + else + { + CUtlMemory<char> buffEncoded; + DbgVerify( Base64EncodeIntoUTLMemory( (const uint8 *)pCurrent->m_pStringBuffer->Base() + pCurrent->m_BinaryValue.m_nDataPos, pCurrent->m_BinaryValue.m_unBytes, buffEncoded ) ); + EmitJSONString( outputBuffer, buffEncoded.Base() ); + } + } + break; + default: + break; + } + } + } + + // Now, check for any peers + if ( bSuccess && pCurrent->m_pNextPeer ) + { + outputBuffer.PutChar( ',' ); + outputBuffer.PutChar( '\n' ); + + pCurrent = pCurrent->m_pNextPeer; + } + else + { + // We're done, or failing early + pCurrent = NULL; + } + } + + return bSuccess && outputBuffer.IsValid(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Emits KeyValues .vdf style formatted representation of values +//----------------------------------------------------------------------------- +bool CWebAPIValues::BEmitVDFRecursive( const CWebAPIValues *pCurrent, CUtlBuffer &outputBuffer, int nTabLevel, uint32 nArrayElement, size_t unMaxResultSize, bool bIncludeArrayElementName ) +{ + bool bSuccess = true; + + // We can have lots of peers, so this is an optimization to avoid tail recursion and resulting stack-overflows! + while( pCurrent ) + { + // don't let the buffer grow until it consumes all available memory + if( unMaxResultSize && (size_t)outputBuffer.TellMaxPut() > unMaxResultSize ) + { + return false; + } + if ( pCurrent->GetName() ) + { + for( int i=0; i < nTabLevel; ++i ) + outputBuffer.PutChar ( '\t' ); + + // Open node, special naming when inside arrays + if ( pCurrent->m_pParent && pCurrent->m_pParent->GetType() == k_EWebAPIValueType_NumericArray ) + { + CNumStr numStr( nArrayElement ); + numStr.AddQuotes(); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + else + { + EmitVDFString( outputBuffer, pCurrent->GetName() ); + } + + if ( pCurrent->m_eValueType == k_EWebAPIValueType_Object || pCurrent->m_eValueType == k_EWebAPIValueType_NumericArray ) + { + outputBuffer.PutChar( '\n' ); + + for( int i=0; i < nTabLevel; ++i ) + outputBuffer.PutChar ( '\t' ); + + outputBuffer.PutChar( '{' ); + outputBuffer.PutChar( '\n' ); + + if ( pCurrent->m_eValueType == k_EWebAPIValueType_NumericArray && bIncludeArrayElementName ) + { + for( int i=0; i < nTabLevel+1; ++i ) + outputBuffer.PutChar ( '\t' ); + + EmitVDFString( outputBuffer, (const char *)pCurrent->m_pStringBuffer->Base() + pCurrent->m_nArrayChildElementNamePos ); + outputBuffer.PutChar( '\n' ); + + for( int i=0; i < nTabLevel+1; ++i ) + outputBuffer.PutChar ( '\t' ); + + outputBuffer.PutChar( '{' ); + outputBuffer.PutChar( '\n' ); + } + + if ( pCurrent->m_pFirstChild ) + { + int nChildTabLevel = nTabLevel+1; + if ( pCurrent->m_eValueType == k_EWebAPIValueType_NumericArray && bIncludeArrayElementName ) + ++nChildTabLevel; + + bSuccess = BEmitVDFRecursive( pCurrent->m_pFirstChild, outputBuffer, nChildTabLevel, 0, unMaxResultSize, bIncludeArrayElementName ); + if ( !bSuccess ) + return false; + } + + if ( pCurrent->m_eValueType == k_EWebAPIValueType_NumericArray && bIncludeArrayElementName ) + { + outputBuffer.PutChar( '\n' ); + + for( int i=0; i < nTabLevel+1; ++i ) + outputBuffer.PutChar ( '\t' ); + + outputBuffer.PutChar( '}' ); + } + + outputBuffer.PutChar( '\n' ); + + for( int i=0; i < nTabLevel; ++i ) + outputBuffer.PutChar ( '\t' ); + + outputBuffer.PutChar( '}' ); + } + else + { + outputBuffer.PutChar( '\t' ); + + switch ( pCurrent->m_eValueType ) + { + case k_EWebAPIValueType_Int32: + { + CNumStr numStr( pCurrent->m_nValue ); + numStr.AddQuotes(); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + case k_EWebAPIValueType_Int64: + { + CNumStr numStr( pCurrent->m_lValue ); + numStr.AddQuotes(); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + case k_EWebAPIValueType_UInt32: + { + CNumStr numStr( pCurrent->m_unValue ); + numStr.AddQuotes(); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + case k_EWebAPIValueType_UInt64: + { + CNumStr numStr( pCurrent->m_ulValue ); + numStr.AddQuotes(); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + case k_EWebAPIValueType_Double: + { + CNumStr numStr( pCurrent->m_flValue ); + numStr.AddQuotes(); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + + case k_EWebAPIValueType_String: + { + EmitVDFString( outputBuffer, pCurrent->m_nStrValuePos >= 0 ? ( (const char *)pCurrent->m_pStringBuffer->Base() + pCurrent->m_nStrValuePos ) : NULL ); + } + break; + + case k_EWebAPIValueType_Bool: + { + if ( !pCurrent->m_bValue ) + outputBuffer.Put( "\"0\"", 3 ); + else + outputBuffer.Put( "\"1\"", 3 ); + } + break; + + case k_EWebAPIValueType_Null: + { + outputBuffer.Put ("\"\"", 2 ); + } + break; + + case k_EWebAPIValueType_BinaryBlob: + { + if ( pCurrent->m_BinaryValue.m_unBytes == 0 ) + outputBuffer.Put( "\"\"", 2 ); + else + { + CUtlMemory<char> buffEncoded; + DbgVerify( Base64EncodeIntoUTLMemory( (const uint8 *)pCurrent->m_pStringBuffer->Base() + pCurrent->m_BinaryValue.m_nDataPos, pCurrent->m_BinaryValue.m_unBytes, buffEncoded ) ); + EmitVDFString( outputBuffer, buffEncoded.Base() ); + } + } + break; + default: + break; + } + } + } + + // Now, check for any peers + if ( bSuccess && pCurrent->m_pNextPeer ) + { + outputBuffer.PutChar( '\n' ); + pCurrent = pCurrent->m_pNextPeer; + nArrayElement += 1; + } + else + { + // We're done, or failing early + pCurrent = NULL; + } + } + + return bSuccess && outputBuffer.IsValid(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Emits XML formatted representation of values +//----------------------------------------------------------------------------- +bool CWebAPIValues::BEmitXMLRecursive( const CWebAPIValues *pCurrent, CUtlBuffer &outputBuffer, int nTabLevel, size_t unMaxResultSize ) +{ + bool bSuccess = true; + + while( pCurrent ) + { + + // don't let the buffer grow until it consumes all available memory + if( unMaxResultSize && (size_t)outputBuffer.TellMaxPut() > unMaxResultSize ) + { + return false; + } + + // Can't emit nameless nodes in XML. Nodes should always have a name. + Assert( pCurrent->GetName() ); + if ( pCurrent->GetName() ) + { + for( int i=0; i < nTabLevel; ++i ) + outputBuffer.PutChar ( '\t' ); + + // Open node + outputBuffer.PutChar( '<' ); + EmitXMLString( outputBuffer, pCurrent->GetName() ); + outputBuffer.PutChar( '>' ); + + if ( pCurrent->m_eValueType == k_EWebAPIValueType_Object || pCurrent->m_eValueType == k_EWebAPIValueType_NumericArray ) + { + // First add any children + if ( pCurrent->m_pFirstChild ) + { + outputBuffer.PutChar( '\n' ); + + bSuccess = BEmitXMLRecursive( pCurrent->m_pFirstChild, outputBuffer, nTabLevel+1, unMaxResultSize ); + if ( !bSuccess ) + return false; + + outputBuffer.PutChar( '\n' ); + + // Return to correct tab level, for when we close element below + for( int i=0; i < nTabLevel; ++i ) + outputBuffer.PutChar ( '\t' ); + } + } + else + { + + switch ( pCurrent->m_eValueType ) + { + case k_EWebAPIValueType_Int32: + { + CNumStr numStr( pCurrent->m_nValue ); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + case k_EWebAPIValueType_Int64: + { + CNumStr numStr( pCurrent->m_lValue ); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + case k_EWebAPIValueType_UInt32: + { + CNumStr numStr( pCurrent->m_unValue ); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + case k_EWebAPIValueType_UInt64: + { + CNumStr numStr( pCurrent->m_ulValue ); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + case k_EWebAPIValueType_Double: + { + CNumStr numStr( pCurrent->m_flValue ); + outputBuffer.Put( numStr.String(), Q_strlen( numStr.String() ) ); + } + break; + + case k_EWebAPIValueType_String: + { + if ( pCurrent->m_nStrValuePos < 0 ) + outputBuffer.Put( "null", 4 ); + else + EmitXMLString( outputBuffer, (const char *)pCurrent->m_pStringBuffer->Base() + pCurrent->m_nStrValuePos ); + } + break; + + case k_EWebAPIValueType_Bool: + { + if ( !pCurrent->m_bValue ) + outputBuffer.Put( "false", 5 ); + else + outputBuffer.Put( "true", 4 ); + } + break; + + case k_EWebAPIValueType_Null: + { + outputBuffer.Put ("null", 4 ); + } + break; + + case k_EWebAPIValueType_BinaryBlob: + { + if ( pCurrent->m_BinaryValue.m_unBytes == 0 ) + outputBuffer.Put( "null", 4 ); + else + { + CUtlMemory<char> buffEncoded; + DbgVerify( Base64EncodeIntoUTLMemory( (const uint8 *)pCurrent->m_pStringBuffer->Base() + pCurrent->m_BinaryValue.m_nDataPos, pCurrent->m_BinaryValue.m_unBytes, buffEncoded ) ); + EmitXMLString( outputBuffer, buffEncoded.Base() ); + } + } + break; + default: + break; + } + } + } + + // Close element + outputBuffer.PutChar( '<' ); + outputBuffer.PutChar( '/' ); + EmitXMLString( outputBuffer, pCurrent->GetName() ); + outputBuffer.PutChar( '>' ); + + // Now, check for any peers + if ( bSuccess && pCurrent->m_pNextPeer ) + { + outputBuffer.PutChar( '\n' ); + pCurrent = pCurrent->m_pNextPeer; + } + else + { + // We're done, or failing early + pCurrent = NULL; + } + } + + return bSuccess && outputBuffer.IsValid(); +} + + +struct JSONParserContext_t +{ + CWebAPIValues *m_pCurrentNode; + CWebAPIValues *m_pRootNode; + CUtlString m_sChildName; + bool m_bIsKey; +}; + +static int JSONParserCallback(void* void_ctx, int type, const JSON_value* value) +{ + JSONParserContext_t *ctx = (JSONParserContext_t *)void_ctx; + CWebAPIValues *pCreatedNode = NULL; + switch(type) + { + case JSON_T_ARRAY_BEGIN: + // handle the root node + if( !ctx->m_pRootNode ) + { + Assert( !ctx->m_pCurrentNode ); + ctx->m_pRootNode = ctx->m_pCurrentNode = new CWebAPIValues( ctx->m_sChildName, "e" ); + break; + } + + if( ctx->m_pCurrentNode->IsArray() ) + { + Assert( ctx->m_sChildName.IsEmpty() ); + ctx->m_pCurrentNode = ctx->m_pCurrentNode->AddChildArrayToArray( "e" ); + } + else + { + Assert( !ctx->m_sChildName.IsEmpty() ); + ctx->m_pCurrentNode = ctx->m_pCurrentNode->CreateChildArray( ctx->m_sChildName, "e" ); + ctx->m_sChildName.Clear(); + } + break; + + case JSON_T_ARRAY_END: + ctx->m_pCurrentNode = ctx->m_pCurrentNode->GetParent(); + break; + + case JSON_T_OBJECT_BEGIN: + // this might be the start of the root object itself + if( !ctx->m_pRootNode ) + { + ctx->m_pRootNode = ctx->m_pCurrentNode = new CWebAPIValues( ctx->m_sChildName ); + break; + } + + if( ctx->m_pCurrentNode->IsArray() ) + { + Assert( ctx->m_sChildName.IsEmpty() ); + ctx->m_pCurrentNode = ctx->m_pCurrentNode->AddChildObjectToArray(); + } + else + { + Assert( !ctx->m_sChildName.IsEmpty() ); + ctx->m_pCurrentNode = ctx->m_pCurrentNode->CreateChildObject( ctx->m_sChildName ); + ctx->m_sChildName.Clear(); + } + break; + + case JSON_T_OBJECT_END: + ctx->m_pCurrentNode = ctx->m_pCurrentNode->GetParent(); + break; + + case JSON_T_KEY: + ctx->m_sChildName = value->vu.str.value; + break; + + case JSON_T_INTEGER: + case JSON_T_FLOAT: + case JSON_T_NULL: + case JSON_T_TRUE: + case JSON_T_FALSE: + case JSON_T_STRING: + // create the new node for this value + if( ctx->m_pCurrentNode->IsArray() ) + { + pCreatedNode = ctx->m_pCurrentNode->AddChildObjectToArray(); + } + else + { + pCreatedNode = ctx->m_pCurrentNode->CreateChildObject( ctx->m_sChildName ); + ctx->m_sChildName.Clear(); + } + + // set the actual value + switch( type ) + { + case JSON_T_INTEGER: + + // try to figure out what type to use + if( value->vu.integer_value >= 0 ) + { + // unsigned + if( value->vu.integer_value <= UINT_MAX ) + { + pCreatedNode->SetUInt32Value( (uint32)value->vu.integer_value ); + } + else + { + pCreatedNode->SetUInt64Value( (uint64)value->vu.integer_value ); + } + } + else + { + // signed + if( value->vu.integer_value >= INT_MIN ) + { + pCreatedNode->SetInt32Value( (int32)value->vu.integer_value ); + } + else + { + pCreatedNode->SetInt64Value( value->vu.integer_value ); + } + } + break; + + case JSON_T_FLOAT: + pCreatedNode->SetDoubleValue( value->vu.float_value ); + break; + + case JSON_T_NULL: + pCreatedNode->SetNullValue(); + break; + + case JSON_T_TRUE: + pCreatedNode->SetBoolValue( true ); + break; + + case JSON_T_FALSE: + pCreatedNode->SetBoolValue( false ); + break; + + case JSON_T_STRING: + pCreatedNode->SetStringValue( value->vu.str.value ); + break; + + } + break; + + default: + Assert( false ); + break; + } + + return 1; +} + + +// parses JSON into a tree of CWebAPIValues nodes with this as the root +CWebAPIValues *CWebAPIValues::ParseJSON( CUtlBuffer &inputBuffer ) +{ + // + // if there's nothing to parse, just early out + inputBuffer.EatWhiteSpace(); + if( inputBuffer.GetBytesRemaining() == 0 ) + return NULL; + + // if the first character is the start of a string, + // wrap the whole thing in an object so we can parse it. + // We'll unwrap it at the end + char cFirst = *(char *)inputBuffer.PeekGet(); + bool bWrapContent = cFirst == '\"'; + + JSON_config config; + + struct JSON_parser_struct* jc = NULL; + + init_JSON_config(&config); + + JSONParserContext_t context; + context.m_pCurrentNode = NULL; + context.m_pRootNode = NULL; + context.m_bIsKey = false; + + config.depth = 19; + config.callback = &JSONParserCallback; + config.allow_comments = 1; + config.handle_floats_manually = 0; + config.callback_ctx = &context; + config.malloc = malloc; + config.free = free; + + jc = new_JSON_parser(&config); + + bool bSuccess = true; + if( bWrapContent ) + JSON_parser_char( jc, '{' ); + while( inputBuffer.GetBytesRemaining( ) > 0 ) + { + if( !JSON_parser_char( jc, (unsigned char)inputBuffer.GetChar() ) ) + { + bSuccess = false; + break; + } + } + + if( bWrapContent ) + { + JSON_parser_char( jc, '}' ); + } + + if( bSuccess ) + { + if (!JSON_parser_done(jc)) + { + bSuccess = false; + } + } + + delete_JSON_parser(jc); + + // unwrap the root node + if( bWrapContent && bSuccess ) + { + CWebAPIValues *pWrapRoot = context.m_pRootNode; + if( !pWrapRoot ) + { + bSuccess = false; + } + else + { + CWebAPIValues *pRealRoot = pWrapRoot->GetFirstChild(); + if( !pRealRoot ) + { + bSuccess = false; + } + else + { + if( pRealRoot->GetNextChild() ) + { + bSuccess = false; + } + else + { + pWrapRoot->m_pFirstChild = NULL; + pRealRoot->m_pParent = NULL; + context.m_pRootNode = pRealRoot; + + delete pWrapRoot; + } + } + } + } + + if( bSuccess ) + { + return context.m_pRootNode; + } + else + { + delete context.m_pRootNode; + return NULL; + } +} + + +CWebAPIValues *CWebAPIValues::ParseJSON( const char *pchJSONString ) +{ + CUtlBuffer bufJSON( pchJSONString, Q_strlen( pchJSONString), CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY ); + return ParseJSON( bufJSON ); +} + + +void CWebAPIValues::CopyFrom( const CWebAPIValues *pSource ) +{ + switch( pSource->GetType() ) + { + case k_EWebAPIValueType_Int32: + SetInt32Value( pSource->GetInt32Value() ); + break; + case k_EWebAPIValueType_Int64: + SetInt64Value( pSource->GetInt64Value() ); + break; + case k_EWebAPIValueType_UInt32: + SetUInt32Value( pSource->GetUInt32Value() ); + break; + case k_EWebAPIValueType_UInt64: + SetUInt64Value( pSource->GetUInt64Value() ); + break; + case k_EWebAPIValueType_Double: + SetDoubleValue( pSource->GetDoubleValue() ); + break; + + case k_EWebAPIValueType_String: + { + CUtlString sValue; + pSource->GetStringValue( sValue ); + SetStringValue( sValue ); + } + break; + + case k_EWebAPIValueType_Bool: + SetBoolValue( pSource->GetBoolValue() ); + break; + + case k_EWebAPIValueType_Null: + SetNullValue( ); + break; + + case k_EWebAPIValueType_BinaryBlob: + { + CUtlBuffer bufValue; + pSource->GetBinaryValue( bufValue ); + SetBinaryValue( (uint8 *)bufValue.Base(), bufValue.TellMaxPut() ); + } + break; + + case k_EWebAPIValueType_Object: + { + ClearValue(); + m_eValueType = k_EWebAPIValueType_Object; + + const CWebAPIValues *pSourceChild = pSource->GetFirstChild(); + while( pSourceChild ) + { + CWebAPIValues *pChild; + if( pSourceChild->IsArray() ) + pChild = CreateChildArray( pSourceChild->GetName(), pSourceChild->GetElementName() ); + else + pChild = CreateChildObject( pSourceChild->GetName() ); + + pChild->CopyFrom( pSourceChild ); + pSourceChild = pSourceChild->GetNextChild(); + } + } + break; + + case k_EWebAPIValueType_NumericArray: + { + // the type should have been set when this node was first created + Assert( m_eValueType == k_EWebAPIValueType_NumericArray ); + m_eValueType = k_EWebAPIValueType_NumericArray; + + const CWebAPIValues *pSourceChild = pSource->GetFirstChild(); + while( pSourceChild ) + { + CWebAPIValues *pChild; + if( pSourceChild->IsArray() ) + pChild = AddChildArrayToArray( pSourceChild->GetElementName() ); + else + pChild = AddChildObjectToArray( ); + + pChild->CopyFrom( pSourceChild ); + pSourceChild = pSourceChild->GetNextChild(); + } + } + break; + + default: + AssertMsg( false, "Unknown type in CWebAPIValues::CopyFrom" ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Fills a WebAPI key values from a corresponding protobuf message. +//----------------------------------------------------------------------------- +bool ProtoBufHelper::RecursiveAddProtoBufToWebAPIValues( CWebAPIValues *pWebAPIRoot, const ::google::protobuf::Message & msg ) +{ + using ::google::protobuf::FieldDescriptor; + const ::google::protobuf::Descriptor *pDescriptor = msg.GetDescriptor(); + const ::google::protobuf::Reflection *pReflection = msg.GetReflection(); + + for ( int iField = 0; iField < pDescriptor->field_count(); iField++ ) + { + const ::google::protobuf::FieldDescriptor *pField = pDescriptor->field( iField ); + const char *pFieldName = pField->name().c_str(); + + if ( pField->is_repeated() ) + { + if ( pReflection->FieldSize( msg, pField ) == 0 ) + { + continue; // No need to create the array if it is empty + // If the field has been disabled externally, it has been cleared already (and thus will be skipped) + } + + // bugbug: Is there a way to get this from the google API? + const char *pchTypeName = "unknown_type"; + switch ( pField->cpp_type() ) + { + case FieldDescriptor::CPPTYPE_INT32: pchTypeName = "int32"; break; + case FieldDescriptor::CPPTYPE_INT64: pchTypeName = "int64"; break; + case FieldDescriptor::CPPTYPE_UINT32: pchTypeName = "uint32"; break; + case FieldDescriptor::CPPTYPE_DOUBLE: pchTypeName = "double"; break; + case FieldDescriptor::CPPTYPE_FLOAT: pchTypeName = "float"; break; + case FieldDescriptor::CPPTYPE_BOOL: pchTypeName = "bool"; break; + case FieldDescriptor::CPPTYPE_ENUM: pchTypeName = "enum"; break; + case FieldDescriptor::CPPTYPE_STRING: + { + if ( pField->type() == FieldDescriptor::TYPE_STRING ) + { + pchTypeName = "string"; + } + else + { + AssertMsg1( pField->type() == FieldDescriptor::TYPE_BYTES, "Unrecognized field type: %d", pField->type() ); + pchTypeName = "bytes"; + } + break; + } + case FieldDescriptor::CPPTYPE_MESSAGE: pchTypeName = "message"; break; + } + + CWebAPIValues *pContainer = pWebAPIRoot->CreateChildArray( pFieldName, pchTypeName ); + for ( int iRepeated = 0; iRepeated < pReflection->FieldSize( msg, pField ); iRepeated++ ) + { + switch ( pField->cpp_type() ) + { + case FieldDescriptor::CPPTYPE_INT32: pContainer->SetChildInt32Value( NULL, pReflection->GetRepeatedInt32( msg, pField, iRepeated ) ); break; + case FieldDescriptor::CPPTYPE_INT64: pContainer->SetChildInt64Value( NULL, pReflection->GetRepeatedInt64( msg, pField, iRepeated ) ); break; + case FieldDescriptor::CPPTYPE_UINT32: pContainer->SetChildUInt32Value( NULL, pReflection->GetRepeatedUInt32( msg, pField, iRepeated ) ); break; + case FieldDescriptor::CPPTYPE_UINT64: pContainer->SetChildUInt64Value( NULL, pReflection->GetRepeatedUInt64( msg, pField, iRepeated ) ); break; + case FieldDescriptor::CPPTYPE_DOUBLE: pContainer->SetChildDoubleValue( NULL, pReflection->GetRepeatedDouble( msg, pField, iRepeated ) ); break; + case FieldDescriptor::CPPTYPE_FLOAT: pContainer->SetChildDoubleValue( NULL, pReflection->GetRepeatedFloat( msg, pField, iRepeated ) ); break; + case FieldDescriptor::CPPTYPE_BOOL: pContainer->SetChildBoolValue( NULL, pReflection->GetRepeatedBool( msg, pField, iRepeated ) ); break; + case FieldDescriptor::CPPTYPE_ENUM: pContainer->SetChildInt32Value( NULL, pReflection->GetRepeatedEnum( msg, pField, iRepeated )->number() ); break; + case FieldDescriptor::CPPTYPE_STRING: + { + const std::string &strValue = pReflection->GetRepeatedString( msg, pField, iRepeated ); + if ( pField->type() == FieldDescriptor::TYPE_STRING ) + { + pContainer->SetChildStringValue( NULL, strValue.c_str() ); + } + else + { + // Binary blobs are automatically encoded in Base64 when converted to string by Web request + pContainer->SetChildBinaryValue( NULL, (const uint8 *)strValue.c_str(), strValue.size() ); + } + break; + } + case FieldDescriptor::CPPTYPE_MESSAGE: + { + const ::google::protobuf::Message &subMsg = pReflection->GetRepeatedMessage( msg, pField, iRepeated ); + CWebAPIValues *pChild = pContainer->CreateChildObject( NULL ); + if ( RecursiveAddProtoBufToWebAPIValues( pChild, subMsg ) == false ) + { + return false; + } + break; + } + default: + AssertMsg1( false, "Unknown cpp_type %d", pField->cpp_type() ); + return false; + } + } + } + else + { + if ( ( ( pReflection->HasField( msg, pField ) == false ) && ( pField->has_default_value() == false ) ) + // || pField->options().GetExtension( is_field_disabled_externally ) // Steam vesion supports this + ) + { + continue; // If no field set and no default value set, there is no need to send the field + // Or it is externally disabled (it has been cleared already but there still may be a default value set) + } + + switch ( pField->cpp_type() ) + { + case FieldDescriptor::CPPTYPE_INT32: pWebAPIRoot->SetChildInt32Value( pFieldName, pReflection->GetInt32( msg, pField ) ); break; + case FieldDescriptor::CPPTYPE_INT64: pWebAPIRoot->SetChildInt64Value( pFieldName, pReflection->GetInt64( msg, pField ) ); break; + case FieldDescriptor::CPPTYPE_UINT32: pWebAPIRoot->SetChildUInt32Value( pFieldName, pReflection->GetUInt32( msg, pField ) ); break; + case FieldDescriptor::CPPTYPE_UINT64: pWebAPIRoot->SetChildUInt64Value( pFieldName, pReflection->GetUInt64( msg, pField ) ); break; + case FieldDescriptor::CPPTYPE_DOUBLE: pWebAPIRoot->SetChildDoubleValue( pFieldName, pReflection->GetDouble( msg, pField ) ); break; + case FieldDescriptor::CPPTYPE_FLOAT: pWebAPIRoot->SetChildDoubleValue( pFieldName, pReflection->GetFloat( msg, pField ) ); break; + case FieldDescriptor::CPPTYPE_BOOL: pWebAPIRoot->SetChildBoolValue( pFieldName, pReflection->GetBool( msg, pField ) ); break; + case FieldDescriptor::CPPTYPE_ENUM: pWebAPIRoot->SetChildInt32Value( pFieldName, pReflection->GetEnum( msg, pField )->number() ); break; + case FieldDescriptor::CPPTYPE_STRING: + { + const std::string &strValue = pReflection->GetString( msg, pField ); + if ( pField->type() == FieldDescriptor::TYPE_STRING ) + { + pWebAPIRoot->SetChildStringValue( pFieldName, strValue.c_str() ); + } + else + { + AssertMsg1( pField->type() == FieldDescriptor::TYPE_BYTES, "Unrecognized field type: %d", pField->type() ); + // Binary blobs are automatically encoded in Base64 when converted to string by Web request + pWebAPIRoot->SetChildBinaryValue( pFieldName, (const uint8 *)strValue.c_str(), strValue.size() ); + } + break; + } + case FieldDescriptor::CPPTYPE_MESSAGE: + { + CWebAPIValues *pChild = pWebAPIRoot->CreateChildObject( pFieldName ); +#undef GetMessage // Work around unfortunate Microsoft macro + const ::google::protobuf::Message &subMsg = pReflection->GetMessage( msg, pField ); +#ifdef UNICODE +#define GetMessage GetMessageW +#else +#define GetMessage GetMessageA +#endif // !UNICODE + if ( RecursiveAddProtoBufToWebAPIValues( pChild, subMsg ) == false ) + { + return false; + } + break; + } + default: + AssertMsg1( false, "Unknown cpp_type %d", pField->cpp_type() ); + return false; + } + } + } + return true; +} + |