You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4338 lines
111 KiB
4338 lines
111 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: String Tools |
|
// |
|
//===========================================================================// |
|
|
|
// These are redefined in the project settings to prevent anyone from using them. |
|
// We in this module are of a higher caste and thus are privileged in their use. |
|
#ifdef strncpy |
|
#undef strncpy |
|
#endif |
|
|
|
#ifdef _snprintf |
|
#undef _snprintf |
|
#endif |
|
|
|
#if defined( sprintf ) |
|
#undef sprintf |
|
#endif |
|
|
|
#if defined( vsprintf ) |
|
#undef vsprintf |
|
#endif |
|
|
|
#ifdef _vsnprintf |
|
#ifdef _WIN32 |
|
#undef _vsnprintf |
|
#endif |
|
#endif |
|
|
|
#ifdef vsnprintf |
|
#ifndef _WIN32 |
|
#undef vsnprintf |
|
#endif |
|
#endif |
|
|
|
#if defined( strcat ) |
|
#undef strcat |
|
#endif |
|
|
|
#ifdef strncat |
|
#undef strncat |
|
#endif |
|
|
|
// NOTE: I have to include stdio + stdarg first so vsnprintf gets compiled in |
|
#include <stdio.h> |
|
#include <stdarg.h> |
|
|
|
#ifdef POSIX |
|
#ifdef ANDROID |
|
#include <../thirdparty/libiconv-1.14/include/iconv.h> |
|
#else |
|
#include <iconv.h> |
|
#endif |
|
#include <ctype.h> |
|
#include <unistd.h> |
|
#include <stdlib.h> |
|
#define _getcwd getcwd |
|
#elif _WIN32 |
|
#include <direct.h> |
|
#if !defined( _X360 ) |
|
#define WIN32_LEAN_AND_MEAN |
|
#include <windows.h> |
|
#endif |
|
#endif |
|
|
|
#ifdef _WIN32 |
|
#ifndef CP_UTF8 |
|
#define CP_UTF8 65001 |
|
#endif |
|
#endif |
|
#include "tier0/dbg.h" |
|
#include "tier1/strtools.h" |
|
#include <string.h> |
|
#include <stdlib.h> |
|
#include <time.h> |
|
#include "tier0/basetypes.h" |
|
#include "tier1/utldict.h" |
|
#include "tier1/utlbuffer.h" |
|
#include "tier1/utlstring.h" |
|
#include "tier1/fmtstr.h" |
|
#if defined( _X360 ) |
|
#include "xbox/xbox_win32stubs.h" |
|
#endif |
|
#include "tier0/memdbgon.h" |
|
|
|
static int FastToLower( char c ) |
|
{ |
|
int i = (unsigned char) c; |
|
if ( i < 0x80 ) |
|
{ |
|
// Brutally fast branchless ASCII tolower(): |
|
i += (((('A'-1) - i) & (i - ('Z'+1))) >> 26) & 0x20; |
|
} |
|
else |
|
{ |
|
i += isupper( i ) ? 0x20 : 0; |
|
} |
|
return i; |
|
} |
|
|
|
void _V_memset (const char* file, int line, void *dest, int fill, int count) |
|
{ |
|
Assert( count >= 0 ); |
|
AssertValidWritePtr( dest, count ); |
|
|
|
memset(dest,fill,count); |
|
} |
|
|
|
void _V_memcpy (const char* file, int line, void *dest, const void *src, int count) |
|
{ |
|
Assert( count >= 0 ); |
|
AssertValidReadPtr( src, count ); |
|
AssertValidWritePtr( dest, count ); |
|
|
|
memcpy( dest, src, count ); |
|
} |
|
|
|
void _V_memmove(const char* file, int line, void *dest, const void *src, int count) |
|
{ |
|
Assert( count >= 0 ); |
|
AssertValidReadPtr( src, count ); |
|
AssertValidWritePtr( dest, count ); |
|
|
|
memmove( dest, src, count ); |
|
} |
|
|
|
int _V_memcmp (const char* file, int line, const void *m1, const void *m2, int count) |
|
{ |
|
Assert( count >= 0 ); |
|
AssertValidReadPtr( m1, count ); |
|
AssertValidReadPtr( m2, count ); |
|
|
|
return memcmp( m1, m2, count ); |
|
} |
|
|
|
int _V_strlen(const char* file, int line, const char *str) |
|
{ |
|
AssertValidStringPtr(str); |
|
return strlen( str ); |
|
} |
|
|
|
void _V_strcpy (const char* file, int line, char *dest, const char *src) |
|
{ |
|
AssertValidWritePtr(dest); |
|
AssertValidStringPtr(src); |
|
|
|
strcpy( dest, src ); |
|
} |
|
|
|
int _V_wcslen(const char* file, int line, const wchar_t *pwch) |
|
{ |
|
return wcslen( pwch ); |
|
} |
|
|
|
char *_V_strrchr(const char* file, int line, const char *s, char c) |
|
{ |
|
AssertValidStringPtr( s ); |
|
int len = V_strlen(s); |
|
s += len; |
|
while (len--) |
|
if (*--s == c) return (char *)s; |
|
return 0; |
|
} |
|
|
|
int _V_strcmp (const char* file, int line, const char *s1, const char *s2) |
|
{ |
|
AssertValidStringPtr( s1 ); |
|
AssertValidStringPtr( s2 ); |
|
|
|
return strcmp( s1, s2 ); |
|
} |
|
|
|
int _V_wcscmp (const char* file, int line, const wchar_t *s1, const wchar_t *s2) |
|
{ |
|
AssertValidReadPtr( s1 ); |
|
AssertValidReadPtr( s2 ); |
|
|
|
while ( *s1 == *s2 ) |
|
{ |
|
if ( !*s1 ) |
|
return 0; // strings are equal |
|
|
|
s1++; |
|
s2++; |
|
} |
|
|
|
return *s1 > *s2 ? 1 : -1; // strings not equal |
|
} |
|
|
|
|
|
char *_V_strstr(const char* file, int line, const char *s1, const char *search ) |
|
{ |
|
AssertValidStringPtr( s1 ); |
|
AssertValidStringPtr( search ); |
|
|
|
#if defined( _X360 ) |
|
return (char *)strstr( (char *)s1, search ); |
|
#else |
|
return (char *)strstr( s1, search ); |
|
#endif |
|
} |
|
|
|
wchar_t *_V_wcsupr (const char* file, int line, wchar_t *start) |
|
{ |
|
return _wcsupr( start ); |
|
} |
|
|
|
|
|
wchar_t *_V_wcslower (const char* file, int line, wchar_t *start) |
|
{ |
|
return _wcslwr(start); |
|
} |
|
|
|
|
|
|
|
char *V_strupr( char *start ) |
|
{ |
|
unsigned char *str = (unsigned char*)start; |
|
while( *str ) |
|
{ |
|
if ( (unsigned char)(*str - 'a') <= ('z' - 'a') ) |
|
*str -= 'a' - 'A'; |
|
else if ( (unsigned char)*str >= 0x80 ) // non-ascii, fall back to CRT |
|
*str = toupper( *str ); |
|
str++; |
|
} |
|
return start; |
|
} |
|
|
|
char *V_strlower( char *start ) |
|
{ |
|
unsigned char *str = (unsigned char*)start; |
|
while( *str ) |
|
{ |
|
if ( (unsigned char)(*str - 'A') <= ('Z' - 'A') ) |
|
*str += 'a' - 'A'; |
|
else if ( (unsigned char)*str >= 0x80 ) // non-ascii, fall back to CRT |
|
*str = tolower( *str ); |
|
str++; |
|
} |
|
return start; |
|
} |
|
|
|
char *V_strnlwr(char *s, size_t count) |
|
{ |
|
// Assert( count >= 0 ); tautology since size_t is unsigned |
|
AssertValidStringPtr( s, count ); |
|
|
|
char* pRet = s; |
|
if ( !s || !count ) |
|
return s; |
|
|
|
while ( -- count > 0 ) |
|
{ |
|
if ( !*s ) |
|
return pRet; // reached end of string |
|
|
|
*s = tolower( *s ); |
|
++s; |
|
} |
|
|
|
*s = 0; // null-terminate original string at "count-1" |
|
return pRet; |
|
} |
|
|
|
int V_stricmp( const char *str1, const char *str2 ) |
|
{ |
|
// It is not uncommon to compare a string to itself. See |
|
// VPanelWrapper::GetPanel which does this a lot. Since stricmp |
|
// is expensive and pointer comparison is cheap, this simple test |
|
// can save a lot of cycles, and cache pollution. |
|
if ( str1 == str2 ) |
|
{ |
|
return 0; |
|
} |
|
const unsigned char *s1 = (const unsigned char*)str1; |
|
const unsigned char *s2 = (const unsigned char*)str2; |
|
for ( ; *s1; ++s1, ++s2 ) |
|
{ |
|
if ( *s1 != *s2 ) |
|
{ |
|
// in ascii char set, lowercase = uppercase | 0x20 |
|
unsigned char c1 = *s1 | 0x20; |
|
unsigned char c2 = *s2 | 0x20; |
|
if ( c1 != c2 || (unsigned char)(c1 - 'a') > ('z' - 'a') ) |
|
{ |
|
// if non-ascii mismatch, fall back to CRT for locale |
|
if ( (c1 | c2) >= 0x80 ) return stricmp( (const char*)s1, (const char*)s2 ); |
|
// ascii mismatch. only use the | 0x20 value if alphabetic. |
|
if ((unsigned char)(c1 - 'a') > ('z' - 'a')) c1 = *s1; |
|
if ((unsigned char)(c2 - 'a') > ('z' - 'a')) c2 = *s2; |
|
return c1 > c2 ? 1 : -1; |
|
} |
|
} |
|
} |
|
return *s2 ? -1 : 0; |
|
} |
|
|
|
int V_strnicmp( const char *str1, const char *str2, int n ) |
|
{ |
|
const unsigned char *s1 = (const unsigned char*)str1; |
|
const unsigned char *s2 = (const unsigned char*)str2; |
|
for ( ; n > 0 && *s1; --n, ++s1, ++s2 ) |
|
{ |
|
if ( *s1 != *s2 ) |
|
{ |
|
// in ascii char set, lowercase = uppercase | 0x20 |
|
unsigned char c1 = *s1 | 0x20; |
|
unsigned char c2 = *s2 | 0x20; |
|
if ( c1 != c2 || (unsigned char)(c1 - 'a') > ('z' - 'a') ) |
|
{ |
|
// if non-ascii mismatch, fall back to CRT for locale |
|
if ( (c1 | c2) >= 0x80 ) return strnicmp( (const char*)s1, (const char*)s2, n ); |
|
// ascii mismatch. only use the | 0x20 value if alphabetic. |
|
if ((unsigned char)(c1 - 'a') > ('z' - 'a')) c1 = *s1; |
|
if ((unsigned char)(c2 - 'a') > ('z' - 'a')) c2 = *s2; |
|
return c1 > c2 ? 1 : -1; |
|
} |
|
} |
|
} |
|
return (n > 0 && *s2) ? -1 : 0; |
|
} |
|
|
|
int V_strncmp( const char *s1, const char *s2, int count ) |
|
{ |
|
Assert( count >= 0 ); |
|
AssertValidStringPtr( s1, count ); |
|
AssertValidStringPtr( s2, count ); |
|
|
|
while ( count > 0 ) |
|
{ |
|
if ( *s1 != *s2 ) |
|
return (unsigned char)*s1 < (unsigned char)*s2 ? -1 : 1; // string different |
|
if ( *s1 == '\0' ) |
|
return 0; // null terminator hit - strings the same |
|
s1++; |
|
s2++; |
|
count--; |
|
} |
|
|
|
return 0; // count characters compared the same |
|
} |
|
|
|
|
|
const char *StringAfterPrefix( const char *str, const char *prefix ) |
|
{ |
|
AssertValidStringPtr( str ); |
|
AssertValidStringPtr( prefix ); |
|
do |
|
{ |
|
if ( !*prefix ) |
|
return str; |
|
} |
|
while ( FastToLower( *str++ ) == FastToLower( *prefix++ ) ); |
|
return NULL; |
|
} |
|
|
|
const char *StringAfterPrefixCaseSensitive( const char *str, const char *prefix ) |
|
{ |
|
AssertValidStringPtr( str ); |
|
AssertValidStringPtr( prefix ); |
|
do |
|
{ |
|
if ( !*prefix ) |
|
return str; |
|
} |
|
while ( *str++ == *prefix++ ); |
|
return NULL; |
|
} |
|
|
|
|
|
int64 V_atoi64( const char *str ) |
|
{ |
|
AssertValidStringPtr( str ); |
|
|
|
int64 val; |
|
int64 sign; |
|
int64 c; |
|
|
|
Assert( str ); |
|
if (*str == '-') |
|
{ |
|
sign = -1; |
|
str++; |
|
} |
|
else if (*str == '+') |
|
{ |
|
sign = 1; |
|
str++; |
|
} |
|
else |
|
{ |
|
sign = 1; |
|
} |
|
|
|
val = 0; |
|
|
|
// |
|
// check for hex |
|
// |
|
if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) |
|
{ |
|
str += 2; |
|
while (1) |
|
{ |
|
c = *str++; |
|
if (c >= '0' && c <= '9') |
|
val = (val<<4) + c - '0'; |
|
else if (c >= 'a' && c <= 'f') |
|
val = (val<<4) + c - 'a' + 10; |
|
else if (c >= 'A' && c <= 'F') |
|
val = (val<<4) + c - 'A' + 10; |
|
else |
|
return val*sign; |
|
} |
|
} |
|
|
|
// |
|
// check for character |
|
// |
|
if (str[0] == '\'') |
|
{ |
|
return sign * str[1]; |
|
} |
|
|
|
// |
|
// assume decimal |
|
// |
|
while (1) |
|
{ |
|
c = *str++; |
|
if (c <'0' || c > '9') |
|
return val*sign; |
|
val = val*10 + c - '0'; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
uint64 V_atoui64( const char *str ) |
|
{ |
|
AssertValidStringPtr( str ); |
|
|
|
uint64 val; |
|
uint64 c; |
|
|
|
Assert( str ); |
|
|
|
val = 0; |
|
|
|
// |
|
// check for hex |
|
// |
|
if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) |
|
{ |
|
str += 2; |
|
while (1) |
|
{ |
|
c = *str++; |
|
if (c >= '0' && c <= '9') |
|
val = (val<<4) + c - '0'; |
|
else if (c >= 'a' && c <= 'f') |
|
val = (val<<4) + c - 'a' + 10; |
|
else if (c >= 'A' && c <= 'F') |
|
val = (val<<4) + c - 'A' + 10; |
|
else |
|
return val; |
|
} |
|
} |
|
|
|
// |
|
// check for character |
|
// |
|
if (str[0] == '\'') |
|
{ |
|
return str[1]; |
|
} |
|
|
|
// |
|
// assume decimal |
|
// |
|
while (1) |
|
{ |
|
c = *str++; |
|
if (c <'0' || c > '9') |
|
return val; |
|
val = val*10 + c - '0'; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int V_atoi( const char *str ) |
|
{ |
|
return (int)V_atoi64( str ); |
|
} |
|
|
|
float V_atof (const char *str) |
|
{ |
|
AssertValidStringPtr( str ); |
|
double val; |
|
int sign; |
|
int c; |
|
int decimal, total; |
|
|
|
if (*str == '-') |
|
{ |
|
sign = -1; |
|
str++; |
|
} |
|
else if (*str == '+') |
|
{ |
|
sign = 1; |
|
str++; |
|
} |
|
else |
|
{ |
|
sign = 1; |
|
} |
|
|
|
val = 0; |
|
|
|
// |
|
// check for hex |
|
// |
|
if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) |
|
{ |
|
str += 2; |
|
while (1) |
|
{ |
|
c = *str++; |
|
if (c >= '0' && c <= '9') |
|
val = (val*16) + c - '0'; |
|
else if (c >= 'a' && c <= 'f') |
|
val = (val*16) + c - 'a' + 10; |
|
else if (c >= 'A' && c <= 'F') |
|
val = (val*16) + c - 'A' + 10; |
|
else |
|
return val*sign; |
|
} |
|
} |
|
|
|
// |
|
// check for character |
|
// |
|
if (str[0] == '\'') |
|
{ |
|
return sign * str[1]; |
|
} |
|
|
|
// |
|
// assume decimal |
|
// |
|
decimal = -1; |
|
total = 0; |
|
int exponent = 0; |
|
while (1) |
|
{ |
|
c = *str++; |
|
if (c == '.') |
|
{ |
|
if ( decimal != -1 ) |
|
{ |
|
break; |
|
} |
|
|
|
decimal = total; |
|
continue; |
|
} |
|
if (c <'0' || c > '9') |
|
{ |
|
if ( c == 'e' || c == 'E' ) |
|
{ |
|
exponent = V_atoi(str); |
|
} |
|
break; |
|
} |
|
val = val*10 + c - '0'; |
|
total++; |
|
} |
|
|
|
if ( exponent != 0 ) |
|
{ |
|
val *= pow( 10.0, exponent ); |
|
} |
|
if (decimal == -1) |
|
return val*sign; |
|
while (total > decimal) |
|
{ |
|
val /= 10; |
|
total--; |
|
} |
|
|
|
return val*sign; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Normalizes a float string in place. |
|
// |
|
// (removes leading zeros, trailing zeros after the decimal point, and the decimal point itself where possible) |
|
//----------------------------------------------------------------------------- |
|
void V_normalizeFloatString( char* pFloat ) |
|
{ |
|
// If we have a decimal point, remove trailing zeroes: |
|
if( strchr( pFloat,'.' ) ) |
|
{ |
|
int len = V_strlen(pFloat); |
|
|
|
while( len > 1 && pFloat[len - 1] == '0' ) |
|
{ |
|
pFloat[len - 1] = '\0'; |
|
len--; |
|
} |
|
|
|
if( len > 1 && pFloat[ len - 1 ] == '.' ) |
|
{ |
|
pFloat[len - 1] = '\0'; |
|
len--; |
|
} |
|
} |
|
|
|
// TODO: Strip leading zeros |
|
|
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Finds a string in another string with a case insensitive test |
|
//----------------------------------------------------------------------------- |
|
char const* V_stristr( char const* pStr, char const* pSearch ) |
|
{ |
|
AssertValidStringPtr(pStr); |
|
AssertValidStringPtr(pSearch); |
|
|
|
if (!pStr || !pSearch) |
|
return 0; |
|
|
|
char const* pLetter = pStr; |
|
|
|
// Check the entire string |
|
while (*pLetter != 0) |
|
{ |
|
// Skip over non-matches |
|
if (FastToLower((unsigned char)*pLetter) == FastToLower((unsigned char)*pSearch)) |
|
{ |
|
// Check for match |
|
char const* pMatch = pLetter + 1; |
|
char const* pTest = pSearch + 1; |
|
while (*pTest != 0) |
|
{ |
|
// We've run off the end; don't bother. |
|
if (*pMatch == 0) |
|
return 0; |
|
|
|
if (FastToLower((unsigned char)*pMatch) != FastToLower((unsigned char)*pTest)) |
|
break; |
|
|
|
++pMatch; |
|
++pTest; |
|
} |
|
|
|
// Found a match! |
|
if (*pTest == 0) |
|
return pLetter; |
|
} |
|
|
|
++pLetter; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
char* V_stristr( char* pStr, char const* pSearch ) |
|
{ |
|
AssertValidStringPtr( pStr ); |
|
AssertValidStringPtr( pSearch ); |
|
|
|
return (char*)V_stristr( (char const*)pStr, pSearch ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Finds a string in another string with a case insensitive test w/ length validation |
|
//----------------------------------------------------------------------------- |
|
|
|
char const* V_strnistr( char const* pStr, char const* pSearch, int n ) |
|
{ |
|
AssertValidStringPtr(pStr); |
|
AssertValidStringPtr(pSearch); |
|
|
|
if (!pStr || !pSearch) |
|
return 0; |
|
|
|
char const* pLetter = pStr; |
|
|
|
// Check the entire string |
|
while (*pLetter != 0) |
|
{ |
|
if ( n <= 0 ) |
|
return 0; |
|
|
|
// Skip over non-matches |
|
if (FastToLower(*pLetter) == FastToLower(*pSearch)) |
|
{ |
|
int n1 = n - 1; |
|
|
|
// Check for match |
|
char const* pMatch = pLetter + 1; |
|
char const* pTest = pSearch + 1; |
|
while (*pTest != 0) |
|
{ |
|
if ( n1 <= 0 ) |
|
return 0; |
|
|
|
// We've run off the end; don't bother. |
|
if (*pMatch == 0) |
|
return 0; |
|
|
|
if (FastToLower(*pMatch) != FastToLower(*pTest)) |
|
break; |
|
|
|
++pMatch; |
|
++pTest; |
|
--n1; |
|
} |
|
|
|
// Found a match! |
|
if (*pTest == 0) |
|
return pLetter; |
|
} |
|
|
|
++pLetter; |
|
--n; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
const char* V_strnchr( const char* pStr, char c, int n ) |
|
{ |
|
char const* pLetter = pStr; |
|
char const* pLast = pStr + n; |
|
|
|
// Check the entire string |
|
while ( (pLetter < pLast) && (*pLetter != 0) ) |
|
{ |
|
if (*pLetter == c) |
|
return pLetter; |
|
++pLetter; |
|
} |
|
return NULL; |
|
} |
|
|
|
void V_strncpy( char *pDest, char const *pSrc, int maxLen ) |
|
{ |
|
Assert( maxLen >= sizeof( *pDest ) ); |
|
AssertValidWritePtr( pDest, maxLen ); |
|
AssertValidStringPtr( pSrc ); |
|
|
|
strncpy( pDest, pSrc, maxLen ); |
|
if ( maxLen > 0 ) |
|
{ |
|
pDest[maxLen-1] = 0; |
|
} |
|
} |
|
|
|
// warning C6053: Call to 'wcsncpy' might not zero-terminate string 'pDest' |
|
// warning C6059: Incorrect length parameter in call to 'strncat'. Pass the number of remaining characters, not the buffer size of 'argument 1' |
|
// warning C6386: Buffer overrun: accessing 'argument 1', the writable size is 'destBufferSize' bytes, but '1000' bytes might be written |
|
// These warnings were investigated through code inspection and writing of tests and they are |
|
// believed to all be spurious. |
|
#ifdef _PREFAST_ |
|
#pragma warning( push ) |
|
#pragma warning( disable : 6053 6059 6386 ) |
|
#endif |
|
|
|
void V_wcsncpy( wchar_t *pDest, wchar_t const *pSrc, int maxLenInBytes ) |
|
{ |
|
Assert( maxLenInBytes >= sizeof( *pDest ) ); |
|
AssertValidWritePtr( pDest, maxLenInBytes ); |
|
AssertValidReadPtr( pSrc ); |
|
|
|
int maxLen = maxLenInBytes / sizeof(wchar_t); |
|
|
|
wcsncpy( pDest, pSrc, maxLen ); |
|
if( maxLen ) |
|
{ |
|
pDest[maxLen-1] = 0; |
|
} |
|
} |
|
|
|
|
|
|
|
int V_snwprintf( wchar_t *pDest, int maxLen, const wchar_t *pFormat, ... ) |
|
{ |
|
Assert( maxLen > 0 ); |
|
AssertValidWritePtr( pDest, maxLen ); |
|
AssertValidReadPtr( pFormat ); |
|
|
|
va_list marker; |
|
|
|
va_start( marker, pFormat ); |
|
#ifdef _WIN32 |
|
int len = _vsnwprintf( pDest, maxLen, pFormat, marker ); |
|
#elif POSIX |
|
int len = vswprintf( pDest, maxLen, pFormat, marker ); |
|
#else |
|
#error "define vsnwprintf type." |
|
#endif |
|
va_end( marker ); |
|
|
|
// Len > maxLen represents an overflow on POSIX, < 0 is an overflow on windows |
|
if( len < 0 || len >= maxLen ) |
|
{ |
|
len = maxLen; |
|
pDest[maxLen-1] = 0; |
|
} |
|
|
|
return len; |
|
} |
|
|
|
|
|
int V_vsnwprintf( wchar_t *pDest, int maxLen, const wchar_t *pFormat, va_list params ) |
|
{ |
|
Assert( maxLen > 0 ); |
|
|
|
#ifdef _WIN32 |
|
int len = _vsnwprintf( pDest, maxLen, pFormat, params ); |
|
#elif POSIX |
|
int len = vswprintf( pDest, maxLen, pFormat, params ); |
|
#else |
|
#error "define vsnwprintf type." |
|
#endif |
|
|
|
// Len < 0 represents an overflow |
|
// Len == maxLen represents exactly fitting with no NULL termination |
|
// Len >= maxLen represents overflow on POSIX |
|
if ( len < 0 || len >= maxLen ) |
|
{ |
|
len = maxLen; |
|
pDest[maxLen-1] = 0; |
|
} |
|
|
|
return len; |
|
} |
|
|
|
|
|
int V_snprintf( char *pDest, int maxLen, char const *pFormat, ... ) |
|
{ |
|
Assert( maxLen > 0 ); |
|
AssertValidWritePtr( pDest, maxLen ); |
|
AssertValidStringPtr( pFormat ); |
|
|
|
va_list marker; |
|
|
|
va_start( marker, pFormat ); |
|
#ifdef _WIN32 |
|
int len = _vsnprintf( pDest, maxLen, pFormat, marker ); |
|
#elif POSIX |
|
int len = vsnprintf( pDest, maxLen, pFormat, marker ); |
|
#else |
|
#error "define vsnprintf type." |
|
#endif |
|
va_end( marker ); |
|
|
|
// Len > maxLen represents an overflow on POSIX, < 0 is an overflow on windows |
|
if( len < 0 || len >= maxLen ) |
|
{ |
|
len = maxLen; |
|
pDest[maxLen-1] = 0; |
|
} |
|
|
|
return len; |
|
} |
|
|
|
|
|
int V_vsnprintf( char *pDest, int maxLen, char const *pFormat, va_list params ) |
|
{ |
|
Assert( maxLen > 0 ); |
|
AssertValidWritePtr( pDest, maxLen ); |
|
AssertValidStringPtr( pFormat ); |
|
|
|
int len = _vsnprintf( pDest, maxLen, pFormat, params ); |
|
|
|
// Len > maxLen represents an overflow on POSIX, < 0 is an overflow on windows |
|
if( len < 0 || len >= maxLen ) |
|
{ |
|
len = maxLen; |
|
pDest[maxLen-1] = 0; |
|
} |
|
|
|
return len; |
|
} |
|
|
|
|
|
int V_vsnprintfRet( char *pDest, int maxLen, const char *pFormat, va_list params, bool *pbTruncated ) |
|
{ |
|
Assert( maxLen > 0 ); |
|
AssertValidWritePtr( pDest, maxLen ); |
|
AssertValidStringPtr( pFormat ); |
|
|
|
int len = _vsnprintf( pDest, maxLen, pFormat, params ); |
|
|
|
if ( pbTruncated ) |
|
{ |
|
*pbTruncated = ( len < 0 || len >= maxLen ); |
|
} |
|
|
|
if ( len < 0 || len >= maxLen ) |
|
{ |
|
len = maxLen; |
|
pDest[maxLen-1] = 0; |
|
} |
|
|
|
return len; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: If COPY_ALL_CHARACTERS == max_chars_to_copy then we try to add the whole pSrc to the end of pDest, otherwise |
|
// we copy only as many characters as are specified in max_chars_to_copy (or the # of characters in pSrc if thats's less). |
|
// Input : *pDest - destination buffer |
|
// *pSrc - string to append |
|
// destBufferSize - sizeof the buffer pointed to by pDest |
|
// max_chars_to_copy - COPY_ALL_CHARACTERS in pSrc or max # to copy |
|
// Output : char * the copied buffer |
|
//----------------------------------------------------------------------------- |
|
char *V_strncat(char *pDest, const char *pSrc, size_t destBufferSize, int max_chars_to_copy ) |
|
{ |
|
size_t charstocopy = (size_t)0; |
|
|
|
Assert( (ptrdiff_t)destBufferSize >= 0 ); |
|
AssertValidStringPtr( pDest); |
|
AssertValidStringPtr( pSrc ); |
|
|
|
size_t len = strlen(pDest); |
|
size_t srclen = strlen( pSrc ); |
|
if ( max_chars_to_copy <= COPY_ALL_CHARACTERS ) |
|
{ |
|
charstocopy = srclen; |
|
} |
|
else |
|
{ |
|
charstocopy = (size_t)min( max_chars_to_copy, (int)srclen ); |
|
} |
|
|
|
if ( len + charstocopy >= destBufferSize ) |
|
{ |
|
charstocopy = destBufferSize - len - 1; |
|
} |
|
|
|
if ( (int)charstocopy <= 0 ) |
|
{ |
|
return pDest; |
|
} |
|
|
|
ANALYZE_SUPPRESS( 6059 ); // warning C6059: : Incorrect length parameter in call to 'strncat'. Pass the number of remaining characters, not the buffer size of 'argument 1' |
|
char *pOut = strncat( pDest, pSrc, charstocopy ); |
|
return pOut; |
|
} |
|
|
|
wchar_t *V_wcsncat( INOUT_Z_CAP(cchDest) wchar_t *pDest, const wchar_t *pSrc, size_t cchDest, int max_chars_to_copy ) |
|
{ |
|
size_t charstocopy = (size_t)0; |
|
|
|
Assert( (ptrdiff_t)cchDest >= 0 ); |
|
|
|
size_t len = wcslen(pDest); |
|
size_t srclen = wcslen( pSrc ); |
|
if ( max_chars_to_copy <= COPY_ALL_CHARACTERS ) |
|
{ |
|
charstocopy = srclen; |
|
} |
|
else |
|
{ |
|
charstocopy = (size_t)min( max_chars_to_copy, (int)srclen ); |
|
} |
|
|
|
if ( len + charstocopy >= cchDest ) |
|
{ |
|
charstocopy = cchDest - len - 1; |
|
} |
|
|
|
if ( (int)charstocopy <= 0 ) |
|
{ |
|
return pDest; |
|
} |
|
|
|
ANALYZE_SUPPRESS( 6059 ); // warning C6059: : Incorrect length parameter in call to 'strncat'. Pass the number of remaining characters, not the buffer size of 'argument 1' |
|
wchar_t *pOut = wcsncat( pDest, pSrc, charstocopy ); |
|
return pOut; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Converts value into x.xx MB/ x.xx KB, x.xx bytes format, including commas |
|
// Input : value - |
|
// 2 - |
|
// false - |
|
// Output : char |
|
//----------------------------------------------------------------------------- |
|
#define NUM_PRETIFYMEM_BUFFERS 8 |
|
char *V_pretifymem( float value, int digitsafterdecimal /*= 2*/, bool usebinaryonek /*= false*/ ) |
|
{ |
|
static char output[ NUM_PRETIFYMEM_BUFFERS ][ 32 ]; |
|
static int current; |
|
|
|
float onekb = usebinaryonek ? 1024.0f : 1000.0f; |
|
float onemb = onekb * onekb; |
|
|
|
char *out = output[ current ]; |
|
current = ( current + 1 ) & ( NUM_PRETIFYMEM_BUFFERS -1 ); |
|
|
|
char suffix[ 8 ]; |
|
|
|
// First figure out which bin to use |
|
if ( value > onemb ) |
|
{ |
|
value /= onemb; |
|
V_snprintf( suffix, sizeof( suffix ), " MB" ); |
|
} |
|
else if ( value > onekb ) |
|
{ |
|
value /= onekb; |
|
V_snprintf( suffix, sizeof( suffix ), " KB" ); |
|
} |
|
else |
|
{ |
|
V_snprintf( suffix, sizeof( suffix ), " bytes" ); |
|
} |
|
|
|
char val[ 32 ]; |
|
|
|
// Clamp to >= 0 |
|
digitsafterdecimal = max( digitsafterdecimal, 0 ); |
|
|
|
// If it's basically integral, don't do any decimals |
|
if ( FloatMakePositive( value - (int)value ) < 0.00001 ) |
|
{ |
|
V_snprintf( val, sizeof( val ), "%i%s", (int)value, suffix ); |
|
} |
|
else |
|
{ |
|
char fmt[ 32 ]; |
|
|
|
// Otherwise, create a format string for the decimals |
|
V_snprintf( fmt, sizeof( fmt ), "%%.%if%s", digitsafterdecimal, suffix ); |
|
V_snprintf( val, sizeof( val ), fmt, value ); |
|
} |
|
|
|
// Copy from in to out |
|
char *i = val; |
|
char *o = out; |
|
|
|
// Search for decimal or if it was integral, find the space after the raw number |
|
char *dot = strstr( i, "." ); |
|
if ( !dot ) |
|
{ |
|
dot = strstr( i, " " ); |
|
} |
|
|
|
// Compute position of dot |
|
int pos = dot - i; |
|
// Don't put a comma if it's <= 3 long |
|
pos -= 3; |
|
|
|
while ( *i ) |
|
{ |
|
// If pos is still valid then insert a comma every third digit, except if we would be |
|
// putting one in the first spot |
|
if ( pos >= 0 && !( pos % 3 ) ) |
|
{ |
|
// Never in first spot |
|
if ( o != out ) |
|
{ |
|
*o++ = ','; |
|
} |
|
} |
|
|
|
// Count down comma position |
|
pos--; |
|
|
|
// Copy rest of data as normal |
|
*o++ = *i++; |
|
} |
|
|
|
// Terminate |
|
*o = 0; |
|
|
|
return out; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns a string representation of an integer with commas |
|
// separating the 1000s (ie, 37,426,421) |
|
// Input : value - Value to convert |
|
// Output : Pointer to a static buffer containing the output |
|
//----------------------------------------------------------------------------- |
|
#define NUM_PRETIFYNUM_BUFFERS 8 // Must be a power of two |
|
char *V_pretifynum( int64 inputValue ) |
|
{ |
|
static char output[ NUM_PRETIFYMEM_BUFFERS ][ 32 ]; |
|
static int current; |
|
|
|
// Point to the output buffer. |
|
char * const out = output[ current ]; |
|
// Track the output buffer end for easy calculation of bytes-remaining. |
|
const char* const outEnd = out + sizeof( output[ current ] ); |
|
|
|
// Point to the current output location in the output buffer. |
|
char *pchRender = out; |
|
// Move to the next output pointer. |
|
current = ( current + 1 ) & ( NUM_PRETIFYMEM_BUFFERS -1 ); |
|
|
|
*out = 0; |
|
|
|
// In order to handle the most-negative int64 we need to negate it |
|
// into a uint64. |
|
uint64 value; |
|
// Render the leading minus sign, if necessary |
|
if ( inputValue < 0 ) |
|
{ |
|
V_snprintf( pchRender, 32, "-" ); |
|
value = (uint64)-inputValue; |
|
// Advance our output pointer. |
|
pchRender += V_strlen( pchRender ); |
|
} |
|
else |
|
{ |
|
value = (uint64)inputValue; |
|
} |
|
|
|
// Now let's find out how big our number is. The largest number we can fit |
|
// into 63 bits is about 9.2e18. So, there could potentially be six |
|
// three-digit groups. |
|
|
|
// We need the initial value of 'divisor' to be big enough to divide our |
|
// number down to 1-999 range. |
|
uint64 divisor = 1; |
|
// Loop more than six times to avoid integer overflow. |
|
for ( int i = 0; i < 6; ++i ) |
|
{ |
|
// If our divisor is already big enough then stop. |
|
if ( value < divisor * 1000 ) |
|
break; |
|
|
|
divisor *= 1000; |
|
} |
|
|
|
// Print the leading batch of one to three digits. |
|
int toPrint = value / divisor; |
|
V_snprintf( pchRender, outEnd - pchRender, "%d", toPrint ); |
|
|
|
for (;;) |
|
{ |
|
// Advance our output pointer. |
|
pchRender += V_strlen( pchRender ); |
|
// Adjust our value to be printed and our divisor. |
|
value -= toPrint * divisor; |
|
divisor /= 1000; |
|
if ( !divisor ) |
|
break; |
|
|
|
// The remaining blocks of digits always include a comma and three digits. |
|
toPrint = value / divisor; |
|
V_snprintf( pchRender, outEnd - pchRender, ",%03d", toPrint ); |
|
} |
|
|
|
return out; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: returns true if a wide character is a "mean" space; that is, |
|
// if it is technically a space or punctuation, but causes disruptive |
|
// behavior when used in names, web pages, chat windows, etc. |
|
// |
|
// characters in this set are removed from the beginning and/or end of strings |
|
// by Q_AggressiveStripPrecedingAndTrailingWhitespaceW() |
|
//----------------------------------------------------------------------------- |
|
bool Q_IsMeanSpaceW( wchar_t wch ) |
|
{ |
|
bool bIsMean = false; |
|
|
|
switch ( wch ) |
|
{ |
|
case L'\x0082': // BREAK PERMITTED HERE |
|
case L'\x0083': // NO BREAK PERMITTED HERE |
|
case L'\x00A0': // NO-BREAK SPACE |
|
case L'\x034F': // COMBINING GRAPHEME JOINER |
|
case L'\x2000': // EN QUAD |
|
case L'\x2001': // EM QUAD |
|
case L'\x2002': // EN SPACE |
|
case L'\x2003': // EM SPACE |
|
case L'\x2004': // THICK SPACE |
|
case L'\x2005': // MID SPACE |
|
case L'\x2006': // SIX SPACE |
|
case L'\x2007': // figure space |
|
case L'\x2008': // PUNCTUATION SPACE |
|
case L'\x2009': // THIN SPACE |
|
case L'\x200A': // HAIR SPACE |
|
case L'\x200B': // ZERO-WIDTH SPACE |
|
case L'\x200C': // ZERO-WIDTH NON-JOINER |
|
case L'\x200D': // ZERO WIDTH JOINER |
|
case L'\x200E': // LEFT-TO-RIGHT MARK |
|
case L'\x2028': // LINE SEPARATOR |
|
case L'\x2029': // PARAGRAPH SEPARATOR |
|
case L'\x202F': // NARROW NO-BREAK SPACE |
|
case L'\x2060': // word joiner |
|
case L'\xFEFF': // ZERO-WIDTH NO BREAK SPACE |
|
case L'\xFFFC': // OBJECT REPLACEMENT CHARACTER |
|
bIsMean = true; |
|
break; |
|
} |
|
|
|
return bIsMean; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: strips trailing whitespace; returns pointer inside string just past |
|
// any leading whitespace. |
|
// |
|
// bAggresive = true causes this function to also check for "mean" spaces, |
|
// which we don't want in persona names or chat strings as they're disruptive |
|
// to the user experience. |
|
//----------------------------------------------------------------------------- |
|
static wchar_t *StripWhitespaceWorker( int cchLength, wchar_t *pwch, bool *pbStrippedWhitespace, bool bAggressive ) |
|
{ |
|
// walk backwards from the end of the string, killing any whitespace |
|
*pbStrippedWhitespace = false; |
|
|
|
wchar_t *pwchEnd = pwch + cchLength; |
|
while ( --pwchEnd >= pwch ) |
|
{ |
|
if ( !iswspace( *pwchEnd ) && ( !bAggressive || !Q_IsMeanSpaceW( *pwchEnd ) ) ) |
|
break; |
|
|
|
*pwchEnd = 0; |
|
*pbStrippedWhitespace = true; |
|
} |
|
|
|
// walk forward in the string |
|
while ( pwch < pwchEnd ) |
|
{ |
|
if ( !iswspace( *pwch ) ) |
|
break; |
|
|
|
*pbStrippedWhitespace = true; |
|
pwch++; |
|
} |
|
|
|
return pwch; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Strips all evil characters (ie. zero-width no-break space) |
|
// from a string. |
|
//----------------------------------------------------------------------------- |
|
bool Q_RemoveAllEvilCharacters( char *pch ) |
|
{ |
|
// convert to unicode |
|
int cch = Q_strlen( pch ); |
|
int cubDest = (cch + 1 ) * sizeof( wchar_t ); |
|
wchar_t *pwch = (wchar_t *)stackalloc( cubDest ); |
|
int cwch = Q_UTF8ToUnicode( pch, pwch, cubDest ) / sizeof( wchar_t ); |
|
|
|
bool bStrippedWhitespace = false; |
|
|
|
// Walk through and skip over evil characters |
|
int nWalk = 0; |
|
for( int i=0; i<cwch; ++i ) |
|
{ |
|
if( !Q_IsMeanSpaceW( pwch[i] ) ) |
|
{ |
|
pwch[nWalk] = pwch[i]; |
|
++nWalk; |
|
} |
|
else |
|
{ |
|
bStrippedWhitespace = true; |
|
} |
|
} |
|
|
|
// Null terminate |
|
pwch[nWalk-1] = L'\0'; |
|
|
|
|
|
// copy back, if necessary |
|
if ( bStrippedWhitespace ) |
|
{ |
|
Q_UnicodeToUTF8( pwch, pch, cch ); |
|
} |
|
|
|
return bStrippedWhitespace; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: strips leading and trailing whitespace |
|
//----------------------------------------------------------------------------- |
|
bool Q_StripPrecedingAndTrailingWhitespaceW( wchar_t *pwch ) |
|
{ |
|
int cch = Q_wcslen( pwch ); |
|
|
|
// Early out and don't convert if we don't have any chars or leading/trailing ws. |
|
if ( ( cch < 1 ) || ( !iswspace( pwch[ 0 ] ) && !iswspace( pwch[ cch - 1 ] ) ) ) |
|
return false; |
|
|
|
// duplicate on stack |
|
int cubDest = ( cch + 1 ) * sizeof( wchar_t ); |
|
wchar_t *pwchT = (wchar_t *)stackalloc( cubDest ); |
|
Q_wcsncpy( pwchT, pwch, cubDest ); |
|
|
|
bool bStrippedWhitespace = false; |
|
pwchT = StripWhitespaceWorker( cch, pwch, &bStrippedWhitespace, false /* not aggressive */ ); |
|
|
|
// copy back, if necessary |
|
if ( bStrippedWhitespace ) |
|
{ |
|
Q_wcsncpy( pwch, pwchT, cubDest ); |
|
} |
|
|
|
return bStrippedWhitespace; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: strips leading and trailing whitespace, |
|
// and also strips punctuation and formatting characters with "clear" |
|
// representations. |
|
//----------------------------------------------------------------------------- |
|
bool Q_AggressiveStripPrecedingAndTrailingWhitespaceW( wchar_t *pwch ) |
|
{ |
|
// duplicate on stack |
|
int cch = Q_wcslen( pwch ); |
|
int cubDest = ( cch + 1 ) * sizeof( wchar_t ); |
|
wchar_t *pwchT = (wchar_t *)stackalloc( cubDest ); |
|
Q_wcsncpy( pwchT, pwch, cubDest ); |
|
|
|
bool bStrippedWhitespace = false; |
|
pwchT = StripWhitespaceWorker( cch, pwch, &bStrippedWhitespace, true /* is aggressive */ ); |
|
|
|
// copy back, if necessary |
|
if ( bStrippedWhitespace ) |
|
{ |
|
Q_wcsncpy( pwch, pwchT, cubDest ); |
|
} |
|
|
|
return bStrippedWhitespace; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: strips leading and trailing whitespace |
|
//----------------------------------------------------------------------------- |
|
bool Q_StripPrecedingAndTrailingWhitespace( char *pch ) |
|
{ |
|
int cch = Q_strlen( pch ); |
|
|
|
// Early out and don't convert if we don't have any chars or leading/trailing ws. |
|
if ( ( cch < 1 ) || ( !isspace( (unsigned char)pch[ 0 ] ) && !isspace( (unsigned char)pch[ cch - 1 ] ) ) ) |
|
return false; |
|
|
|
// convert to unicode |
|
int cubDest = (cch + 1 ) * sizeof( wchar_t ); |
|
wchar_t *pwch = (wchar_t *)stackalloc( cubDest ); |
|
int cwch = Q_UTF8ToUnicode( pch, pwch, cubDest ) / sizeof( wchar_t ); |
|
|
|
bool bStrippedWhitespace = false; |
|
pwch = StripWhitespaceWorker( cwch-1, pwch, &bStrippedWhitespace, false /* not aggressive */ ); |
|
|
|
// copy back, if necessary |
|
if ( bStrippedWhitespace ) |
|
{ |
|
Q_UnicodeToUTF8( pwch, pch, cch ); |
|
} |
|
|
|
return bStrippedWhitespace; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: strips leading and trailing whitespace |
|
//----------------------------------------------------------------------------- |
|
bool Q_AggressiveStripPrecedingAndTrailingWhitespace( char *pch ) |
|
{ |
|
// convert to unicode |
|
int cch = Q_strlen( pch ); |
|
int cubDest = (cch + 1 ) * sizeof( wchar_t ); |
|
wchar_t *pwch = (wchar_t *)stackalloc( cubDest ); |
|
int cwch = Q_UTF8ToUnicode( pch, pwch, cubDest ) / sizeof( wchar_t ); |
|
|
|
bool bStrippedWhitespace = false; |
|
pwch = StripWhitespaceWorker( cwch-1, pwch, &bStrippedWhitespace, true /* is aggressive */ ); |
|
|
|
// copy back, if necessary |
|
if ( bStrippedWhitespace ) |
|
{ |
|
Q_UnicodeToUTF8( pwch, pch, cch ); |
|
} |
|
|
|
return bStrippedWhitespace; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Converts a ucs2 string to a unicode (wchar_t) one, no-op on win32 |
|
//----------------------------------------------------------------------------- |
|
int _V_UCS2ToUnicode( const ucs2 *pUCS2, wchar_t *pUnicode, int cubDestSizeInBytes ) |
|
{ |
|
Assert( cubDestSizeInBytes >= sizeof( *pUnicode ) ); |
|
AssertValidWritePtr(pUnicode); |
|
AssertValidReadPtr(pUCS2); |
|
|
|
pUnicode[0] = 0; |
|
#ifdef _WIN32 |
|
int cchResult = V_wcslen( pUCS2 ); |
|
V_memcpy( pUnicode, pUCS2, cubDestSizeInBytes ); |
|
#else |
|
iconv_t conv_t = iconv_open( "UCS-4LE", "UCS-2LE" ); |
|
int cchResult = -1; |
|
size_t nLenUnicde = cubDestSizeInBytes; |
|
size_t nMaxUTF8 = cubDestSizeInBytes; |
|
char *pIn = (char *)pUCS2; |
|
char *pOut = (char *)pUnicode; |
|
if ( conv_t > (void*)0 ) |
|
{ |
|
cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8 ); |
|
iconv_close( conv_t ); |
|
if ( (int)cchResult < 0 ) |
|
cchResult = 0; |
|
else |
|
cchResult = nMaxUTF8; |
|
} |
|
#endif |
|
pUnicode[(cubDestSizeInBytes / sizeof(wchar_t)) - 1] = 0; |
|
return cchResult; |
|
|
|
} |
|
|
|
#ifdef _PREFAST_ |
|
#pragma warning( pop ) // Restore the /analyze warnings |
|
#endif |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Converts a wchar_t string into a UCS2 string -noop on windows |
|
//----------------------------------------------------------------------------- |
|
int _V_UnicodeToUCS2( const wchar_t *pUnicode, int cubSrcInBytes, char *pUCS2, int cubDestSizeInBytes ) |
|
{ |
|
#ifdef _WIN32 |
|
// Figure out which buffer is smaller and convert from bytes to character |
|
// counts. |
|
int cchResult = min( (size_t)cubSrcInBytes/sizeof(wchar_t), cubDestSizeInBytes/sizeof(wchar_t) ); |
|
wchar_t *pDest = (wchar_t*)pUCS2; |
|
wcsncpy( pDest, pUnicode, cchResult ); |
|
// Make sure we NULL-terminate. |
|
pDest[ cchResult - 1 ] = 0; |
|
#elif defined (POSIX) |
|
iconv_t conv_t = iconv_open( "UCS-2LE", "UTF-32LE" ); |
|
size_t cchResult = -1; |
|
size_t nLenUnicde = cubSrcInBytes; |
|
size_t nMaxUCS2 = cubDestSizeInBytes; |
|
char *pIn = (char*)pUnicode; |
|
char *pOut = pUCS2; |
|
if ( conv_t > (void*)0 ) |
|
{ |
|
cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUCS2 ); |
|
iconv_close( conv_t ); |
|
if ( (int)cchResult < 0 ) |
|
cchResult = 0; |
|
else |
|
cchResult = cubSrcInBytes / sizeof( wchar_t ); |
|
} |
|
#else |
|
#error Must be implemented for this platform |
|
#endif |
|
return cchResult; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Converts a ucs-2 (windows wchar_t) string into a UTF8 (standard) string |
|
//----------------------------------------------------------------------------- |
|
int _V_UCS2ToUTF8( const ucs2 *pUCS2, char *pUTF8, int cubDestSizeInBytes ) |
|
{ |
|
AssertValidStringPtr(pUTF8, cubDestSizeInBytes); |
|
AssertValidReadPtr(pUCS2); |
|
|
|
pUTF8[0] = 0; |
|
#ifdef _WIN32 |
|
// under win32 wchar_t == ucs2, sigh |
|
int cchResult = WideCharToMultiByte( CP_UTF8, 0, pUCS2, -1, pUTF8, cubDestSizeInBytes, NULL, NULL ); |
|
#elif defined(POSIX) |
|
iconv_t conv_t = iconv_open( "UTF-8", "UCS-2LE" ); |
|
size_t cchResult = -1; |
|
|
|
// pUCS2 will be null-terminated so use that to work out the input |
|
// buffer size. Note that we shouldn't assume iconv will stop when it |
|
// finds a zero, and nLenUnicde should be given in bytes, so we multiply |
|
// it by sizeof( ucs2 ) at the end. |
|
size_t nLenUnicde = 0; |
|
while ( pUCS2[nLenUnicde] ) |
|
{ |
|
++nLenUnicde; |
|
} |
|
nLenUnicde *= sizeof( ucs2 ); |
|
|
|
// Calculate number of bytes we want iconv to write, leaving space |
|
// for the null-terminator |
|
size_t nMaxUTF8 = cubDestSizeInBytes - 1; |
|
char *pIn = (char *)pUCS2; |
|
char *pOut = (char *)pUTF8; |
|
if ( conv_t > (void*)0 ) |
|
{ |
|
const size_t nBytesToWrite = nMaxUTF8; |
|
cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8 ); |
|
|
|
// Calculate how many bytes were actually written and use that to |
|
// null-terminate our output string. |
|
const size_t nBytesWritten = nBytesToWrite - nMaxUTF8; |
|
pUTF8[nBytesWritten] = 0; |
|
|
|
iconv_close( conv_t ); |
|
if ( (int)cchResult < 0 ) |
|
cchResult = 0; |
|
else |
|
cchResult = nMaxUTF8; |
|
} |
|
#endif |
|
pUTF8[cubDestSizeInBytes - 1] = 0; |
|
return cchResult; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Converts a UTF8 to ucs-2 (windows wchar_t) |
|
//----------------------------------------------------------------------------- |
|
int _V_UTF8ToUCS2( const char *pUTF8, int cubSrcInBytes, ucs2 *pUCS2, int cubDestSizeInBytes ) |
|
{ |
|
Assert( cubDestSizeInBytes >= sizeof(pUCS2[0]) ); |
|
AssertValidStringPtr(pUTF8, cubDestSizeInBytes); |
|
AssertValidReadPtr(pUCS2); |
|
|
|
pUCS2[0] = 0; |
|
#ifdef _WIN32 |
|
// under win32 wchar_t == ucs2, sigh |
|
int cchResult = MultiByteToWideChar( CP_UTF8, 0, pUTF8, -1, pUCS2, cubDestSizeInBytes / sizeof(wchar_t) ); |
|
#elif defined( _PS3 ) // bugbug JLB |
|
int cchResult = 0; |
|
Assert( 0 ); |
|
#elif defined(POSIX) |
|
iconv_t conv_t = iconv_open( "UCS-2LE", "UTF-8" ); |
|
size_t cchResult = -1; |
|
size_t nLenUnicde = cubSrcInBytes; |
|
size_t nMaxUTF8 = cubDestSizeInBytes; |
|
char *pIn = (char *)pUTF8; |
|
char *pOut = (char *)pUCS2; |
|
if ( conv_t > (void*)0 ) |
|
{ |
|
cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8 ); |
|
iconv_close( conv_t ); |
|
if ( (int)cchResult < 0 ) |
|
cchResult = 0; |
|
else |
|
cchResult = cubSrcInBytes; |
|
|
|
} |
|
#endif |
|
pUCS2[ (cubDestSizeInBytes/sizeof(ucs2)) - 1] = 0; |
|
return cchResult; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the 4 bit nibble for a hex character |
|
// Input : c - |
|
// Output : unsigned char |
|
//----------------------------------------------------------------------------- |
|
unsigned char V_nibble( char c ) |
|
{ |
|
if ( ( c >= '0' ) && |
|
( c <= '9' ) ) |
|
{ |
|
return (unsigned char)(c - '0'); |
|
} |
|
|
|
if ( ( c >= 'A' ) && |
|
( c <= 'F' ) ) |
|
{ |
|
return (unsigned char)(c - 'A' + 0x0a); |
|
} |
|
|
|
if ( ( c >= 'a' ) && |
|
( c <= 'f' ) ) |
|
{ |
|
return (unsigned char)(c - 'a' + 0x0a); |
|
} |
|
|
|
return '0'; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *in - |
|
// numchars - |
|
// *out - |
|
// maxoutputbytes - |
|
//----------------------------------------------------------------------------- |
|
void V_hextobinary( char const *in, int numchars, byte *out, int maxoutputbytes ) |
|
{ |
|
int len = V_strlen( in ); |
|
numchars = min( len, numchars ); |
|
// Make sure it's even |
|
numchars = ( numchars ) & ~0x1; |
|
|
|
// Must be an even # of input characters (two chars per output byte) |
|
Assert( numchars >= 2 ); |
|
|
|
memset( out, 0x00, maxoutputbytes ); |
|
|
|
byte *p; |
|
int i; |
|
|
|
p = out; |
|
for ( i = 0; |
|
( i < numchars ) && ( ( p - out ) < maxoutputbytes ); |
|
i+=2, p++ ) |
|
{ |
|
*p = ( V_nibble( in[i] ) << 4 ) | V_nibble( in[i+1] ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *in - |
|
// inputbytes - |
|
// *out - |
|
// outsize - |
|
//----------------------------------------------------------------------------- |
|
void V_binarytohex( const byte *in, int inputbytes, char *out, int outsize ) |
|
{ |
|
Assert( outsize >= 1 ); |
|
char doublet[10]; |
|
int i; |
|
|
|
out[0]=0; |
|
|
|
for ( i = 0; i < inputbytes; i++ ) |
|
{ |
|
unsigned char c = in[i]; |
|
V_snprintf( doublet, sizeof( doublet ), "%02x", c ); |
|
V_strncat( out, doublet, outsize, COPY_ALL_CHARACTERS ); |
|
} |
|
} |
|
|
|
// Even though \ on Posix (Linux&Mac) isn't techincally a path separator we are |
|
// now counting it as one even Posix since so many times our filepaths aren't actual |
|
// paths but rather text strings passed in from data files, treating \ as a pathseparator |
|
// covers the full range of cases |
|
bool PATHSEPARATOR( char c ) |
|
{ |
|
return c == '\\' || c == '/'; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Extracts the base name of a file (no path, no extension, assumes '/' or '\' as path separator) |
|
// Input : *in - |
|
// *out - |
|
// maxlen - |
|
//----------------------------------------------------------------------------- |
|
void V_FileBase( const char *in, char *out, int maxlen ) |
|
{ |
|
Assert( maxlen >= 1 ); |
|
Assert( in ); |
|
Assert( out ); |
|
|
|
if ( !in || !in[ 0 ] ) |
|
{ |
|
*out = 0; |
|
return; |
|
} |
|
|
|
int len, start, end; |
|
|
|
len = V_strlen( in ); |
|
|
|
// scan backward for '.' |
|
end = len - 1; |
|
while ( end&& in[end] != '.' && !PATHSEPARATOR( in[end] ) ) |
|
{ |
|
end--; |
|
} |
|
|
|
if ( in[end] != '.' ) // no '.', copy to end |
|
{ |
|
end = len-1; |
|
} |
|
else |
|
{ |
|
end--; // Found ',', copy to left of '.' |
|
} |
|
|
|
// Scan backward for '/' |
|
start = len-1; |
|
while ( start >= 0 && !PATHSEPARATOR( in[start] ) ) |
|
{ |
|
start--; |
|
} |
|
|
|
if ( start < 0 || !PATHSEPARATOR( in[start] ) ) |
|
{ |
|
start = 0; |
|
} |
|
else |
|
{ |
|
start++; |
|
} |
|
|
|
// Length of new sting |
|
len = end - start + 1; |
|
|
|
int maxcopy = min( len + 1, maxlen ); |
|
|
|
// Copy partial string |
|
V_strncpy( out, &in[start], maxcopy ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *ppath - |
|
//----------------------------------------------------------------------------- |
|
void V_StripTrailingSlash( char *ppath ) |
|
{ |
|
Assert( ppath ); |
|
|
|
int len = V_strlen( ppath ); |
|
if ( len > 0 ) |
|
{ |
|
if ( PATHSEPARATOR( ppath[ len - 1 ] ) ) |
|
{ |
|
ppath[ len - 1 ] = 0; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *ppline - |
|
//----------------------------------------------------------------------------- |
|
void V_StripTrailingWhitespace( char *ppline ) |
|
{ |
|
Assert( ppline ); |
|
|
|
int len = V_strlen( ppline ); |
|
while ( len > 0 ) |
|
{ |
|
if ( !V_isspace( ppline[ len - 1 ] ) ) |
|
break; |
|
ppline[ len - 1 ] = 0; |
|
len--; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *ppline - |
|
//----------------------------------------------------------------------------- |
|
void V_StripLeadingWhitespace( char *ppline ) |
|
{ |
|
Assert( ppline ); |
|
|
|
// Skip past initial whitespace |
|
int skip = 0; |
|
while( V_isspace( ppline[ skip ] ) ) |
|
skip++; |
|
// Shuffle the rest of the string back (including the NULL-terminator) |
|
if ( skip ) |
|
{ |
|
while( ( ppline[0] = ppline[skip] ) != 0 ) |
|
ppline++; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *ppline - |
|
//----------------------------------------------------------------------------- |
|
void V_StripSurroundingQuotes( char *ppline ) |
|
{ |
|
Assert( ppline ); |
|
|
|
int len = V_strlen( ppline ) - 2; |
|
if ( ( ppline[0] == '"' ) && ( len >= 0 ) && ( ppline[len+1] == '"' ) ) |
|
{ |
|
for ( int i = 0; i < len; i++ ) |
|
ppline[i] = ppline[i+1]; |
|
ppline[len] = 0; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *in - |
|
// *out - |
|
// outSize - |
|
//----------------------------------------------------------------------------- |
|
void V_StripExtension( const char *in, char *out, int outSize ) |
|
{ |
|
// Find the last dot. If it's followed by a dot or a slash, then it's part of a |
|
// directory specifier like ../../somedir/./blah. |
|
|
|
// scan backward for '.' |
|
int end = V_strlen( in ) - 1; |
|
while ( end > 0 && in[end] != '.' && !PATHSEPARATOR( in[end] ) ) |
|
{ |
|
--end; |
|
} |
|
|
|
if (end > 0 && !PATHSEPARATOR( in[end] ) && end < outSize) |
|
{ |
|
int nChars = min( end, outSize-1 ); |
|
if ( out != in ) |
|
{ |
|
memcpy( out, in, nChars ); |
|
} |
|
out[nChars] = 0; |
|
} |
|
else |
|
{ |
|
// nothing found |
|
if ( out != in ) |
|
{ |
|
V_strncpy( out, in, outSize ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *path - |
|
// *extension - |
|
// pathStringLength - |
|
//----------------------------------------------------------------------------- |
|
void V_DefaultExtension( char *path, const char *extension, int pathStringLength ) |
|
{ |
|
Assert( path ); |
|
Assert( pathStringLength >= 1 ); |
|
Assert( extension ); |
|
Assert( extension[0] == '.' ); |
|
|
|
char *src; |
|
|
|
// if path doesn't have a .EXT, append extension |
|
// (extension should include the .) |
|
src = path + V_strlen(path) - 1; |
|
|
|
while ( !PATHSEPARATOR( *src ) && ( src > path ) ) |
|
{ |
|
if (*src == '.') |
|
{ |
|
// it has an extension |
|
return; |
|
} |
|
src--; |
|
} |
|
|
|
// Concatenate the desired extension |
|
V_strncat( path, extension, pathStringLength, COPY_ALL_CHARACTERS ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Force extension... |
|
// Input : *path - |
|
// *extension - |
|
// pathStringLength - |
|
//----------------------------------------------------------------------------- |
|
void V_SetExtension( char *path, const char *extension, int pathStringLength ) |
|
{ |
|
V_StripExtension( path, path, pathStringLength ); |
|
|
|
// We either had an extension and stripped it, or didn't have an extension |
|
// at all. Either way, we need to concatenate our extension now. |
|
|
|
// extension is not required to start with '.', so if it's not there, |
|
// then append that first. |
|
if ( extension[0] != '.' ) |
|
{ |
|
V_strncat( path, ".", pathStringLength, COPY_ALL_CHARACTERS ); |
|
} |
|
|
|
V_strncat( path, extension, pathStringLength, COPY_ALL_CHARACTERS ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Remove final filename from string |
|
// Input : *path - |
|
// Output : void V_StripFilename |
|
//----------------------------------------------------------------------------- |
|
void V_StripFilename (char *path) |
|
{ |
|
int length; |
|
|
|
length = V_strlen( path )-1; |
|
if ( length <= 0 ) |
|
return; |
|
|
|
while ( length > 0 && |
|
!PATHSEPARATOR( path[length] ) ) |
|
{ |
|
length--; |
|
} |
|
|
|
path[ length ] = 0; |
|
} |
|
|
|
#ifdef _WIN32 |
|
#define CORRECT_PATH_SEPARATOR '\\' |
|
#define INCORRECT_PATH_SEPARATOR '/' |
|
#elif POSIX |
|
#define CORRECT_PATH_SEPARATOR '/' |
|
#define INCORRECT_PATH_SEPARATOR '\\' |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Changes all '/' or '\' characters into separator |
|
// Input : *pname - |
|
// separator - |
|
//----------------------------------------------------------------------------- |
|
void V_FixSlashes( char *pname, char separator /* = CORRECT_PATH_SEPARATOR */ ) |
|
{ |
|
while ( *pname ) |
|
{ |
|
if ( *pname == INCORRECT_PATH_SEPARATOR || *pname == CORRECT_PATH_SEPARATOR ) |
|
{ |
|
*pname = separator; |
|
} |
|
pname++; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This function fixes cases of filenames like materials\\blah.vmt or somepath\otherpath\\ and removes the extra double slash. |
|
//----------------------------------------------------------------------------- |
|
void V_FixDoubleSlashes( char *pStr ) |
|
{ |
|
int len = V_strlen( pStr ); |
|
|
|
for ( int i=1; i < len-1; i++ ) |
|
{ |
|
if ( (pStr[i] == '/' || pStr[i] == '\\') && (pStr[i+1] == '/' || pStr[i+1] == '\\') ) |
|
{ |
|
// This means there's a double slash somewhere past the start of the filename. That |
|
// can happen in Hammer if they use a material in the root directory. You'll get a filename |
|
// that looks like 'materials\\blah.vmt' |
|
V_memmove( &pStr[i], &pStr[i+1], len - i ); |
|
--len; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Strip off the last directory from dirName |
|
// Input : *dirName - |
|
// maxlen - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool V_StripLastDir( char *dirName, int maxlen ) |
|
{ |
|
if( dirName[0] == 0 || |
|
!V_stricmp( dirName, "./" ) || |
|
!V_stricmp( dirName, ".\\" ) ) |
|
return false; |
|
|
|
int len = V_strlen( dirName ); |
|
|
|
Assert( len < maxlen ); |
|
|
|
// skip trailing slash |
|
if ( PATHSEPARATOR( dirName[len-1] ) ) |
|
{ |
|
len--; |
|
} |
|
|
|
while ( len > 0 ) |
|
{ |
|
if ( PATHSEPARATOR( dirName[len-1] ) ) |
|
{ |
|
dirName[len] = 0; |
|
V_FixSlashes( dirName, CORRECT_PATH_SEPARATOR ); |
|
return true; |
|
} |
|
len--; |
|
} |
|
|
|
// Allow it to return an empty string and true. This can happen if something like "tf2/" is passed in. |
|
// The correct behavior is to strip off the last directory ("tf2") and return true. |
|
if( len == 0 ) |
|
{ |
|
V_snprintf( dirName, maxlen, ".%c", CORRECT_PATH_SEPARATOR ); |
|
return true; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns a pointer to the beginning of the unqualified file name |
|
// (no path information) |
|
// Input: in - file name (may be unqualified, relative or absolute path) |
|
// Output: pointer to unqualified file name |
|
//----------------------------------------------------------------------------- |
|
const char * V_UnqualifiedFileName( const char * in ) |
|
{ |
|
// back up until the character after the first path separator we find, |
|
// or the beginning of the string |
|
const char * out = in + strlen( in ) - 1; |
|
while ( ( out > in ) && ( !PATHSEPARATOR( *( out-1 ) ) ) ) |
|
out--; |
|
return out; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Composes a path and filename together, inserting a path separator |
|
// if need be |
|
// Input: path - path to use |
|
// filename - filename to use |
|
// dest - buffer to compose result in |
|
// destSize - size of destination buffer |
|
//----------------------------------------------------------------------------- |
|
void V_ComposeFileName( const char *path, const char *filename, char *dest, int destSize ) |
|
{ |
|
V_strncpy( dest, path, destSize ); |
|
V_FixSlashes( dest ); |
|
V_AppendSlash( dest, destSize ); |
|
V_strncat( dest, filename, destSize, COPY_ALL_CHARACTERS ); |
|
V_FixSlashes( dest ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *path - |
|
// *dest - |
|
// destSize - |
|
// Output : void V_ExtractFilePath |
|
//----------------------------------------------------------------------------- |
|
bool V_ExtractFilePath (const char *path, char *dest, int destSize ) |
|
{ |
|
Assert( destSize >= 1 ); |
|
if ( destSize < 1 ) |
|
{ |
|
return false; |
|
} |
|
|
|
// Last char |
|
int len = V_strlen(path); |
|
const char *src = path + (len ? len-1 : 0); |
|
|
|
// back up until a \ or the start |
|
while ( src != path && !PATHSEPARATOR( *(src-1) ) ) |
|
{ |
|
src--; |
|
} |
|
|
|
int copysize = min( (int)((ptrdiff_t)src - (ptrdiff_t)path), destSize - 1 ); |
|
memcpy( dest, path, copysize ); |
|
dest[copysize] = 0; |
|
|
|
return copysize != 0 ? true : false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *path - |
|
// *dest - |
|
// destSize - |
|
// Output : void V_ExtractFileExtension |
|
//----------------------------------------------------------------------------- |
|
void V_ExtractFileExtension( const char *path, char *dest, int destSize ) |
|
{ |
|
*dest = NULL; |
|
const char * extension = V_GetFileExtension( path ); |
|
if ( NULL != extension ) |
|
V_strncpy( dest, extension, destSize ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns a pointer to the file extension within a file name string |
|
// Input: in - file name |
|
// Output: pointer to beginning of extension (after the "."), or NULL |
|
// if there is no extension |
|
//----------------------------------------------------------------------------- |
|
const char * V_GetFileExtension( const char * path ) |
|
{ |
|
const char *src; |
|
|
|
src = path + strlen(path) - 1; |
|
|
|
// |
|
// back up until a . or the start |
|
// |
|
while (src != path && *(src-1) != '.' ) |
|
src--; |
|
|
|
// check to see if the '.' is part of a pathname |
|
if (src == path || PATHSEPARATOR( *src ) ) |
|
{ |
|
return NULL; // no extension |
|
} |
|
|
|
return src; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns a pointer to the filename part of a path string |
|
// Input: in - file name |
|
// Output: pointer to beginning of filename (after the "/"). If there were no /, |
|
// output is identical to input |
|
//----------------------------------------------------------------------------- |
|
const char * V_GetFileName( const char * path ) |
|
{ |
|
return V_UnqualifiedFileName( path ); |
|
} |
|
|
|
|
|
bool V_RemoveDotSlashes( char *pFilename, char separator, bool bRemoveDoubleSlashes /* = true */ ) |
|
{ |
|
char *pIn = pFilename; |
|
char *pOut = pFilename; |
|
bool bRetVal = true; |
|
|
|
bool bBoundary = true; |
|
while ( *pIn ) |
|
{ |
|
if ( bBoundary && pIn[0] == '.' && pIn[1] == '.' && ( PATHSEPARATOR( pIn[2] ) || !pIn[2] ) ) |
|
{ |
|
// Get rid of /../ or trailing /.. by backing pOut up to previous separator |
|
|
|
// Eat the last separator (or repeated separators) we wrote out |
|
while ( pOut != pFilename && pOut[-1] == separator ) |
|
{ |
|
--pOut; |
|
} |
|
|
|
while ( true ) |
|
{ |
|
if ( pOut == pFilename ) |
|
{ |
|
bRetVal = false; // backwards compat. return value, even though we continue handling |
|
break; |
|
} |
|
--pOut; |
|
if ( *pOut == separator ) |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
// Skip the '..' but not the slash, next loop iteration will handle separator |
|
pIn += 2; |
|
bBoundary = ( pOut == pFilename ); |
|
} |
|
else if ( bBoundary && pIn[0] == '.' && ( PATHSEPARATOR( pIn[1] ) || !pIn[1] ) ) |
|
{ |
|
// Handle "./" by simply skipping this sequence. bBoundary is unchanged. |
|
if ( PATHSEPARATOR( pIn[1] ) ) |
|
{ |
|
pIn += 2; |
|
} |
|
else |
|
{ |
|
// Special case: if trailing "." is preceded by separator, eg "path/.", |
|
// then the final separator should also be stripped. bBoundary may then |
|
// be in an incorrect state, but we are at the end of processing anyway |
|
// so we don't really care (the processing loop is about to terminate). |
|
if ( pOut != pFilename && pOut[-1] == separator ) |
|
{ |
|
--pOut; |
|
} |
|
pIn += 1; |
|
} |
|
} |
|
else if ( PATHSEPARATOR( pIn[0] ) ) |
|
{ |
|
*pOut = separator; |
|
pOut += 1 - (bBoundary & bRemoveDoubleSlashes & (pOut != pFilename)); |
|
pIn += 1; |
|
bBoundary = true; |
|
} |
|
else |
|
{ |
|
if ( pOut != pIn ) |
|
{ |
|
*pOut = *pIn; |
|
} |
|
pOut += 1; |
|
pIn += 1; |
|
bBoundary = false; |
|
} |
|
} |
|
*pOut = 0; |
|
|
|
return bRetVal; |
|
} |
|
|
|
|
|
void V_AppendSlash( char *pStr, int strSize ) |
|
{ |
|
int len = V_strlen( pStr ); |
|
if ( len > 0 && !PATHSEPARATOR(pStr[len-1]) ) |
|
{ |
|
if ( len+1 >= strSize ) |
|
Error( "V_AppendSlash: ran out of space on %s.", pStr ); |
|
|
|
pStr[len] = CORRECT_PATH_SEPARATOR; |
|
pStr[len+1] = 0; |
|
} |
|
} |
|
|
|
|
|
void V_MakeAbsolutePath( char *pOut, int outLen, const char *pPath, const char *pStartingDir ) |
|
{ |
|
if ( V_IsAbsolutePath( pPath ) ) |
|
{ |
|
// pPath is not relative.. just copy it. |
|
V_strncpy( pOut, pPath, outLen ); |
|
} |
|
else |
|
{ |
|
// Make sure the starting directory is absolute.. |
|
if ( pStartingDir && V_IsAbsolutePath( pStartingDir ) ) |
|
{ |
|
V_strncpy( pOut, pStartingDir, outLen ); |
|
} |
|
else |
|
{ |
|
if ( !_getcwd( pOut, outLen ) ) |
|
Error( "V_MakeAbsolutePath: _getcwd failed." ); |
|
|
|
if ( pStartingDir ) |
|
{ |
|
V_AppendSlash( pOut, outLen ); |
|
V_strncat( pOut, pStartingDir, outLen, COPY_ALL_CHARACTERS ); |
|
} |
|
} |
|
|
|
// Concatenate the paths. |
|
V_AppendSlash( pOut, outLen ); |
|
V_strncat( pOut, pPath, outLen, COPY_ALL_CHARACTERS ); |
|
} |
|
|
|
if ( !V_RemoveDotSlashes( pOut ) ) |
|
Error( "V_MakeAbsolutePath: tried to \"..\" past the root." ); |
|
|
|
//V_FixSlashes( pOut ); - handled by V_RemoveDotSlashes |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Makes a relative path |
|
//----------------------------------------------------------------------------- |
|
bool V_MakeRelativePath( const char *pFullPath, const char *pDirectory, char *pRelativePath, int nBufLen ) |
|
{ |
|
pRelativePath[0] = 0; |
|
|
|
const char *pPath = pFullPath; |
|
const char *pDir = pDirectory; |
|
|
|
// Strip out common parts of the path |
|
const char *pLastCommonPath = NULL; |
|
const char *pLastCommonDir = NULL; |
|
while ( *pPath && ( FastToLower( *pPath ) == FastToLower( *pDir ) || |
|
( PATHSEPARATOR( *pPath ) && ( PATHSEPARATOR( *pDir ) || (*pDir == 0) ) ) ) ) |
|
{ |
|
if ( PATHSEPARATOR( *pPath ) ) |
|
{ |
|
pLastCommonPath = pPath + 1; |
|
pLastCommonDir = pDir + 1; |
|
} |
|
if ( *pDir == 0 ) |
|
{ |
|
--pLastCommonDir; |
|
break; |
|
} |
|
++pDir; ++pPath; |
|
} |
|
|
|
// Nothing in common |
|
if ( !pLastCommonPath ) |
|
return false; |
|
|
|
// For each path separator remaining in the dir, need a ../ |
|
int nOutLen = 0; |
|
bool bLastCharWasSeparator = true; |
|
for ( ; *pLastCommonDir; ++pLastCommonDir ) |
|
{ |
|
if ( PATHSEPARATOR( *pLastCommonDir ) ) |
|
{ |
|
pRelativePath[nOutLen++] = '.'; |
|
pRelativePath[nOutLen++] = '.'; |
|
pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR; |
|
bLastCharWasSeparator = true; |
|
} |
|
else |
|
{ |
|
bLastCharWasSeparator = false; |
|
} |
|
} |
|
|
|
// Deal with relative paths not specified with a trailing slash |
|
if ( !bLastCharWasSeparator ) |
|
{ |
|
pRelativePath[nOutLen++] = '.'; |
|
pRelativePath[nOutLen++] = '.'; |
|
pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR; |
|
} |
|
|
|
// Copy the remaining part of the relative path over, fixing the path separators |
|
for ( ; *pLastCommonPath; ++pLastCommonPath ) |
|
{ |
|
if ( PATHSEPARATOR( *pLastCommonPath ) ) |
|
{ |
|
pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR; |
|
} |
|
else |
|
{ |
|
pRelativePath[nOutLen++] = *pLastCommonPath; |
|
} |
|
|
|
// Check for overflow |
|
if ( nOutLen == nBufLen - 1 ) |
|
break; |
|
} |
|
|
|
pRelativePath[nOutLen] = 0; |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// small helper function shared by lots of modules |
|
//----------------------------------------------------------------------------- |
|
bool V_IsAbsolutePath( const char *pStr ) |
|
{ |
|
bool bIsAbsolute = ( pStr[0] && pStr[1] == ':' ) || pStr[0] == '/' || pStr[0] == '\\'; |
|
if ( IsX360() && !bIsAbsolute ) |
|
{ |
|
bIsAbsolute = ( V_stristr( pStr, ":" ) != NULL ); |
|
} |
|
return bIsAbsolute; |
|
} |
|
|
|
|
|
// Copies at most nCharsToCopy bytes from pIn into pOut. |
|
// Returns false if it would have overflowed pOut's buffer. |
|
static bool CopyToMaxChars( char *pOut, int outSize, const char *pIn, int nCharsToCopy ) |
|
{ |
|
if ( outSize == 0 ) |
|
return false; |
|
|
|
int iOut = 0; |
|
while ( *pIn && nCharsToCopy > 0 ) |
|
{ |
|
if ( iOut == (outSize-1) ) |
|
{ |
|
pOut[iOut] = 0; |
|
return false; |
|
} |
|
pOut[iOut] = *pIn; |
|
++iOut; |
|
++pIn; |
|
--nCharsToCopy; |
|
} |
|
|
|
pOut[iOut] = 0; |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Fixes up a file name, removing dot slashes, fixing slashes, converting to lowercase, etc. |
|
//----------------------------------------------------------------------------- |
|
void V_FixupPathName( char *pOut, size_t nOutLen, const char *pPath ) |
|
{ |
|
V_strncpy( pOut, pPath, nOutLen ); |
|
V_RemoveDotSlashes( pOut, CORRECT_PATH_SEPARATOR, true ); |
|
#ifdef WIN32 |
|
V_strlower( pOut ); |
|
#endif |
|
} |
|
|
|
|
|
// Returns true if it completed successfully. |
|
// If it would overflow pOut, it fills as much as it can and returns false. |
|
bool V_StrSubst( |
|
const char *pIn, |
|
const char *pMatch, |
|
const char *pReplaceWith, |
|
char *pOut, |
|
int outLen, |
|
bool bCaseSensitive |
|
) |
|
{ |
|
int replaceFromLen = strlen( pMatch ); |
|
int replaceToLen = strlen( pReplaceWith ); |
|
|
|
const char *pInStart = pIn; |
|
char *pOutPos = pOut; |
|
pOutPos[0] = 0; |
|
|
|
while ( 1 ) |
|
{ |
|
int nRemainingOut = outLen - (pOutPos - pOut); |
|
|
|
const char *pTestPos = ( bCaseSensitive ? strstr( pInStart, pMatch ) : V_stristr( pInStart, pMatch ) ); |
|
if ( pTestPos ) |
|
{ |
|
// Found an occurence of pMatch. First, copy whatever leads up to the string. |
|
int copyLen = pTestPos - pInStart; |
|
if ( !CopyToMaxChars( pOutPos, nRemainingOut, pInStart, copyLen ) ) |
|
return false; |
|
|
|
// Did we hit the end of the output string? |
|
if ( copyLen > nRemainingOut-1 ) |
|
return false; |
|
|
|
pOutPos += strlen( pOutPos ); |
|
nRemainingOut = outLen - (pOutPos - pOut); |
|
|
|
// Now add the replacement string. |
|
if ( !CopyToMaxChars( pOutPos, nRemainingOut, pReplaceWith, replaceToLen ) ) |
|
return false; |
|
|
|
pInStart += copyLen + replaceFromLen; |
|
pOutPos += replaceToLen; |
|
} |
|
else |
|
{ |
|
// We're at the end of pIn. Copy whatever remains and get out. |
|
int copyLen = strlen( pInStart ); |
|
V_strncpy( pOutPos, pInStart, nRemainingOut ); |
|
return ( copyLen <= nRemainingOut-1 ); |
|
} |
|
} |
|
} |
|
|
|
|
|
char* AllocString( const char *pStr, int nMaxChars ) |
|
{ |
|
int allocLen; |
|
if ( nMaxChars == -1 ) |
|
allocLen = strlen( pStr ) + 1; |
|
else |
|
allocLen = min( (int)strlen(pStr), nMaxChars ) + 1; |
|
|
|
char *pOut = new char[allocLen]; |
|
V_strncpy( pOut, pStr, allocLen ); |
|
return pOut; |
|
} |
|
|
|
|
|
void V_SplitString2( const char *pString, const char **pSeparators, int nSeparators, CUtlVector<char*> &outStrings ) |
|
{ |
|
outStrings.Purge(); |
|
const char *pCurPos = pString; |
|
while ( 1 ) |
|
{ |
|
int iFirstSeparator = -1; |
|
const char *pFirstSeparator = 0; |
|
for ( int i=0; i < nSeparators; i++ ) |
|
{ |
|
const char *pTest = V_stristr( pCurPos, pSeparators[i] ); |
|
if ( pTest && (!pFirstSeparator || pTest < pFirstSeparator) ) |
|
{ |
|
iFirstSeparator = i; |
|
pFirstSeparator = pTest; |
|
} |
|
} |
|
|
|
if ( pFirstSeparator ) |
|
{ |
|
// Split on this separator and continue on. |
|
int separatorLen = strlen( pSeparators[iFirstSeparator] ); |
|
if ( pFirstSeparator > pCurPos ) |
|
{ |
|
outStrings.AddToTail( AllocString( pCurPos, pFirstSeparator-pCurPos ) ); |
|
} |
|
|
|
pCurPos = pFirstSeparator + separatorLen; |
|
} |
|
else |
|
{ |
|
// Copy the rest of the string |
|
if ( strlen( pCurPos ) ) |
|
{ |
|
outStrings.AddToTail( AllocString( pCurPos, -1 ) ); |
|
} |
|
return; |
|
} |
|
} |
|
} |
|
|
|
|
|
void V_SplitString( const char *pString, const char *pSeparator, CUtlVector<char*> &outStrings ) |
|
{ |
|
V_SplitString2( pString, &pSeparator, 1, outStrings ); |
|
} |
|
|
|
|
|
bool V_GetCurrentDirectory( char *pOut, int maxLen ) |
|
{ |
|
return _getcwd( pOut, maxLen ) == pOut; |
|
} |
|
|
|
|
|
bool V_SetCurrentDirectory( const char *pDirName ) |
|
{ |
|
return _chdir( pDirName ) == 0; |
|
} |
|
|
|
|
|
// This function takes a slice out of pStr and stores it in pOut. |
|
// It follows the Python slice convention: |
|
// Negative numbers wrap around the string (-1 references the last character). |
|
// Numbers are clamped to the end of the string. |
|
void V_StrSlice( const char *pStr, int firstChar, int lastCharNonInclusive, char *pOut, int outSize ) |
|
{ |
|
if ( outSize == 0 ) |
|
return; |
|
|
|
int length = strlen( pStr ); |
|
|
|
// Fixup the string indices. |
|
if ( firstChar < 0 ) |
|
{ |
|
firstChar = length - (-firstChar % length); |
|
} |
|
else if ( firstChar >= length ) |
|
{ |
|
pOut[0] = 0; |
|
return; |
|
} |
|
|
|
if ( lastCharNonInclusive < 0 ) |
|
{ |
|
lastCharNonInclusive = length - (-lastCharNonInclusive % length); |
|
} |
|
else if ( lastCharNonInclusive > length ) |
|
{ |
|
lastCharNonInclusive %= length; |
|
} |
|
|
|
if ( lastCharNonInclusive <= firstChar ) |
|
{ |
|
pOut[0] = 0; |
|
return; |
|
} |
|
|
|
int copyLen = lastCharNonInclusive - firstChar; |
|
if ( copyLen <= (outSize-1) ) |
|
{ |
|
memcpy( pOut, &pStr[firstChar], copyLen ); |
|
pOut[copyLen] = 0; |
|
} |
|
else |
|
{ |
|
memcpy( pOut, &pStr[firstChar], outSize-1 ); |
|
pOut[outSize-1] = 0; |
|
} |
|
} |
|
|
|
|
|
void V_StrLeft( const char *pStr, int nChars, char *pOut, int outSize ) |
|
{ |
|
if ( nChars == 0 ) |
|
{ |
|
if ( outSize != 0 ) |
|
pOut[0] = 0; |
|
|
|
return; |
|
} |
|
|
|
V_StrSlice( pStr, 0, nChars, pOut, outSize ); |
|
} |
|
|
|
|
|
void V_StrRight( const char *pStr, int nChars, char *pOut, int outSize ) |
|
{ |
|
int len = strlen( pStr ); |
|
if ( nChars >= len ) |
|
{ |
|
V_strncpy( pOut, pStr, outSize ); |
|
} |
|
else |
|
{ |
|
V_StrSlice( pStr, -nChars, strlen( pStr ), pOut, outSize ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Convert multibyte to wchar + back |
|
//----------------------------------------------------------------------------- |
|
void V_strtowcs( const char *pString, int nInSize, wchar_t *pWString, int nOutSizeInBytes ) |
|
{ |
|
Assert( nOutSizeInBytes >= sizeof(pWString[0]) ); |
|
#ifdef _WIN32 |
|
int nOutSizeInChars = nOutSizeInBytes / sizeof(pWString[0]); |
|
int result = MultiByteToWideChar( CP_UTF8, 0, pString, nInSize, pWString, nOutSizeInChars ); |
|
// If the string completely fails to fit then MultiByteToWideChar will return 0. |
|
// If the string exactly fits but with no room for a null-terminator then MultiByteToWideChar |
|
// will happily fill the buffer and omit the null-terminator, returning nOutSizeInChars. |
|
// Either way we need to return an empty string rather than a bogus and possibly not |
|
// null-terminated result. |
|
if ( result <= 0 || result >= nOutSizeInChars ) |
|
{ |
|
// If nInSize includes the null-terminator then a result of nOutSizeInChars is |
|
// legal. We check this by seeing if the last character in the output buffer is |
|
// a zero. |
|
if ( result == nOutSizeInChars && pWString[ nOutSizeInChars - 1 ] == 0) |
|
{ |
|
// We're okay! Do nothing. |
|
} |
|
else |
|
{ |
|
// The string completely to fit. Null-terminate the buffer. |
|
*pWString = L'\0'; |
|
} |
|
} |
|
else |
|
{ |
|
// We have successfully converted our string. Now we need to null-terminate it, because |
|
// MultiByteToWideChar will only do that if nInSize includes the source null-terminator! |
|
pWString[ result ] = 0; |
|
} |
|
#elif POSIX |
|
if ( mbstowcs( pWString, pString, nOutSizeInBytes / sizeof(pWString[0]) ) <= 0 ) |
|
{ |
|
*pWString = 0; |
|
} |
|
#endif |
|
} |
|
|
|
void V_wcstostr( const wchar_t *pWString, int nInSize, char *pString, int nOutSizeInChars ) |
|
{ |
|
#ifdef _WIN32 |
|
int result = WideCharToMultiByte( CP_UTF8, 0, pWString, nInSize, pString, nOutSizeInChars, NULL, NULL ); |
|
// If the string completely fails to fit then MultiByteToWideChar will return 0. |
|
// If the string exactly fits but with no room for a null-terminator then MultiByteToWideChar |
|
// will happily fill the buffer and omit the null-terminator, returning nOutSizeInChars. |
|
// Either way we need to return an empty string rather than a bogus and possibly not |
|
// null-terminated result. |
|
if ( result <= 0 || result >= nOutSizeInChars ) |
|
{ |
|
// If nInSize includes the null-terminator then a result of nOutSizeInChars is |
|
// legal. We check this by seeing if the last character in the output buffer is |
|
// a zero. |
|
if ( result == nOutSizeInChars && pWString[ nOutSizeInChars - 1 ] == 0) |
|
{ |
|
// We're okay! Do nothing. |
|
} |
|
else |
|
{ |
|
*pString = '\0'; |
|
} |
|
} |
|
else |
|
{ |
|
// We have successfully converted our string. Now we need to null-terminate it, because |
|
// MultiByteToWideChar will only do that if nInSize includes the source null-terminator! |
|
pString[ result ] = '\0'; |
|
} |
|
#elif POSIX |
|
if ( wcstombs( pString, pWString, nOutSizeInChars ) <= 0 ) |
|
{ |
|
*pString = '\0'; |
|
} |
|
#endif |
|
} |
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------- |
|
// backslashification |
|
//-------------------------------------------------------------------------------- |
|
|
|
static char s_BackSlashMap[]="\tt\nn\rr\"\"\\\\"; |
|
|
|
char *V_AddBackSlashesToSpecialChars( char const *pSrc ) |
|
{ |
|
// first, count how much space we are going to need |
|
int nSpaceNeeded = 0; |
|
for( char const *pScan = pSrc; *pScan; pScan++ ) |
|
{ |
|
nSpaceNeeded++; |
|
for(char const *pCharSet=s_BackSlashMap; *pCharSet; pCharSet += 2 ) |
|
{ |
|
if ( *pCharSet == *pScan ) |
|
nSpaceNeeded++; // we need to store a bakslash |
|
} |
|
} |
|
char *pRet = new char[ nSpaceNeeded + 1 ]; // +1 for null |
|
char *pOut = pRet; |
|
|
|
for( char const *pScan = pSrc; *pScan; pScan++ ) |
|
{ |
|
bool bIsSpecial = false; |
|
for(char const *pCharSet=s_BackSlashMap; *pCharSet; pCharSet += 2 ) |
|
{ |
|
if ( *pCharSet == *pScan ) |
|
{ |
|
*( pOut++ ) = '\\'; |
|
*( pOut++ ) = pCharSet[1]; |
|
bIsSpecial = true; |
|
break; |
|
} |
|
} |
|
if (! bIsSpecial ) |
|
{ |
|
*( pOut++ ) = *pScan; |
|
} |
|
} |
|
*( pOut++ ) = 0; |
|
return pRet; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Helper for converting a numeric value to a hex digit, value should be 0-15. |
|
//----------------------------------------------------------------------------- |
|
char cIntToHexDigit( int nValue ) |
|
{ |
|
Assert( nValue >= 0 && nValue <= 15 ); |
|
return "0123456789ABCDEF"[ nValue & 15 ]; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Helper for converting a hex char value to numeric, return -1 if the char |
|
// is not a valid hex digit. |
|
//----------------------------------------------------------------------------- |
|
int iHexCharToInt( char cValue ) |
|
{ |
|
int32 iValue = cValue; |
|
if ( (uint32)( iValue - '0' ) < 10 ) |
|
return iValue - '0'; |
|
|
|
iValue |= 0x20; |
|
if ( (uint32)( iValue - 'a' ) < 6 ) |
|
return iValue - 'a' + 10; |
|
|
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Internal implementation of encode, works in the strict RFC manner, or |
|
// with spaces turned to + like HTML form encoding. |
|
//----------------------------------------------------------------------------- |
|
void Q_URLEncodeInternal( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen, bool bUsePlusForSpace ) |
|
{ |
|
if ( nDestLen < 3*nSourceLen ) |
|
{ |
|
pchDest[0] = '\0'; |
|
AssertMsg( false, "Target buffer for Q_URLEncode needs to be 3 times larger than source to guarantee enough space\n" ); |
|
return; |
|
} |
|
|
|
int iDestPos = 0; |
|
for ( int i=0; i < nSourceLen; ++i ) |
|
{ |
|
// We allow only a-z, A-Z, 0-9, period, underscore, and hyphen to pass through unescaped. |
|
// These are the characters allowed by both the original RFC 1738 and the latest RFC 3986. |
|
// Current specs also allow '~', but that is forbidden under original RFC 1738. |
|
if ( !( pchSource[i] >= 'a' && pchSource[i] <= 'z' ) && !( pchSource[i] >= 'A' && pchSource[i] <= 'Z' ) && !(pchSource[i] >= '0' && pchSource[i] <= '9' ) |
|
&& pchSource[i] != '-' && pchSource[i] != '_' && pchSource[i] != '.' |
|
) |
|
{ |
|
if ( bUsePlusForSpace && pchSource[i] == ' ' ) |
|
{ |
|
pchDest[iDestPos++] = '+'; |
|
} |
|
else |
|
{ |
|
pchDest[iDestPos++] = '%'; |
|
uint8 iValue = pchSource[i]; |
|
if ( iValue == 0 ) |
|
{ |
|
pchDest[iDestPos++] = '0'; |
|
pchDest[iDestPos++] = '0'; |
|
} |
|
else |
|
{ |
|
char cHexDigit1 = cIntToHexDigit( iValue % 16 ); |
|
iValue /= 16; |
|
char cHexDigit2 = cIntToHexDigit( iValue ); |
|
pchDest[iDestPos++] = cHexDigit2; |
|
pchDest[iDestPos++] = cHexDigit1; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
pchDest[iDestPos++] = pchSource[i]; |
|
} |
|
} |
|
|
|
// Null terminate |
|
pchDest[iDestPos++] = 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Internal implementation of decode, works in the strict RFC manner, or |
|
// with spaces turned to + like HTML form encoding. |
|
// |
|
// Returns the amount of space used in the output buffer. |
|
//----------------------------------------------------------------------------- |
|
size_t Q_URLDecodeInternal( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen, bool bUsePlusForSpace ) |
|
{ |
|
if ( nDecodeDestLen < nEncodedSourceLen ) |
|
{ |
|
AssertMsg( false, "Q_URLDecode needs a dest buffer at least as large as the source" ); |
|
return 0; |
|
} |
|
|
|
int iDestPos = 0; |
|
for( int i=0; i < nEncodedSourceLen; ++i ) |
|
{ |
|
if ( bUsePlusForSpace && pchEncodedSource[i] == '+' ) |
|
{ |
|
pchDecodeDest[ iDestPos++ ] = ' '; |
|
} |
|
else if ( pchEncodedSource[i] == '%' ) |
|
{ |
|
// Percent signifies an encoded value, look ahead for the hex code, convert to numeric, and use that |
|
|
|
// First make sure we have 2 more chars |
|
if ( i < nEncodedSourceLen - 2 ) |
|
{ |
|
char cHexDigit1 = pchEncodedSource[i+1]; |
|
char cHexDigit2 = pchEncodedSource[i+2]; |
|
|
|
// Turn the chars into a hex value, if they are not valid, then we'll |
|
// just place the % and the following two chars direct into the string, |
|
// even though this really shouldn't happen, who knows what bad clients |
|
// may do with encoding. |
|
bool bValid = false; |
|
int iValue = iHexCharToInt( cHexDigit1 ); |
|
if ( iValue != -1 ) |
|
{ |
|
iValue *= 16; |
|
int iValue2 = iHexCharToInt( cHexDigit2 ); |
|
if ( iValue2 != -1 ) |
|
{ |
|
iValue += iValue2; |
|
pchDecodeDest[ iDestPos++ ] = iValue; |
|
bValid = true; |
|
} |
|
} |
|
|
|
if ( !bValid ) |
|
{ |
|
pchDecodeDest[ iDestPos++ ] = '%'; |
|
pchDecodeDest[ iDestPos++ ] = cHexDigit1; |
|
pchDecodeDest[ iDestPos++ ] = cHexDigit2; |
|
} |
|
} |
|
|
|
// Skip ahead |
|
i += 2; |
|
} |
|
else |
|
{ |
|
pchDecodeDest[ iDestPos++ ] = pchEncodedSource[i]; |
|
} |
|
} |
|
|
|
// We may not have extra room to NULL terminate, since this can be used on raw data, but if we do |
|
// go ahead and do it as this can avoid bugs. |
|
if ( iDestPos < nDecodeDestLen ) |
|
{ |
|
pchDecodeDest[iDestPos] = 0; |
|
} |
|
|
|
return (size_t)iDestPos; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Encodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2. |
|
// This version of the call isn't a strict RFC implementation, but uses + for space as is |
|
// the standard in HTML form encoding, despite it not being part of the RFC. |
|
// |
|
// Dest buffer should be at least as large as source buffer to guarantee room for decode. |
|
//----------------------------------------------------------------------------- |
|
void Q_URLEncode( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen ) |
|
{ |
|
return Q_URLEncodeInternal( pchDest, nDestLen, pchSource, nSourceLen, true ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Decodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2. |
|
// This version of the call isn't a strict RFC implementation, but uses + for space as is |
|
// the standard in HTML form encoding, despite it not being part of the RFC. |
|
// |
|
// Dest buffer should be at least as large as source buffer to guarantee room for decode. |
|
// Dest buffer being the same as the source buffer (decode in-place) is explicitly allowed. |
|
//----------------------------------------------------------------------------- |
|
size_t Q_URLDecode( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen ) |
|
{ |
|
return Q_URLDecodeInternal( pchDecodeDest, nDecodeDestLen, pchEncodedSource, nEncodedSourceLen, true ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Encodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2. |
|
// This version will not encode space as + (which HTML form encoding uses despite not being part of the RFC) |
|
// |
|
// Dest buffer should be at least as large as source buffer to guarantee room for decode. |
|
//----------------------------------------------------------------------------- |
|
void Q_URLEncodeRaw( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen ) |
|
{ |
|
return Q_URLEncodeInternal( pchDest, nDestLen, pchSource, nSourceLen, false ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Decodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2. |
|
// This version will not recognize + as a space (which HTML form encoding uses despite not being part of the RFC) |
|
// |
|
// Dest buffer should be at least as large as source buffer to guarantee room for decode. |
|
// Dest buffer being the same as the source buffer (decode in-place) is explicitly allowed. |
|
//----------------------------------------------------------------------------- |
|
size_t Q_URLDecodeRaw( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen ) |
|
{ |
|
return Q_URLDecodeInternal( pchDecodeDest, nDecodeDestLen, pchEncodedSource, nEncodedSourceLen, false ); |
|
} |
|
|
|
#if defined( LINUX ) || defined( _PS3 ) |
|
extern "C" void qsort_s( void *base, size_t num, size_t width, int (*compare )(void *, const void *, const void *), void * context ); |
|
#endif |
|
|
|
void V_qsort_s( void *base, size_t num, size_t width, int ( __cdecl *compare )(void *, const void *, const void *), void * context ) |
|
{ |
|
#if defined OSX |
|
// the arguments are swapped 'round on the mac - awesome, huh? |
|
return qsort_r( base, num, width, context, compare ); |
|
#else |
|
return qsort_s( base, num, width, compare, context ); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: format the time and/or date with the user's current locale |
|
// If timeVal is 0, gets the current time |
|
// |
|
// This is generally for use with chatroom dialogs, etc. which need to be |
|
// able to say "Last message received: %date% at %time%" |
|
// |
|
// Note that this uses time_t because RTime32 is not hooked-up on the client |
|
//----------------------------------------------------------------------------- |
|
bool BGetLocalFormattedDateAndTime( time_t timeVal, char *pchDate, int cubDate, char *pchTime, int cubTime ) |
|
{ |
|
if ( 0 == timeVal || timeVal < 0 ) |
|
{ |
|
// get the current time |
|
time( &timeVal ); |
|
} |
|
|
|
if ( timeVal ) |
|
{ |
|
// Convert it to our local time |
|
struct tm tmStruct; |
|
struct tm tmToDisplay = *( Plat_localtime( ( const time_t* )&timeVal, &tmStruct ) ); |
|
#ifdef POSIX |
|
if ( pchDate != NULL ) |
|
{ |
|
pchDate[ 0 ] = 0; |
|
if ( 0 == strftime( pchDate, cubDate, "%A %b %d", &tmToDisplay ) ) |
|
return false; |
|
} |
|
|
|
if ( pchTime != NULL ) |
|
{ |
|
pchTime[ 0 ] = 0; |
|
if ( 0 == strftime( pchTime, cubTime - 6, "%I:%M ", &tmToDisplay ) ) |
|
return false; |
|
|
|
// append am/pm in lower case (since strftime doesn't have a lowercase formatting option) |
|
if (tmToDisplay.tm_hour >= 12) |
|
{ |
|
Q_strcat( pchTime, "p.m.", cubTime ); |
|
} |
|
else |
|
{ |
|
Q_strcat( pchTime, "a.m.", cubTime ); |
|
} |
|
} |
|
#else // WINDOWS |
|
// convert time_t to a SYSTEMTIME |
|
SYSTEMTIME st; |
|
st.wHour = tmToDisplay.tm_hour; |
|
st.wMinute = tmToDisplay.tm_min; |
|
st.wSecond = tmToDisplay.tm_sec; |
|
st.wDay = tmToDisplay.tm_mday; |
|
st.wMonth = tmToDisplay.tm_mon + 1; |
|
st.wYear = tmToDisplay.tm_year + 1900; |
|
st.wDayOfWeek = tmToDisplay.tm_wday; |
|
st.wMilliseconds = 0; |
|
|
|
WCHAR rgwch[ MAX_PATH ]; |
|
|
|
if ( pchDate != NULL ) |
|
{ |
|
pchDate[ 0 ] = 0; |
|
if ( !GetDateFormatW( LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, rgwch, MAX_PATH ) ) |
|
return false; |
|
Q_strncpy( pchDate, CStrAutoEncode( rgwch ).ToString(), cubDate ); |
|
} |
|
|
|
if ( pchTime != NULL ) |
|
{ |
|
pchTime[ 0 ] = 0; |
|
if ( !GetTimeFormatW( LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, rgwch, MAX_PATH ) ) |
|
return false; |
|
Q_strncpy( pchTime, CStrAutoEncode( rgwch ).ToString(), cubTime ); |
|
} |
|
#endif |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
// And a couple of helpers so people don't have to remember the order of the parameters in the above function |
|
bool BGetLocalFormattedDate( time_t timeVal, char *pchDate, int cubDate ) |
|
{ |
|
return BGetLocalFormattedDateAndTime( timeVal, pchDate, cubDate, NULL, 0 ); |
|
} |
|
bool BGetLocalFormattedTime( time_t timeVal, char *pchTime, int cubTime ) |
|
{ |
|
return BGetLocalFormattedDateAndTime( timeVal, NULL, 0, pchTime, cubTime ); |
|
} |
|
|
|
// Prints out a memory dump where stuff that's ascii is human readable, etc. |
|
void V_LogMultiline( bool input, char const *label, const char *data, size_t len, CUtlString &output ) |
|
{ |
|
static const char HEX[] = "0123456789abcdef"; |
|
const char * direction = (input ? " << " : " >> "); |
|
const size_t LINE_SIZE = 24; |
|
char hex_line[LINE_SIZE * 9 / 4 + 2], asc_line[LINE_SIZE + 1]; |
|
while (len > 0) |
|
{ |
|
V_memset(asc_line, ' ', sizeof(asc_line)); |
|
V_memset(hex_line, ' ', sizeof(hex_line)); |
|
size_t line_len = MIN(len, LINE_SIZE); |
|
for (size_t i=0; i<line_len; ++i) { |
|
unsigned char ch = static_cast<unsigned char>(data[i]); |
|
asc_line[i] = ( V_isprint(ch) && !V_iscntrl(ch) ) ? data[i] : '.'; |
|
hex_line[i*2 + i/4] = HEX[ch >> 4]; |
|
hex_line[i*2 + i/4 + 1] = HEX[ch & 0xf]; |
|
} |
|
asc_line[sizeof(asc_line)-1] = 0; |
|
hex_line[sizeof(hex_line)-1] = 0; |
|
output += CFmtStr( "%s %s %s %s\n", label, direction, asc_line, hex_line ); |
|
data += line_len; |
|
len -= line_len; |
|
} |
|
} |
|
|
|
|
|
#ifdef WIN32 |
|
// Win32 CRT doesn't support the full range of UChar32, has no extended planes |
|
inline int V_iswspace( int c ) { return ( c <= 0xFFFF ) ? iswspace( (wint_t)c ) : 0; } |
|
#else |
|
#define V_iswspace(x) iswspace(x) |
|
#endif |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Slightly modified strtok. Does not modify the input string. Does |
|
// not skip over more than one separator at a time. This allows parsing |
|
// strings where tokens between separators may or may not be present: |
|
// |
|
// Door01,,,0 would be parsed as "Door01" "" "" "0" |
|
// Door01,Open,,0 would be parsed as "Door01" "Open" "" "0" |
|
// |
|
// Input : token - Returns with a token, or zero length if the token was missing. |
|
// str - String to parse. |
|
// sep - Character to use as separator. UNDONE: allow multiple separator chars |
|
// Output : Returns a pointer to the next token to be parsed. |
|
//----------------------------------------------------------------------------- |
|
const char *nexttoken(char *token, size_t nMaxTokenLen, const char *str, char sep) |
|
{ |
|
if (nMaxTokenLen < 1) |
|
{ |
|
Assert(nMaxTokenLen > 0); |
|
return NULL; |
|
} |
|
|
|
if ((str == NULL) || (*str == '\0')) |
|
{ |
|
*token = '\0'; |
|
return(NULL); |
|
} |
|
|
|
char *pTokenLast = token + nMaxTokenLen - 1; |
|
|
|
// |
|
// Copy everything up to the first separator into the return buffer. |
|
// Do not include separators in the return buffer. |
|
// |
|
while ((*str != sep) && (*str != '\0') && (token < pTokenLast)) |
|
{ |
|
*token++ = *str++; |
|
} |
|
*token = '\0'; |
|
|
|
// |
|
// Advance the pointer unless we hit the end of the input string. |
|
// |
|
if (*str == '\0') |
|
{ |
|
return(str); |
|
} |
|
|
|
return(++str); |
|
} |
|
|
|
int V_StrTrim( char *pStr ) |
|
{ |
|
char *pSource = pStr; |
|
char *pDest = pStr; |
|
|
|
// skip white space at the beginning |
|
while ( *pSource != 0 && V_isspace( *pSource ) ) |
|
{ |
|
pSource++; |
|
} |
|
|
|
// copy everything else |
|
char *pLastWhiteBlock = NULL; |
|
char *pStart = pDest; |
|
while ( *pSource != 0 ) |
|
{ |
|
*pDest = *pSource++; |
|
if ( V_isspace( *pDest ) ) |
|
{ |
|
if ( pLastWhiteBlock == NULL ) |
|
pLastWhiteBlock = pDest; |
|
} |
|
else |
|
{ |
|
pLastWhiteBlock = NULL; |
|
} |
|
pDest++; |
|
} |
|
*pDest = 0; |
|
|
|
// did we end in a whitespace block? |
|
if ( pLastWhiteBlock != NULL ) |
|
{ |
|
// yep; shorten the string |
|
pDest = pLastWhiteBlock; |
|
*pLastWhiteBlock = 0; |
|
} |
|
|
|
return pDest - pStart; |
|
} |
|
|
|
#ifdef _WIN32 |
|
int64 V_strtoi64( const char *nptr, char **endptr, int base ) |
|
{ |
|
return _strtoi64( nptr, endptr, base ); |
|
} |
|
|
|
uint64 V_strtoui64( const char *nptr, char **endptr, int base ) |
|
{ |
|
return _strtoui64( nptr, endptr, base ); |
|
} |
|
#elif POSIX |
|
int64 V_strtoi64( const char *nptr, char **endptr, int base ) |
|
{ |
|
return strtoll( nptr, endptr, base ); |
|
} |
|
|
|
uint64 V_strtoui64( const char *nptr, char **endptr, int base ) |
|
{ |
|
return strtoull( nptr, endptr, base ); |
|
} |
|
#endif |
|
|
|
|
|
struct HtmlEntity_t |
|
{ |
|
unsigned short uCharCode; |
|
const char *pchEntity; |
|
int nEntityLength; |
|
}; |
|
|
|
const static HtmlEntity_t g_BasicHTMLEntities[] = { |
|
{ '"', """, 6 }, |
|
{ '\'', "'", 6 }, |
|
{ '<', "<", 4 }, |
|
{ '>', ">", 4 }, |
|
{ '&', "&", 5 }, |
|
{ 0, NULL, 0 } // sentinel for end of array |
|
}; |
|
|
|
const static HtmlEntity_t g_WhitespaceEntities[] = { |
|
{ ' ', " ", 6 }, |
|
{ '\n', "<br>", 4 }, |
|
{ 0, NULL, 0 } // sentinel for end of array |
|
}; |
|
|
|
|
|
struct Tier1FullHTMLEntity_t |
|
{ |
|
uchar32 uCharCode; |
|
const char *pchEntity; |
|
int nEntityLength; |
|
}; |
|
|
|
|
|
#pragma warning( push ) |
|
#pragma warning( disable : 4428 ) // universal-character-name encountered in source |
|
const Tier1FullHTMLEntity_t g_Tier1_FullHTMLEntities[] = |
|
{ |
|
{ L'"', """, 6 }, |
|
{ L'\'', "'", 6 }, |
|
{ L'&', "&", 5 }, |
|
{ L'<', "<", 4 }, |
|
{ L'>', ">", 4 }, |
|
{ L' ', " ", 6 }, |
|
{ L'\u2122', "™", 7 }, |
|
{ L'\u00A9', "©", 6 }, |
|
{ L'\u00AE', "®", 5 }, |
|
{ L'\u2013', "–", 7 }, |
|
{ L'\u2014', "—", 7 }, |
|
{ L'\u20AC', "€", 6 }, |
|
{ L'\u00A1', "¡", 7 }, |
|
{ L'\u00A2', "¢", 6 }, |
|
{ L'\u00A3', "£", 7 }, |
|
{ L'\u00A4', "¤", 8 }, |
|
{ L'\u00A5', "¥", 5 }, |
|
{ L'\u00A6', "¦", 8 }, |
|
{ L'\u00A7', "§", 6 }, |
|
{ L'\u00A8', "¨", 5 }, |
|
{ L'\u00AA', "ª", 6 }, |
|
{ L'\u00AB', "«", 7 }, |
|
{ L'\u00AC', "¬", 8 }, |
|
{ L'\u00AD', "­", 5 }, |
|
{ L'\u00AF', "¯", 6 }, |
|
{ L'\u00B0', "°", 5 }, |
|
{ L'\u00B1', "±", 8 }, |
|
{ L'\u00B2', "²", 6 }, |
|
{ L'\u00B3', "³", 6 }, |
|
{ L'\u00B4', "´", 7 }, |
|
{ L'\u00B5', "µ", 7 }, |
|
{ L'\u00B6', "¶", 6 }, |
|
{ L'\u00B7', "·", 8 }, |
|
{ L'\u00B8', "¸", 7 }, |
|
{ L'\u00B9', "¹", 6 }, |
|
{ L'\u00BA', "º", 6 }, |
|
{ L'\u00BB', "»", 7 }, |
|
{ L'\u00BC', "¼", 8 }, |
|
{ L'\u00BD', "½", 8 }, |
|
{ L'\u00BE', "¾", 8 }, |
|
{ L'\u00BF', "¿", 8 }, |
|
{ L'\u00D7', "×", 7 }, |
|
{ L'\u00F7', "÷", 8 }, |
|
{ L'\u00C0', "À", 8 }, |
|
{ L'\u00C1', "Á", 8 }, |
|
{ L'\u00C2', "Â", 7 }, |
|
{ L'\u00C3', "Ã", 8 }, |
|
{ L'\u00C4', "Ä", 6 }, |
|
{ L'\u00C5', "Å", 7 }, |
|
{ L'\u00C6', "Æ", 7 }, |
|
{ L'\u00C7', "Ç", 8 }, |
|
{ L'\u00C8', "È", 8 }, |
|
{ L'\u00C9', "É", 8 }, |
|
{ L'\u00CA', "Ê", 7 }, |
|
{ L'\u00CB', "Ë", 6 }, |
|
{ L'\u00CC', "Ì", 8 }, |
|
{ L'\u00CD', "Í", 8 }, |
|
{ L'\u00CE', "Î", 7 }, |
|
{ L'\u00CF', "Ï", 6 }, |
|
{ L'\u00D0', "Ð", 5 }, |
|
{ L'\u00D1', "Ñ", 8 }, |
|
{ L'\u00D2', "Ò", 8 }, |
|
{ L'\u00D3', "Ó", 8 }, |
|
{ L'\u00D4', "Ô", 7 }, |
|
{ L'\u00D5', "Õ", 8 }, |
|
{ L'\u00D6', "Ö", 6 }, |
|
{ L'\u00D8', "Ø", 8 }, |
|
{ L'\u00D9', "Ù", 8 }, |
|
{ L'\u00DA', "Ú", 8 }, |
|
{ L'\u00DB', "Û", 7 }, |
|
{ L'\u00DC', "Ü", 6 }, |
|
{ L'\u00DD', "Ý", 8 }, |
|
{ L'\u00DE', "Þ", 7 }, |
|
{ L'\u00DF', "ß", 7 }, |
|
{ L'\u00E0', "à", 8 }, |
|
{ L'\u00E1', "á", 8 }, |
|
{ L'\u00E2', "â", 7 }, |
|
{ L'\u00E3', "ã", 8 }, |
|
{ L'\u00E4', "ä", 6 }, |
|
{ L'\u00E5', "å", 7 }, |
|
{ L'\u00E6', "æ", 7 }, |
|
{ L'\u00E7', "ç", 8 }, |
|
{ L'\u00E8', "è", 8 }, |
|
{ L'\u00E9', "é", 8 }, |
|
{ L'\u00EA', "ê", 7 }, |
|
{ L'\u00EB', "ë", 6 }, |
|
{ L'\u00EC', "ì", 8 }, |
|
{ L'\u00ED', "í", 8 }, |
|
{ L'\u00EE', "î", 7 }, |
|
{ L'\u00EF', "ï", 6 }, |
|
{ L'\u00F0', "ð", 5 }, |
|
{ L'\u00F1', "ñ", 8 }, |
|
{ L'\u00F2', "ò", 8 }, |
|
{ L'\u00F3', "ó", 8 }, |
|
{ L'\u00F4', "ô", 7 }, |
|
{ L'\u00F5', "õ", 8 }, |
|
{ L'\u00F6', "ö", 6 }, |
|
{ L'\u00F8', "ø", 8 }, |
|
{ L'\u00F9', "ù", 8 }, |
|
{ L'\u00FA', "ú", 8 }, |
|
{ L'\u00FB', "û", 7 }, |
|
{ L'\u00FC', "ü", 6 }, |
|
{ L'\u00FD', "ý", 8 }, |
|
{ L'\u00FE', "þ", 7 }, |
|
{ L'\u00FF', "ÿ", 6 }, |
|
{ 0, NULL, 0 } // sentinel for end of array |
|
}; |
|
#ifdef _WIN32 |
|
#pragma warning( pop ) |
|
#endif |
|
|
|
|
|
bool V_BasicHtmlEntityEncode( char *pDest, const int nDestSize, char const *pIn, const int nInSize, bool bPreserveWhitespace /*= false*/ ) |
|
{ |
|
Assert( nDestSize == 0 || pDest != NULL ); |
|
int iOutput = 0; |
|
for ( int iInput = 0; iInput < nInSize; ++iInput ) |
|
{ |
|
bool bReplacementDone = false; |
|
// See if the current char matches any of the basic entities |
|
for ( int i = 0; g_BasicHTMLEntities[ i ].uCharCode != 0; ++i ) |
|
{ |
|
if ( pIn[ iInput ] == g_BasicHTMLEntities[ i ].uCharCode ) |
|
{ |
|
bReplacementDone = true; |
|
for ( int j = 0; j < g_BasicHTMLEntities[ i ].nEntityLength; ++j ) |
|
{ |
|
if ( iOutput >= nDestSize - 1 ) |
|
{ |
|
pDest[ nDestSize - 1 ] = 0; |
|
return false; |
|
} |
|
pDest[ iOutput++ ] = g_BasicHTMLEntities[ i ].pchEntity[ j ]; |
|
} |
|
} |
|
} |
|
|
|
if ( bPreserveWhitespace && !bReplacementDone ) |
|
{ |
|
// See if the current char matches any of the basic entities |
|
for ( int i = 0; g_WhitespaceEntities[ i ].uCharCode != 0; ++i ) |
|
{ |
|
if ( pIn[ iInput ] == g_WhitespaceEntities[ i ].uCharCode ) |
|
{ |
|
bReplacementDone = true; |
|
for ( int j = 0; j < g_WhitespaceEntities[ i ].nEntityLength; ++j ) |
|
{ |
|
if ( iOutput >= nDestSize - 1 ) |
|
{ |
|
pDest[ nDestSize - 1 ] = 0; |
|
return false; |
|
} |
|
pDest[ iOutput++ ] = g_WhitespaceEntities[ i ].pchEntity[ j ]; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ( !bReplacementDone ) |
|
{ |
|
pDest[ iOutput++ ] = pIn[ iInput ]; |
|
} |
|
} |
|
|
|
// Null terminate the output |
|
pDest[ iOutput ] = 0; |
|
return true; |
|
} |
|
|
|
|
|
bool V_HtmlEntityDecodeToUTF8( char *pDest, const int nDestSize, char const *pIn, const int nInSize ) |
|
{ |
|
Assert( nDestSize == 0 || pDest != NULL ); |
|
int iOutput = 0; |
|
for ( int iInput = 0; iInput < nInSize && iOutput < nDestSize; ++iInput ) |
|
{ |
|
bool bReplacementDone = false; |
|
if ( pIn[ iInput ] == '&' ) |
|
{ |
|
bReplacementDone = true; |
|
|
|
uchar32 wrgchReplacement[ 2 ] = { 0, 0 }; |
|
char rgchReplacement[ 8 ]; |
|
rgchReplacement[ 0 ] = 0; |
|
|
|
const char *pchEnd = Q_strstr( pIn + iInput + 1, ";" ); |
|
if ( pchEnd ) |
|
{ |
|
if ( iInput + 1 < nInSize && pIn[ iInput + 1 ] == '#' ) |
|
{ |
|
// Numeric |
|
int iBase = 10; |
|
int iOffset = 2; |
|
if ( iInput + 3 < nInSize && pIn[ iInput + 2 ] == 'x' ) |
|
{ |
|
iBase = 16; |
|
iOffset = 3; |
|
} |
|
|
|
wrgchReplacement[ 0 ] = (uchar32)V_strtoi64( pIn + iInput + iOffset, NULL, iBase ); |
|
if ( !Q_UTF32ToUTF8( wrgchReplacement, rgchReplacement, sizeof( rgchReplacement ) ) ) |
|
{ |
|
rgchReplacement[ 0 ] = 0; |
|
} |
|
} |
|
else |
|
{ |
|
// Lookup in map |
|
const Tier1FullHTMLEntity_t *pFullEntities = g_Tier1_FullHTMLEntities; |
|
for ( int i = 0; pFullEntities[ i ].uCharCode != 0; ++i ) |
|
{ |
|
if ( nInSize - iInput - 1 >= pFullEntities[ i ].nEntityLength ) |
|
{ |
|
if ( Q_memcmp( pIn + iInput, pFullEntities[ i ].pchEntity, pFullEntities[ i ].nEntityLength ) == 0 ) |
|
{ |
|
wrgchReplacement[ 0 ] = pFullEntities[ i ].uCharCode; |
|
if ( !Q_UTF32ToUTF8( wrgchReplacement, rgchReplacement, sizeof( rgchReplacement ) ) ) |
|
{ |
|
rgchReplacement[ 0 ] = 0; |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// make sure we found a replacement. If not, skip |
|
int cchReplacement = V_strlen( rgchReplacement ); |
|
if ( cchReplacement > 0 ) |
|
{ |
|
if ( (int)cchReplacement + iOutput < nDestSize ) |
|
{ |
|
for ( int i = 0; rgchReplacement[ i ] != 0; ++i ) |
|
{ |
|
pDest[ iOutput++ ] = rgchReplacement[ i ]; |
|
} |
|
} |
|
|
|
// Skip extra space that we passed |
|
iInput += pchEnd - ( pIn + iInput ); |
|
} |
|
else |
|
{ |
|
bReplacementDone = false; |
|
} |
|
} |
|
} |
|
|
|
if ( !bReplacementDone ) |
|
{ |
|
pDest[ iOutput++ ] = pIn[ iInput ]; |
|
} |
|
} |
|
|
|
// Null terminate the output |
|
if ( iOutput < nDestSize ) |
|
{ |
|
pDest[ iOutput ] = 0; |
|
} |
|
else |
|
{ |
|
pDest[ nDestSize - 1 ] = 0; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static const char *g_pszSimpleBBCodeReplacements[] = { |
|
"[b]", "<b>", |
|
"[/b]", "</b>", |
|
"[i]", "<i>", |
|
"[/i]", "</i>", |
|
"[u]", "<u>", |
|
"[/u]", "</u>", |
|
"[s]", "<s>", |
|
"[/s]", "</s>", |
|
"[code]", "<pre>", |
|
"[/code]", "</pre>", |
|
"[h1]", "<h1>", |
|
"[/h1]", "</h1>", |
|
"[list]", "<ul>", |
|
"[/list]", "</ul>", |
|
"[*]", "<li>", |
|
"[/url]", "</a>", |
|
"[img]", "<img src=\"", |
|
"[/img]", "\"></img>", |
|
}; |
|
|
|
// Converts BBCode tags to HTML tags |
|
bool V_BBCodeToHTML( OUT_Z_CAP( nDestSize ) char *pDest, const int nDestSize, char const *pIn, const int nInSize ) |
|
{ |
|
Assert( nDestSize == 0 || pDest != NULL ); |
|
int iOutput = 0; |
|
|
|
for ( int iInput = 0; iInput < nInSize && iOutput < nDestSize && pIn[ iInput ]; ++iInput ) |
|
{ |
|
if ( pIn[ iInput ] == '[' ) |
|
{ |
|
// check simple replacements |
|
bool bFoundReplacement = false; |
|
for ( int r = 0; r < ARRAYSIZE( g_pszSimpleBBCodeReplacements ); r += 2 ) |
|
{ |
|
int nBBCodeLength = V_strlen( g_pszSimpleBBCodeReplacements[ r ] ); |
|
if ( !V_strnicmp( &pIn[ iInput ], g_pszSimpleBBCodeReplacements[ r ], nBBCodeLength ) ) |
|
{ |
|
int nHTMLReplacementLength = V_strlen( g_pszSimpleBBCodeReplacements[ r + 1 ] ); |
|
for ( int c = 0; c < nHTMLReplacementLength && iOutput < nDestSize; c++ ) |
|
{ |
|
pDest[ iOutput ] = g_pszSimpleBBCodeReplacements[ r + 1 ][ c ]; |
|
iOutput++; |
|
} |
|
iInput += nBBCodeLength - 1; |
|
bFoundReplacement = true; |
|
break; |
|
} |
|
} |
|
// check URL replacement |
|
if ( !bFoundReplacement && !V_strnicmp( &pIn[ iInput ], "[url=", 5 ) && nDestSize - iOutput > 9 ) |
|
{ |
|
iInput += 5; |
|
pDest[ iOutput++ ] = '<'; |
|
pDest[ iOutput++ ] = 'a'; |
|
pDest[ iOutput++ ] = ' '; |
|
pDest[ iOutput++ ] = 'h'; |
|
pDest[ iOutput++ ] = 'r'; |
|
pDest[ iOutput++ ] = 'e'; |
|
pDest[ iOutput++ ] = 'f'; |
|
pDest[ iOutput++ ] = '='; |
|
pDest[ iOutput++ ] = '\"'; |
|
|
|
// copy all characters up to the closing square bracket |
|
while ( pIn[ iInput ] != ']' && iInput < nInSize && iOutput < nDestSize ) |
|
{ |
|
pDest[ iOutput++ ] = pIn[ iInput++ ]; |
|
} |
|
if ( pIn[ iInput ] == ']' && nDestSize - iOutput > 2 ) |
|
{ |
|
pDest[ iOutput++ ] = '\"'; |
|
pDest[ iOutput++ ] = '>'; |
|
} |
|
bFoundReplacement = true; |
|
} |
|
// otherwise, skip over everything up to the closing square bracket |
|
if ( !bFoundReplacement ) |
|
{ |
|
while ( pIn[ iInput ] != ']' && iInput < nInSize ) |
|
{ |
|
iInput++; |
|
} |
|
} |
|
} |
|
else if ( pIn[ iInput ] == '\r' && pIn[ iInput + 1 ] == '\n' ) |
|
{ |
|
// convert carriage return and newline to a <br> |
|
if ( nDestSize - iOutput > 4 ) |
|
{ |
|
pDest[ iOutput++ ] = '<'; |
|
pDest[ iOutput++ ] = 'b'; |
|
pDest[ iOutput++ ] = 'r'; |
|
pDest[ iOutput++ ] = '>'; |
|
} |
|
iInput++; |
|
} |
|
else if ( pIn[ iInput ] == '\n' ) |
|
{ |
|
// convert newline to a <br> |
|
if ( nDestSize - iOutput > 4 ) |
|
{ |
|
pDest[ iOutput++ ] = '<'; |
|
pDest[ iOutput++ ] = 'b'; |
|
pDest[ iOutput++ ] = 'r'; |
|
pDest[ iOutput++ ] = '>'; |
|
} |
|
} |
|
else |
|
{ |
|
// copy character to destination |
|
pDest[ iOutput++ ] = pIn[ iInput ]; |
|
} |
|
} |
|
// always terminate string |
|
if ( iOutput >= nDestSize ) |
|
{ |
|
iOutput = nDestSize - 1; |
|
} |
|
pDest[ iOutput ] = 0; |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: returns true if a wide character is a "mean" space; that is, |
|
// if it is technically a space or punctuation, but causes disruptive |
|
// behavior when used in names, web pages, chat windows, etc. |
|
// |
|
// characters in this set are removed from the beginning and/or end of strings |
|
// by Q_AggressiveStripPrecedingAndTrailingWhitespaceW() |
|
//----------------------------------------------------------------------------- |
|
bool V_IsMeanUnderscoreW( wchar_t wch ) |
|
{ |
|
bool bIsMean = false; |
|
|
|
switch ( wch ) |
|
{ |
|
case L'\x005f': // low line (normal underscore) |
|
case L'\xff3f': // fullwidth low line |
|
case L'\x0332': // combining low line |
|
bIsMean = true; |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
return bIsMean; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: returns true if a wide character is a "mean" space; that is, |
|
// if it is technically a space or punctuation, but causes disruptive |
|
// behavior when used in names, web pages, chat windows, etc. |
|
// |
|
// characters in this set are removed from the beginning and/or end of strings |
|
// by Q_AggressiveStripPrecedingAndTrailingWhitespaceW() |
|
//----------------------------------------------------------------------------- |
|
bool V_IsMeanSpaceW( wchar_t wch ) |
|
{ |
|
bool bIsMean = false; |
|
|
|
switch ( wch ) |
|
{ |
|
case L'\x0080': // PADDING CHARACTER |
|
case L'\x0081': // HIGH OCTET PRESET |
|
case L'\x0082': // BREAK PERMITTED HERE |
|
case L'\x0083': // NO BREAK PERMITTED HERE |
|
case L'\x0084': // INDEX |
|
case L'\x0085': // NEXT LINE |
|
case L'\x0086': // START OF SELECTED AREA |
|
case L'\x0087': // END OF SELECTED AREA |
|
case L'\x0088': // CHARACTER TABULATION SET |
|
case L'\x0089': // CHARACTER TABULATION WITH JUSTIFICATION |
|
case L'\x008A': // LINE TABULATION SET |
|
case L'\x008B': // PARTIAL LINE FORWARD |
|
case L'\x008C': // PARTIAL LINE BACKWARD |
|
case L'\x008D': // REVERSE LINE FEED |
|
case L'\x008E': // SINGLE SHIFT 2 |
|
case L'\x008F': // SINGLE SHIFT 3 |
|
case L'\x0090': // DEVICE CONTROL STRING |
|
case L'\x0091': // PRIVATE USE |
|
case L'\x0092': // PRIVATE USE |
|
case L'\x0093': // SET TRANSMIT STATE |
|
case L'\x0094': // CANCEL CHARACTER |
|
case L'\x0095': // MESSAGE WAITING |
|
case L'\x0096': // START OF PROTECTED AREA |
|
case L'\x0097': // END OF PROTECED AREA |
|
case L'\x0098': // START OF STRING |
|
case L'\x0099': // SINGLE GRAPHIC CHARACTER INTRODUCER |
|
case L'\x009A': // SINGLE CHARACTER INTRODUCER |
|
case L'\x009B': // CONTROL SEQUENCE INTRODUCER |
|
case L'\x009C': // STRING TERMINATOR |
|
case L'\x009D': // OPERATING SYSTEM COMMAND |
|
case L'\x009E': // PRIVACY MESSAGE |
|
case L'\x009F': // APPLICATION PROGRAM COMMAND |
|
case L'\x00A0': // NO-BREAK SPACE |
|
case L'\x034F': // COMBINING GRAPHEME JOINER |
|
case L'\x2000': // EN QUAD |
|
case L'\x2001': // EM QUAD |
|
case L'\x2002': // EN SPACE |
|
case L'\x2003': // EM SPACE |
|
case L'\x2004': // THICK SPACE |
|
case L'\x2005': // MID SPACE |
|
case L'\x2006': // SIX SPACE |
|
case L'\x2007': // figure space |
|
case L'\x2008': // PUNCTUATION SPACE |
|
case L'\x2009': // THIN SPACE |
|
case L'\x200A': // HAIR SPACE |
|
case L'\x200B': // ZERO-WIDTH SPACE |
|
case L'\x200C': // ZERO-WIDTH NON-JOINER |
|
case L'\x200D': // ZERO WIDTH JOINER |
|
case L'\x2028': // LINE SEPARATOR |
|
case L'\x2029': // PARAGRAPH SEPARATOR |
|
case L'\x202F': // NARROW NO-BREAK SPACE |
|
case L'\x2060': // word joiner |
|
case L'\xFEFF': // ZERO-WIDTH NO BREAK SPACE |
|
case L'\xFFFC': // OBJECT REPLACEMENT CHARACTER |
|
bIsMean = true; |
|
break; |
|
} |
|
|
|
return bIsMean; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: tell us if a Unicode character is deprecated |
|
// |
|
// See Unicode Technical Report #20: http://www.unicode.org/reports/tr20/ |
|
// |
|
// Some characters are difficult or unreliably rendered. These characters eventually |
|
// fell out of the Unicode standard, but are abusable by users. For example, |
|
// setting "RIGHT-TO-LEFT OVERRIDE" without popping or undoing the action causes |
|
// the layout instruction to bleed into following characters in HTML renderings, |
|
// or upset layout calculations in vgui panels. |
|
// |
|
// Many games don't cope with these characters well, and end up providing opportunities |
|
// for griefing others. For example, a user might join a game with a malformed player |
|
// name and it turns out that player name can't be selected or typed into the admin |
|
// console or UI to mute, kick, or ban the disruptive player. |
|
// |
|
// Ideally, we'd perfectly support these end-to-end but we never realistically will. |
|
// The benefit of doing so far outweighs the cost, anyway. |
|
//----------------------------------------------------------------------------- |
|
bool V_IsDeprecatedW( wchar_t wch ) |
|
{ |
|
bool bIsDeprecated = false; |
|
|
|
switch ( wch ) |
|
{ |
|
case L'\x202A': // LEFT-TO-RIGHT EMBEDDING |
|
case L'\x202B': // RIGHT-TO-LEFT EMBEDDING |
|
case L'\x202C': // POP DIRECTIONAL FORMATTING |
|
case L'\x202D': // LEFT-TO-RIGHT OVERRIDE |
|
case L'\x202E': // RIGHT-TO-LEFT OVERRIDE |
|
|
|
case L'\x206A': // INHIBIT SYMMETRIC SWAPPING |
|
case L'\x206B': // ACTIVATE SYMMETRIC SWAPPING |
|
case L'\x206C': // INHIBIT ARABIC FORM SHAPING |
|
case L'\x206D': // ACTIVATE ARABIC FORM SHAPING |
|
case L'\x206E': // NATIONAL DIGIT SHAPES |
|
case L'\x206F': // NOMINAL DIGIT SHAPES |
|
bIsDeprecated = true; |
|
} |
|
|
|
return bIsDeprecated; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// returns true if the character is allowed in a DNS doman name, false otherwise |
|
//----------------------------------------------------------------------------- |
|
bool V_IsValidDomainNameCharacter( const char *pch, int *pAdvanceBytes ) |
|
{ |
|
if ( pAdvanceBytes ) |
|
*pAdvanceBytes = 0; |
|
|
|
|
|
// We allow unicode in Domain Names without the an encoding unless it corresponds to |
|
// a whitespace or control sequence or something we think is an underscore looking thing. |
|
// If this character is the start of a UTF-8 sequence, try decoding it. |
|
unsigned char ch = (unsigned char)*pch; |
|
if ( ( ch & 0xC0 ) == 0xC0 ) |
|
{ |
|
uchar32 rgch32Buf; |
|
bool bError = false; |
|
int iAdvance = Q_UTF8ToUChar32( pch, rgch32Buf, bError ); |
|
if ( bError || iAdvance == 0 ) |
|
{ |
|
// Invalid UTF8 sequence, lets consider that invalid |
|
return false; |
|
} |
|
|
|
if ( pAdvanceBytes ) |
|
*pAdvanceBytes = iAdvance; |
|
|
|
if ( iAdvance ) |
|
{ |
|
// Ick. Want uchar32 versions of unicode character classification functions. |
|
// Really would like Q_IsWhitespace32 and Q_IsNonPrintable32, but this is OK. |
|
if ( rgch32Buf < 0x10000 && ( V_IsMeanSpaceW( (wchar_t)rgch32Buf ) || V_IsDeprecatedW( (wchar_t)rgch32Buf ) || V_IsMeanUnderscoreW( (wchar_t)rgch32Buf ) ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
else |
|
{ |
|
// Unreachable but would be invalid utf8 |
|
return false; |
|
} |
|
} |
|
else |
|
{ |
|
// Was not unicode |
|
if ( pAdvanceBytes ) |
|
*pAdvanceBytes = 1; |
|
|
|
// The only allowable non-unicode chars are a-z A-Z 0-9 and - |
|
if ( ( ch >= 'a' && ch <= 'z' ) || ( ch >= 'A' && ch <= 'Z' ) || ( ch >= '0' && ch <= '9' ) || ch == '-' || ch == '.' ) |
|
return true; |
|
|
|
return false; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// returns true if the character is allowed in a URL, false otherwise |
|
//----------------------------------------------------------------------------- |
|
bool V_IsValidURLCharacter( const char *pch, int *pAdvanceBytes ) |
|
{ |
|
if ( pAdvanceBytes ) |
|
*pAdvanceBytes = 0; |
|
|
|
|
|
// We allow unicode in URLs unless it corresponds to a whitespace or control sequence. |
|
// If this character is the start of a UTF-8 sequence, try decoding it. |
|
unsigned char ch = (unsigned char)*pch; |
|
if ( ( ch & 0xC0 ) == 0xC0 ) |
|
{ |
|
uchar32 rgch32Buf; |
|
bool bError = false; |
|
int iAdvance = Q_UTF8ToUChar32( pch, rgch32Buf, bError ); |
|
if ( bError || iAdvance == 0 ) |
|
{ |
|
// Invalid UTF8 sequence, lets consider that invalid |
|
return false; |
|
} |
|
|
|
if ( pAdvanceBytes ) |
|
*pAdvanceBytes = iAdvance; |
|
|
|
if ( iAdvance ) |
|
{ |
|
// Ick. Want uchar32 versions of unicode character classification functions. |
|
// Really would like Q_IsWhitespace32 and Q_IsNonPrintable32, but this is OK. |
|
if ( rgch32Buf < 0x10000 && ( V_IsMeanSpaceW( (wchar_t)rgch32Buf ) || V_IsDeprecatedW( (wchar_t)rgch32Buf ) ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
else |
|
{ |
|
// Unreachable but would be invalid utf8 |
|
return false; |
|
} |
|
} |
|
else |
|
{ |
|
// Was not unicode |
|
if ( pAdvanceBytes ) |
|
*pAdvanceBytes = 1; |
|
|
|
// Spaces, control characters, quotes, and angle brackets are not legal URL characters. |
|
if ( ch <= 32 || ch == 127 || ch == '"' || ch == '<' || ch == '>' ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: helper function to get a domain from a url |
|
// Checks both standard url and steam://openurl/<url> |
|
//----------------------------------------------------------------------------- |
|
bool V_ExtractDomainFromURL( const char *pchURL, char *pchDomain, int cchDomain ) |
|
{ |
|
pchDomain[ 0 ] = 0; |
|
|
|
static const char *k_pchSteamOpenUrl = "steam://openurl/"; |
|
static const char *k_pchSteamOpenUrlExt = "steam://openurl_external/"; |
|
|
|
const char *pchOpenUrlSuffix = StringAfterPrefix( pchURL, k_pchSteamOpenUrl ); |
|
if ( pchOpenUrlSuffix == NULL ) |
|
pchOpenUrlSuffix = StringAfterPrefix( pchURL, k_pchSteamOpenUrlExt ); |
|
|
|
if ( pchOpenUrlSuffix ) |
|
pchURL = pchOpenUrlSuffix; |
|
|
|
if ( !pchURL || pchURL[ 0 ] == '\0' ) |
|
return false; |
|
|
|
const char *pchDoubleSlash = strstr( pchURL, "//" ); |
|
|
|
// Put the domain and everything after into pchDomain. |
|
// We'll find where to terminate it later. |
|
if ( pchDoubleSlash ) |
|
{ |
|
// Skip the slashes |
|
pchDoubleSlash += 2; |
|
|
|
// If that's all there was, then there's no domain here. Bail. |
|
if ( *pchDoubleSlash == '\0' ) |
|
{ |
|
return false; |
|
} |
|
|
|
// Skip any extra slashes |
|
// ex: http:///steamcommunity.com/ |
|
while ( *pchDoubleSlash == '/' ) |
|
{ |
|
pchDoubleSlash++; |
|
} |
|
|
|
Q_strncpy( pchDomain, pchDoubleSlash, cchDomain ); |
|
} |
|
else |
|
{ |
|
// No double slash, so pchURL has no protocol. |
|
Q_strncpy( pchDomain, pchURL, cchDomain ); |
|
} |
|
|
|
// First character has to be valid |
|
if ( *pchDomain == '?' || *pchDomain == '\0' ) |
|
{ |
|
return false; |
|
} |
|
|
|
// terminate the domain after the first non domain char |
|
int iAdvance = 0; |
|
int iStrLen = 0; |
|
char cLast = 0; |
|
while ( pchDomain[ iStrLen ] ) |
|
{ |
|
if ( !V_IsValidDomainNameCharacter( pchDomain + iStrLen, &iAdvance ) || ( pchDomain[ iStrLen ] == '.' && cLast == '.' ) ) |
|
{ |
|
pchDomain[ iStrLen ] = 0; |
|
break; |
|
} |
|
|
|
cLast = pchDomain[ iStrLen ]; |
|
iStrLen += iAdvance; |
|
} |
|
|
|
return ( pchDomain[ 0 ] != 0 ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: helper function to get a domain from a url |
|
//----------------------------------------------------------------------------- |
|
bool V_URLContainsDomain( const char *pchURL, const char *pchDomain ) |
|
{ |
|
char rgchExtractedDomain[ 2048 ]; |
|
if ( V_ExtractDomainFromURL( pchURL, rgchExtractedDomain, sizeof( rgchExtractedDomain ) ) ) |
|
{ |
|
// see if the last part of the domain matches what we extracted |
|
int cchExtractedDomain = V_strlen( rgchExtractedDomain ); |
|
if ( pchDomain[ 0 ] == '.' ) |
|
{ |
|
++pchDomain; // If the domain has a leading '.', skip it. The test below assumes there is none. |
|
} |
|
int cchDomain = V_strlen( pchDomain ); |
|
|
|
if ( cchDomain > cchExtractedDomain ) |
|
{ |
|
return false; |
|
} |
|
else if ( cchExtractedDomain >= cchDomain ) |
|
{ |
|
// If the actual domain is longer than what we're searching for, the character previous |
|
// to the domain we're searching for must be a period |
|
if ( cchExtractedDomain > cchDomain && rgchExtractedDomain[ cchExtractedDomain - cchDomain - 1 ] != '.' ) |
|
return false; |
|
|
|
if ( 0 == V_stricmp( rgchExtractedDomain + cchExtractedDomain - cchDomain, pchDomain ) ) |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Strips all HTML tags not specified in rgszPreserveTags |
|
// Does some additional formatting, like turning <li> into * when not preserving that tag, |
|
// and auto-closing unclosed tags if they aren't specified in rgszNoCloseTags |
|
//----------------------------------------------------------------------------- |
|
void V_StripAndPreserveHTMLCore( CUtlBuffer *pbuffer, const char *pchHTML, const char **rgszPreserveTags, uint cPreserveTags, const char **rgszNoCloseTags, uint cNoCloseTags, uint cMaxResultSize ) |
|
{ |
|
uint cHTMLCur = 0; |
|
|
|
bool bStripNewLines = true; |
|
if ( cPreserveTags > 0 ) |
|
{ |
|
for ( uint i = 0; i < cPreserveTags; ++i ) |
|
{ |
|
if ( !Q_stricmp( rgszPreserveTags[ i ], "\n" ) ) |
|
bStripNewLines = false; |
|
} |
|
} |
|
|
|
//state- |
|
bool bInStrippedTag = false; |
|
bool bInStrippedContentTag = false; |
|
bool bInPreservedTag = false; |
|
bool bInListItemTag = false; |
|
bool bLastCharWasWhitespace = true; //set to true to strip leading whitespace |
|
bool bInComment = false; |
|
bool bInDoubleQuote = false; |
|
bool bInSingleQuote = false; |
|
int nPreTagDepth = 0; |
|
CUtlVector< const char* > vecTagStack; |
|
|
|
for ( int iContents = 0; pchHTML[ iContents ] != '\0' && cHTMLCur < cMaxResultSize; iContents++ ) |
|
{ |
|
char c = pchHTML[ iContents ]; |
|
|
|
// If we are entering a comment, flag as such and skip past the begin comment tag |
|
const char *pchCur = &pchHTML[ iContents ]; |
|
if ( !Q_strnicmp( pchCur, "<!--", 4 ) ) |
|
{ |
|
bInComment = true; |
|
iContents += 3; |
|
continue; |
|
} |
|
|
|
// If we are in a comment, check if we are exiting |
|
if ( bInComment ) |
|
{ |
|
if ( !Q_strnicmp( pchCur, "-->", 3 ) ) |
|
{ |
|
bInComment = false; |
|
iContents += 2; |
|
continue; |
|
} |
|
else |
|
{ |
|
continue; |
|
} |
|
} |
|
|
|
if ( bInStrippedTag || bInPreservedTag ) |
|
{ |
|
// we're inside a tag, keep stripping/preserving until we get to a > |
|
if ( bInPreservedTag ) |
|
pbuffer->PutChar( c ); |
|
|
|
// While inside a tag, ignore ending > properties if they are inside a property value in "" or '' |
|
if ( c == '"' ) |
|
{ |
|
if ( bInDoubleQuote ) |
|
bInDoubleQuote = false; |
|
else |
|
bInDoubleQuote = true; |
|
} |
|
|
|
if ( c == '\'' ) |
|
{ |
|
if ( bInSingleQuote ) |
|
bInSingleQuote = false; |
|
else |
|
bInSingleQuote = true; |
|
} |
|
|
|
if ( !bInDoubleQuote && !bInSingleQuote && c == '>' ) |
|
{ |
|
if ( bInPreservedTag ) |
|
bLastCharWasWhitespace = false; |
|
|
|
bInPreservedTag = false; |
|
bInStrippedTag = false; |
|
} |
|
} |
|
else if ( bInStrippedContentTag ) |
|
{ |
|
if ( c == '<' && !Q_strnicmp( pchCur, "</script>", 9 ) ) |
|
{ |
|
bInStrippedContentTag = false; |
|
iContents += 8; |
|
continue; |
|
} |
|
else |
|
{ |
|
continue; |
|
} |
|
} |
|
else if ( c & 0x80 && !bInStrippedContentTag ) |
|
{ |
|
// start/continuation of a multibyte sequence, copy to output. |
|
int nMultibyteRemaining = 0; |
|
if ( ( c & 0xF8 ) == 0xF0 ) // first 5 bits are 11110 |
|
nMultibyteRemaining = 3; |
|
else if ( ( c & 0xF0 ) == 0xE0 ) // first 4 bits are 1110 |
|
nMultibyteRemaining = 2; |
|
else if ( ( c & 0xE0 ) == 0xC0 ) // first 3 bits are 110 |
|
nMultibyteRemaining = 1; |
|
|
|
// cHTMLCur is in characters, so just +1 |
|
cHTMLCur++; |
|
pbuffer->Put( pchCur, 1 + nMultibyteRemaining ); |
|
|
|
iContents += nMultibyteRemaining; |
|
|
|
// Need to determine if we just added whitespace or not |
|
wchar_t rgwch[ 3 ] = { 0 }; |
|
Q_UTF8CharsToWString( pchCur, 1, rgwch, sizeof( rgwch ) ); |
|
if ( !V_iswspace( rgwch[ 0 ] ) ) |
|
bLastCharWasWhitespace = false; |
|
else |
|
bLastCharWasWhitespace = true; |
|
} |
|
else |
|
{ |
|
//not in a multibyte sequence- do our parsing/stripping |
|
if ( c == '<' ) |
|
{ |
|
if ( !rgszPreserveTags || cPreserveTags == 0 ) |
|
{ |
|
//not preserving any tags, just strip it |
|
bInStrippedTag = true; |
|
} |
|
else |
|
{ |
|
//look ahead, is this our kind of tag? |
|
bool bPreserve = false; |
|
bool bEndTag = false; |
|
const char *szTagStart = &pchHTML[ iContents + 1 ]; |
|
// if it's a close tag, skip the / |
|
if ( *szTagStart == '/' ) |
|
{ |
|
bEndTag = true; |
|
szTagStart++; |
|
} |
|
if ( Q_strnicmp( "script", szTagStart, 6 ) == 0 ) |
|
{ |
|
bInStrippedTag = true; |
|
bInStrippedContentTag = true; |
|
} |
|
else |
|
{ |
|
//see if this tag is one we want to preserve |
|
for ( uint iTag = 0; iTag < cPreserveTags; iTag++ ) |
|
{ |
|
const char *szTag = rgszPreserveTags[ iTag ]; |
|
int cchTag = Q_strlen( szTag ); |
|
|
|
//make sure characters match, and are followed by some non-alnum char |
|
// so "i" can match <i> or <i class=...>, but not <img> |
|
if ( Q_strnicmp( szTag, szTagStart, cchTag ) == 0 && !V_isalnum( szTagStart[ cchTag ] ) ) |
|
{ |
|
bPreserve = true; |
|
if ( bEndTag ) |
|
{ |
|
// ending a paragraph tag is optional. If we were expecting to find one, and didn't, skip |
|
if ( Q_stricmp( szTag, "p" ) != 0 ) |
|
{ |
|
while ( vecTagStack.Count() > 0 && Q_stricmp( vecTagStack[ vecTagStack.Count() - 1 ], "p" ) == 0 ) |
|
{ |
|
vecTagStack.Remove( vecTagStack.Count() - 1 ); |
|
} |
|
} |
|
|
|
if ( vecTagStack.Count() > 0 && vecTagStack[ vecTagStack.Count() - 1 ] == szTag ) |
|
{ |
|
vecTagStack.Remove( vecTagStack.Count() - 1 ); |
|
|
|
if ( Q_stricmp( szTag, "pre" ) == 0 ) |
|
{ |
|
nPreTagDepth--; |
|
if ( nPreTagDepth < 0 ) |
|
{ |
|
nPreTagDepth = 0; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// don't preserve this unbalanced tag. All open tags will be closed at the end of the blurb |
|
bPreserve = false; |
|
} |
|
} |
|
else |
|
{ |
|
bool bNoCloseTag = false; |
|
for ( uint iNoClose = 0; iNoClose < cNoCloseTags; iNoClose++ ) |
|
{ |
|
if ( Q_stricmp( szTag, rgszNoCloseTags[ iNoClose ] ) == 0 ) |
|
{ |
|
bNoCloseTag = true; |
|
break; |
|
} |
|
} |
|
|
|
if ( !bNoCloseTag ) |
|
{ |
|
vecTagStack.AddToTail( szTag ); |
|
if ( Q_stricmp( szTag, "pre" ) == 0 ) |
|
{ |
|
nPreTagDepth++; |
|
} |
|
} |
|
} |
|
break; |
|
} |
|
} |
|
if ( !bPreserve ) |
|
{ |
|
bInStrippedTag = true; |
|
} |
|
else |
|
{ |
|
bInPreservedTag = true; |
|
pbuffer->PutChar( c ); |
|
} |
|
|
|
} |
|
} |
|
if ( bInStrippedTag ) |
|
{ |
|
const char *szTagStart = &pchHTML[ iContents ]; |
|
if ( Q_strnicmp( szTagStart, "<li>", Q_strlen( "<li>" ) ) == 0 ) |
|
{ |
|
if ( bInListItemTag ) |
|
{ |
|
pbuffer->PutChar( ';' ); |
|
cHTMLCur++; |
|
bInListItemTag = false; |
|
} |
|
|
|
if ( !bLastCharWasWhitespace ) |
|
{ |
|
pbuffer->PutChar( ' ' ); |
|
cHTMLCur++; |
|
} |
|
|
|
pbuffer->PutChar( '*' ); |
|
pbuffer->PutChar( ' ' ); |
|
cHTMLCur += 2; |
|
bInListItemTag = true; |
|
} |
|
else if ( !bLastCharWasWhitespace ) |
|
{ |
|
|
|
if ( bInListItemTag ) |
|
{ |
|
char cLastChar = ' '; |
|
|
|
if ( pbuffer->TellPut() > 0 ) |
|
{ |
|
cLastChar = ( ( (char*)pbuffer->Base() ) + pbuffer->TellPut() - 1 )[ 0 ]; |
|
} |
|
if ( cLastChar != '.' && cLastChar != '?' && cLastChar != '!' ) |
|
{ |
|
pbuffer->PutChar( ';' ); |
|
cHTMLCur++; |
|
} |
|
bInListItemTag = false; |
|
} |
|
|
|
//we're decided to remove a tag, simulate a space in the original text |
|
pbuffer->PutChar( ' ' ); |
|
cHTMLCur++; |
|
} |
|
bLastCharWasWhitespace = true; |
|
} |
|
} |
|
else |
|
{ |
|
//just a normal character, nothin' special. |
|
if ( nPreTagDepth == 0 && V_isspace( c ) && ( bStripNewLines || c != '\n' ) ) |
|
{ |
|
if ( !bLastCharWasWhitespace ) |
|
{ |
|
//replace any block of whitespace with a single space |
|
cHTMLCur++; |
|
pbuffer->PutChar( ' ' ); |
|
bLastCharWasWhitespace = true; |
|
} |
|
// don't put anything for whitespace if the previous character was whitespace |
|
// (effectively trimming all blocks of whitespace down to a single ' ') |
|
} |
|
else |
|
{ |
|
cHTMLCur++; |
|
pbuffer->PutChar( c ); |
|
bLastCharWasWhitespace = false; |
|
} |
|
} |
|
} |
|
} |
|
if ( cHTMLCur >= cMaxResultSize ) |
|
{ |
|
// we terminated because the blurb was full. Add a '...' to the end |
|
pbuffer->Put( "...", 3 ); |
|
} |
|
//close any preserved tags that were open at the end. |
|
FOR_EACH_VEC_BACK( vecTagStack, iTagStack ) |
|
{ |
|
pbuffer->PutChar( '<' ); |
|
pbuffer->PutChar( '/' ); |
|
pbuffer->Put( vecTagStack[ iTagStack ], Q_strlen( vecTagStack[ iTagStack ] ) ); |
|
pbuffer->PutChar( '>' ); |
|
} |
|
|
|
// Null terminate |
|
pbuffer->PutChar( '\0' ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Strips all HTML tags not specified in rgszPreserveTags |
|
// Does some additional formatting, like turning <li> into * when not preserving that tag |
|
//----------------------------------------------------------------------------- |
|
void V_StripAndPreserveHTML( CUtlBuffer *pbuffer, const char *pchHTML, const char **rgszPreserveTags, uint cPreserveTags, uint cMaxResultSize ) |
|
{ |
|
const char *rgszNoCloseTags[] = { "br", "img" }; |
|
V_StripAndPreserveHTMLCore( pbuffer, pchHTML, rgszPreserveTags, cPreserveTags, rgszNoCloseTags, V_ARRAYSIZE( rgszNoCloseTags ), cMaxResultSize ); |
|
} |
|
|
|
|
|
|