//========= 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 = "\n"; outputBuffer.Put( pchProlog, Q_strlen( pchProlog ) ); outputBuffer.Put( "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 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 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 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; }