Team Fortress 2 Source Code as on 22/4/2020
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.

4333 lines
111 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: String Tools
  4. //
  5. //===========================================================================//
  6. // These are redefined in the project settings to prevent anyone from using them.
  7. // We in this module are of a higher caste and thus are privileged in their use.
  8. #ifdef strncpy
  9. #undef strncpy
  10. #endif
  11. #ifdef _snprintf
  12. #undef _snprintf
  13. #endif
  14. #if defined( sprintf )
  15. #undef sprintf
  16. #endif
  17. #if defined( vsprintf )
  18. #undef vsprintf
  19. #endif
  20. #ifdef _vsnprintf
  21. #ifdef _WIN32
  22. #undef _vsnprintf
  23. #endif
  24. #endif
  25. #ifdef vsnprintf
  26. #ifndef _WIN32
  27. #undef vsnprintf
  28. #endif
  29. #endif
  30. #if defined( strcat )
  31. #undef strcat
  32. #endif
  33. #ifdef strncat
  34. #undef strncat
  35. #endif
  36. // NOTE: I have to include stdio + stdarg first so vsnprintf gets compiled in
  37. #include <stdio.h>
  38. #include <stdarg.h>
  39. #ifdef POSIX
  40. #include <iconv.h>
  41. #include <ctype.h>
  42. #include <unistd.h>
  43. #include <stdlib.h>
  44. #define _getcwd getcwd
  45. #elif _WIN32
  46. #include <direct.h>
  47. #if !defined( _X360 )
  48. #define WIN32_LEAN_AND_MEAN
  49. #include <windows.h>
  50. #endif
  51. #endif
  52. #ifdef _WIN32
  53. #ifndef CP_UTF8
  54. #define CP_UTF8 65001
  55. #endif
  56. #endif
  57. #include "tier0/dbg.h"
  58. #include "tier1/strtools.h"
  59. #include <string.h>
  60. #include <stdlib.h>
  61. #include <time.h>
  62. #include "tier0/basetypes.h"
  63. #include "tier1/utldict.h"
  64. #include "tier1/utlbuffer.h"
  65. #include "tier1/utlstring.h"
  66. #include "tier1/fmtstr.h"
  67. #if defined( _X360 )
  68. #include "xbox/xbox_win32stubs.h"
  69. #endif
  70. #include "tier0/memdbgon.h"
  71. static int FastToLower( char c )
  72. {
  73. int i = (unsigned char) c;
  74. if ( i < 0x80 )
  75. {
  76. // Brutally fast branchless ASCII tolower():
  77. i += (((('A'-1) - i) & (i - ('Z'+1))) >> 26) & 0x20;
  78. }
  79. else
  80. {
  81. i += isupper( i ) ? 0x20 : 0;
  82. }
  83. return i;
  84. }
  85. void _V_memset (const char* file, int line, void *dest, int fill, int count)
  86. {
  87. Assert( count >= 0 );
  88. AssertValidWritePtr( dest, count );
  89. memset(dest,fill,count);
  90. }
  91. void _V_memcpy (const char* file, int line, void *dest, const void *src, int count)
  92. {
  93. Assert( count >= 0 );
  94. AssertValidReadPtr( src, count );
  95. AssertValidWritePtr( dest, count );
  96. memcpy( dest, src, count );
  97. }
  98. void _V_memmove(const char* file, int line, void *dest, const void *src, int count)
  99. {
  100. Assert( count >= 0 );
  101. AssertValidReadPtr( src, count );
  102. AssertValidWritePtr( dest, count );
  103. memmove( dest, src, count );
  104. }
  105. int _V_memcmp (const char* file, int line, const void *m1, const void *m2, int count)
  106. {
  107. Assert( count >= 0 );
  108. AssertValidReadPtr( m1, count );
  109. AssertValidReadPtr( m2, count );
  110. return memcmp( m1, m2, count );
  111. }
  112. int _V_strlen(const char* file, int line, const char *str)
  113. {
  114. AssertValidStringPtr(str);
  115. return strlen( str );
  116. }
  117. void _V_strcpy (const char* file, int line, char *dest, const char *src)
  118. {
  119. AssertValidWritePtr(dest);
  120. AssertValidStringPtr(src);
  121. strcpy( dest, src );
  122. }
  123. int _V_wcslen(const char* file, int line, const wchar_t *pwch)
  124. {
  125. return wcslen( pwch );
  126. }
  127. char *_V_strrchr(const char* file, int line, const char *s, char c)
  128. {
  129. AssertValidStringPtr( s );
  130. int len = V_strlen(s);
  131. s += len;
  132. while (len--)
  133. if (*--s == c) return (char *)s;
  134. return 0;
  135. }
  136. int _V_strcmp (const char* file, int line, const char *s1, const char *s2)
  137. {
  138. AssertValidStringPtr( s1 );
  139. AssertValidStringPtr( s2 );
  140. return strcmp( s1, s2 );
  141. }
  142. int _V_wcscmp (const char* file, int line, const wchar_t *s1, const wchar_t *s2)
  143. {
  144. AssertValidReadPtr( s1 );
  145. AssertValidReadPtr( s2 );
  146. while ( *s1 == *s2 )
  147. {
  148. if ( !*s1 )
  149. return 0; // strings are equal
  150. s1++;
  151. s2++;
  152. }
  153. return *s1 > *s2 ? 1 : -1; // strings not equal
  154. }
  155. char *_V_strstr(const char* file, int line, const char *s1, const char *search )
  156. {
  157. AssertValidStringPtr( s1 );
  158. AssertValidStringPtr( search );
  159. #if defined( _X360 )
  160. return (char *)strstr( (char *)s1, search );
  161. #else
  162. return (char *)strstr( s1, search );
  163. #endif
  164. }
  165. wchar_t *_V_wcsupr (const char* file, int line, wchar_t *start)
  166. {
  167. return _wcsupr( start );
  168. }
  169. wchar_t *_V_wcslower (const char* file, int line, wchar_t *start)
  170. {
  171. return _wcslwr(start);
  172. }
  173. char *V_strupr( char *start )
  174. {
  175. unsigned char *str = (unsigned char*)start;
  176. while( *str )
  177. {
  178. if ( (unsigned char)(*str - 'a') <= ('z' - 'a') )
  179. *str -= 'a' - 'A';
  180. else if ( (unsigned char)*str >= 0x80 ) // non-ascii, fall back to CRT
  181. *str = toupper( *str );
  182. str++;
  183. }
  184. return start;
  185. }
  186. char *V_strlower( char *start )
  187. {
  188. unsigned char *str = (unsigned char*)start;
  189. while( *str )
  190. {
  191. if ( (unsigned char)(*str - 'A') <= ('Z' - 'A') )
  192. *str += 'a' - 'A';
  193. else if ( (unsigned char)*str >= 0x80 ) // non-ascii, fall back to CRT
  194. *str = tolower( *str );
  195. str++;
  196. }
  197. return start;
  198. }
  199. char *V_strnlwr(char *s, size_t count)
  200. {
  201. // Assert( count >= 0 ); tautology since size_t is unsigned
  202. AssertValidStringPtr( s, count );
  203. char* pRet = s;
  204. if ( !s || !count )
  205. return s;
  206. while ( -- count > 0 )
  207. {
  208. if ( !*s )
  209. return pRet; // reached end of string
  210. *s = tolower( *s );
  211. ++s;
  212. }
  213. *s = 0; // null-terminate original string at "count-1"
  214. return pRet;
  215. }
  216. int V_stricmp( const char *str1, const char *str2 )
  217. {
  218. // It is not uncommon to compare a string to itself. See
  219. // VPanelWrapper::GetPanel which does this a lot. Since stricmp
  220. // is expensive and pointer comparison is cheap, this simple test
  221. // can save a lot of cycles, and cache pollution.
  222. if ( str1 == str2 )
  223. {
  224. return 0;
  225. }
  226. const unsigned char *s1 = (const unsigned char*)str1;
  227. const unsigned char *s2 = (const unsigned char*)str2;
  228. for ( ; *s1; ++s1, ++s2 )
  229. {
  230. if ( *s1 != *s2 )
  231. {
  232. // in ascii char set, lowercase = uppercase | 0x20
  233. unsigned char c1 = *s1 | 0x20;
  234. unsigned char c2 = *s2 | 0x20;
  235. if ( c1 != c2 || (unsigned char)(c1 - 'a') > ('z' - 'a') )
  236. {
  237. // if non-ascii mismatch, fall back to CRT for locale
  238. if ( (c1 | c2) >= 0x80 ) return stricmp( (const char*)s1, (const char*)s2 );
  239. // ascii mismatch. only use the | 0x20 value if alphabetic.
  240. if ((unsigned char)(c1 - 'a') > ('z' - 'a')) c1 = *s1;
  241. if ((unsigned char)(c2 - 'a') > ('z' - 'a')) c2 = *s2;
  242. return c1 > c2 ? 1 : -1;
  243. }
  244. }
  245. }
  246. return *s2 ? -1 : 0;
  247. }
  248. int V_strnicmp( const char *str1, const char *str2, int n )
  249. {
  250. const unsigned char *s1 = (const unsigned char*)str1;
  251. const unsigned char *s2 = (const unsigned char*)str2;
  252. for ( ; n > 0 && *s1; --n, ++s1, ++s2 )
  253. {
  254. if ( *s1 != *s2 )
  255. {
  256. // in ascii char set, lowercase = uppercase | 0x20
  257. unsigned char c1 = *s1 | 0x20;
  258. unsigned char c2 = *s2 | 0x20;
  259. if ( c1 != c2 || (unsigned char)(c1 - 'a') > ('z' - 'a') )
  260. {
  261. // if non-ascii mismatch, fall back to CRT for locale
  262. if ( (c1 | c2) >= 0x80 ) return strnicmp( (const char*)s1, (const char*)s2, n );
  263. // ascii mismatch. only use the | 0x20 value if alphabetic.
  264. if ((unsigned char)(c1 - 'a') > ('z' - 'a')) c1 = *s1;
  265. if ((unsigned char)(c2 - 'a') > ('z' - 'a')) c2 = *s2;
  266. return c1 > c2 ? 1 : -1;
  267. }
  268. }
  269. }
  270. return (n > 0 && *s2) ? -1 : 0;
  271. }
  272. int V_strncmp( const char *s1, const char *s2, int count )
  273. {
  274. Assert( count >= 0 );
  275. AssertValidStringPtr( s1, count );
  276. AssertValidStringPtr( s2, count );
  277. while ( count > 0 )
  278. {
  279. if ( *s1 != *s2 )
  280. return (unsigned char)*s1 < (unsigned char)*s2 ? -1 : 1; // string different
  281. if ( *s1 == '\0' )
  282. return 0; // null terminator hit - strings the same
  283. s1++;
  284. s2++;
  285. count--;
  286. }
  287. return 0; // count characters compared the same
  288. }
  289. const char *StringAfterPrefix( const char *str, const char *prefix )
  290. {
  291. AssertValidStringPtr( str );
  292. AssertValidStringPtr( prefix );
  293. do
  294. {
  295. if ( !*prefix )
  296. return str;
  297. }
  298. while ( FastToLower( *str++ ) == FastToLower( *prefix++ ) );
  299. return NULL;
  300. }
  301. const char *StringAfterPrefixCaseSensitive( const char *str, const char *prefix )
  302. {
  303. AssertValidStringPtr( str );
  304. AssertValidStringPtr( prefix );
  305. do
  306. {
  307. if ( !*prefix )
  308. return str;
  309. }
  310. while ( *str++ == *prefix++ );
  311. return NULL;
  312. }
  313. int64 V_atoi64( const char *str )
  314. {
  315. AssertValidStringPtr( str );
  316. int64 val;
  317. int64 sign;
  318. int64 c;
  319. Assert( str );
  320. if (*str == '-')
  321. {
  322. sign = -1;
  323. str++;
  324. }
  325. else if (*str == '+')
  326. {
  327. sign = 1;
  328. str++;
  329. }
  330. else
  331. {
  332. sign = 1;
  333. }
  334. val = 0;
  335. //
  336. // check for hex
  337. //
  338. if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') )
  339. {
  340. str += 2;
  341. while (1)
  342. {
  343. c = *str++;
  344. if (c >= '0' && c <= '9')
  345. val = (val<<4) + c - '0';
  346. else if (c >= 'a' && c <= 'f')
  347. val = (val<<4) + c - 'a' + 10;
  348. else if (c >= 'A' && c <= 'F')
  349. val = (val<<4) + c - 'A' + 10;
  350. else
  351. return val*sign;
  352. }
  353. }
  354. //
  355. // check for character
  356. //
  357. if (str[0] == '\'')
  358. {
  359. return sign * str[1];
  360. }
  361. //
  362. // assume decimal
  363. //
  364. while (1)
  365. {
  366. c = *str++;
  367. if (c <'0' || c > '9')
  368. return val*sign;
  369. val = val*10 + c - '0';
  370. }
  371. return 0;
  372. }
  373. uint64 V_atoui64( const char *str )
  374. {
  375. AssertValidStringPtr( str );
  376. uint64 val;
  377. uint64 c;
  378. Assert( str );
  379. val = 0;
  380. //
  381. // check for hex
  382. //
  383. if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') )
  384. {
  385. str += 2;
  386. while (1)
  387. {
  388. c = *str++;
  389. if (c >= '0' && c <= '9')
  390. val = (val<<4) + c - '0';
  391. else if (c >= 'a' && c <= 'f')
  392. val = (val<<4) + c - 'a' + 10;
  393. else if (c >= 'A' && c <= 'F')
  394. val = (val<<4) + c - 'A' + 10;
  395. else
  396. return val;
  397. }
  398. }
  399. //
  400. // check for character
  401. //
  402. if (str[0] == '\'')
  403. {
  404. return str[1];
  405. }
  406. //
  407. // assume decimal
  408. //
  409. while (1)
  410. {
  411. c = *str++;
  412. if (c <'0' || c > '9')
  413. return val;
  414. val = val*10 + c - '0';
  415. }
  416. return 0;
  417. }
  418. int V_atoi( const char *str )
  419. {
  420. return (int)V_atoi64( str );
  421. }
  422. float V_atof (const char *str)
  423. {
  424. AssertValidStringPtr( str );
  425. double val;
  426. int sign;
  427. int c;
  428. int decimal, total;
  429. if (*str == '-')
  430. {
  431. sign = -1;
  432. str++;
  433. }
  434. else if (*str == '+')
  435. {
  436. sign = 1;
  437. str++;
  438. }
  439. else
  440. {
  441. sign = 1;
  442. }
  443. val = 0;
  444. //
  445. // check for hex
  446. //
  447. if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') )
  448. {
  449. str += 2;
  450. while (1)
  451. {
  452. c = *str++;
  453. if (c >= '0' && c <= '9')
  454. val = (val*16) + c - '0';
  455. else if (c >= 'a' && c <= 'f')
  456. val = (val*16) + c - 'a' + 10;
  457. else if (c >= 'A' && c <= 'F')
  458. val = (val*16) + c - 'A' + 10;
  459. else
  460. return val*sign;
  461. }
  462. }
  463. //
  464. // check for character
  465. //
  466. if (str[0] == '\'')
  467. {
  468. return sign * str[1];
  469. }
  470. //
  471. // assume decimal
  472. //
  473. decimal = -1;
  474. total = 0;
  475. int exponent = 0;
  476. while (1)
  477. {
  478. c = *str++;
  479. if (c == '.')
  480. {
  481. if ( decimal != -1 )
  482. {
  483. break;
  484. }
  485. decimal = total;
  486. continue;
  487. }
  488. if (c <'0' || c > '9')
  489. {
  490. if ( c == 'e' || c == 'E' )
  491. {
  492. exponent = V_atoi(str);
  493. }
  494. break;
  495. }
  496. val = val*10 + c - '0';
  497. total++;
  498. }
  499. if ( exponent != 0 )
  500. {
  501. val *= pow( 10.0, exponent );
  502. }
  503. if (decimal == -1)
  504. return val*sign;
  505. while (total > decimal)
  506. {
  507. val /= 10;
  508. total--;
  509. }
  510. return val*sign;
  511. }
  512. //-----------------------------------------------------------------------------
  513. // Normalizes a float string in place.
  514. //
  515. // (removes leading zeros, trailing zeros after the decimal point, and the decimal point itself where possible)
  516. //-----------------------------------------------------------------------------
  517. void V_normalizeFloatString( char* pFloat )
  518. {
  519. // If we have a decimal point, remove trailing zeroes:
  520. if( strchr( pFloat,'.' ) )
  521. {
  522. int len = V_strlen(pFloat);
  523. while( len > 1 && pFloat[len - 1] == '0' )
  524. {
  525. pFloat[len - 1] = '\0';
  526. len--;
  527. }
  528. if( len > 1 && pFloat[ len - 1 ] == '.' )
  529. {
  530. pFloat[len - 1] = '\0';
  531. len--;
  532. }
  533. }
  534. // TODO: Strip leading zeros
  535. }
  536. //-----------------------------------------------------------------------------
  537. // Finds a string in another string with a case insensitive test
  538. //-----------------------------------------------------------------------------
  539. char const* V_stristr( char const* pStr, char const* pSearch )
  540. {
  541. AssertValidStringPtr(pStr);
  542. AssertValidStringPtr(pSearch);
  543. if (!pStr || !pSearch)
  544. return 0;
  545. char const* pLetter = pStr;
  546. // Check the entire string
  547. while (*pLetter != 0)
  548. {
  549. // Skip over non-matches
  550. if (FastToLower((unsigned char)*pLetter) == FastToLower((unsigned char)*pSearch))
  551. {
  552. // Check for match
  553. char const* pMatch = pLetter + 1;
  554. char const* pTest = pSearch + 1;
  555. while (*pTest != 0)
  556. {
  557. // We've run off the end; don't bother.
  558. if (*pMatch == 0)
  559. return 0;
  560. if (FastToLower((unsigned char)*pMatch) != FastToLower((unsigned char)*pTest))
  561. break;
  562. ++pMatch;
  563. ++pTest;
  564. }
  565. // Found a match!
  566. if (*pTest == 0)
  567. return pLetter;
  568. }
  569. ++pLetter;
  570. }
  571. return 0;
  572. }
  573. char* V_stristr( char* pStr, char const* pSearch )
  574. {
  575. AssertValidStringPtr( pStr );
  576. AssertValidStringPtr( pSearch );
  577. return (char*)V_stristr( (char const*)pStr, pSearch );
  578. }
  579. //-----------------------------------------------------------------------------
  580. // Finds a string in another string with a case insensitive test w/ length validation
  581. //-----------------------------------------------------------------------------
  582. char const* V_strnistr( char const* pStr, char const* pSearch, int n )
  583. {
  584. AssertValidStringPtr(pStr);
  585. AssertValidStringPtr(pSearch);
  586. if (!pStr || !pSearch)
  587. return 0;
  588. char const* pLetter = pStr;
  589. // Check the entire string
  590. while (*pLetter != 0)
  591. {
  592. if ( n <= 0 )
  593. return 0;
  594. // Skip over non-matches
  595. if (FastToLower(*pLetter) == FastToLower(*pSearch))
  596. {
  597. int n1 = n - 1;
  598. // Check for match
  599. char const* pMatch = pLetter + 1;
  600. char const* pTest = pSearch + 1;
  601. while (*pTest != 0)
  602. {
  603. if ( n1 <= 0 )
  604. return 0;
  605. // We've run off the end; don't bother.
  606. if (*pMatch == 0)
  607. return 0;
  608. if (FastToLower(*pMatch) != FastToLower(*pTest))
  609. break;
  610. ++pMatch;
  611. ++pTest;
  612. --n1;
  613. }
  614. // Found a match!
  615. if (*pTest == 0)
  616. return pLetter;
  617. }
  618. ++pLetter;
  619. --n;
  620. }
  621. return 0;
  622. }
  623. const char* V_strnchr( const char* pStr, char c, int n )
  624. {
  625. char const* pLetter = pStr;
  626. char const* pLast = pStr + n;
  627. // Check the entire string
  628. while ( (pLetter < pLast) && (*pLetter != 0) )
  629. {
  630. if (*pLetter == c)
  631. return pLetter;
  632. ++pLetter;
  633. }
  634. return NULL;
  635. }
  636. void V_strncpy( char *pDest, char const *pSrc, int maxLen )
  637. {
  638. Assert( maxLen >= sizeof( *pDest ) );
  639. AssertValidWritePtr( pDest, maxLen );
  640. AssertValidStringPtr( pSrc );
  641. strncpy( pDest, pSrc, maxLen );
  642. if ( maxLen > 0 )
  643. {
  644. pDest[maxLen-1] = 0;
  645. }
  646. }
  647. // warning C6053: Call to 'wcsncpy' might not zero-terminate string 'pDest'
  648. // warning C6059: Incorrect length parameter in call to 'strncat'. Pass the number of remaining characters, not the buffer size of 'argument 1'
  649. // warning C6386: Buffer overrun: accessing 'argument 1', the writable size is 'destBufferSize' bytes, but '1000' bytes might be written
  650. // These warnings were investigated through code inspection and writing of tests and they are
  651. // believed to all be spurious.
  652. #ifdef _PREFAST_
  653. #pragma warning( push )
  654. #pragma warning( disable : 6053 6059 6386 )
  655. #endif
  656. void V_wcsncpy( wchar_t *pDest, wchar_t const *pSrc, int maxLenInBytes )
  657. {
  658. Assert( maxLenInBytes >= sizeof( *pDest ) );
  659. AssertValidWritePtr( pDest, maxLenInBytes );
  660. AssertValidReadPtr( pSrc );
  661. int maxLen = maxLenInBytes / sizeof(wchar_t);
  662. wcsncpy( pDest, pSrc, maxLen );
  663. if( maxLen )
  664. {
  665. pDest[maxLen-1] = 0;
  666. }
  667. }
  668. int V_snwprintf( wchar_t *pDest, int maxLen, const wchar_t *pFormat, ... )
  669. {
  670. Assert( maxLen > 0 );
  671. AssertValidWritePtr( pDest, maxLen );
  672. AssertValidReadPtr( pFormat );
  673. va_list marker;
  674. va_start( marker, pFormat );
  675. #ifdef _WIN32
  676. int len = _vsnwprintf( pDest, maxLen, pFormat, marker );
  677. #elif POSIX
  678. int len = vswprintf( pDest, maxLen, pFormat, marker );
  679. #else
  680. #error "define vsnwprintf type."
  681. #endif
  682. va_end( marker );
  683. // Len > maxLen represents an overflow on POSIX, < 0 is an overflow on windows
  684. if( len < 0 || len >= maxLen )
  685. {
  686. len = maxLen;
  687. pDest[maxLen-1] = 0;
  688. }
  689. return len;
  690. }
  691. int V_vsnwprintf( wchar_t *pDest, int maxLen, const wchar_t *pFormat, va_list params )
  692. {
  693. Assert( maxLen > 0 );
  694. #ifdef _WIN32
  695. int len = _vsnwprintf( pDest, maxLen, pFormat, params );
  696. #elif POSIX
  697. int len = vswprintf( pDest, maxLen, pFormat, params );
  698. #else
  699. #error "define vsnwprintf type."
  700. #endif
  701. // Len < 0 represents an overflow
  702. // Len == maxLen represents exactly fitting with no NULL termination
  703. // Len >= maxLen represents overflow on POSIX
  704. if ( len < 0 || len >= maxLen )
  705. {
  706. len = maxLen;
  707. pDest[maxLen-1] = 0;
  708. }
  709. return len;
  710. }
  711. int V_snprintf( char *pDest, int maxLen, char const *pFormat, ... )
  712. {
  713. Assert( maxLen > 0 );
  714. AssertValidWritePtr( pDest, maxLen );
  715. AssertValidStringPtr( pFormat );
  716. va_list marker;
  717. va_start( marker, pFormat );
  718. #ifdef _WIN32
  719. int len = _vsnprintf( pDest, maxLen, pFormat, marker );
  720. #elif POSIX
  721. int len = vsnprintf( pDest, maxLen, pFormat, marker );
  722. #else
  723. #error "define vsnprintf type."
  724. #endif
  725. va_end( marker );
  726. // Len > maxLen represents an overflow on POSIX, < 0 is an overflow on windows
  727. if( len < 0 || len >= maxLen )
  728. {
  729. len = maxLen;
  730. pDest[maxLen-1] = 0;
  731. }
  732. return len;
  733. }
  734. int V_vsnprintf( char *pDest, int maxLen, char const *pFormat, va_list params )
  735. {
  736. Assert( maxLen > 0 );
  737. AssertValidWritePtr( pDest, maxLen );
  738. AssertValidStringPtr( pFormat );
  739. int len = _vsnprintf( pDest, maxLen, pFormat, params );
  740. // Len > maxLen represents an overflow on POSIX, < 0 is an overflow on windows
  741. if( len < 0 || len >= maxLen )
  742. {
  743. len = maxLen;
  744. pDest[maxLen-1] = 0;
  745. }
  746. return len;
  747. }
  748. int V_vsnprintfRet( char *pDest, int maxLen, const char *pFormat, va_list params, bool *pbTruncated )
  749. {
  750. Assert( maxLen > 0 );
  751. AssertValidWritePtr( pDest, maxLen );
  752. AssertValidStringPtr( pFormat );
  753. int len = _vsnprintf( pDest, maxLen, pFormat, params );
  754. if ( pbTruncated )
  755. {
  756. *pbTruncated = ( len < 0 || len >= maxLen );
  757. }
  758. if ( len < 0 || len >= maxLen )
  759. {
  760. len = maxLen;
  761. pDest[maxLen-1] = 0;
  762. }
  763. return len;
  764. }
  765. //-----------------------------------------------------------------------------
  766. // Purpose: If COPY_ALL_CHARACTERS == max_chars_to_copy then we try to add the whole pSrc to the end of pDest, otherwise
  767. // we copy only as many characters as are specified in max_chars_to_copy (or the # of characters in pSrc if thats's less).
  768. // Input : *pDest - destination buffer
  769. // *pSrc - string to append
  770. // destBufferSize - sizeof the buffer pointed to by pDest
  771. // max_chars_to_copy - COPY_ALL_CHARACTERS in pSrc or max # to copy
  772. // Output : char * the copied buffer
  773. //-----------------------------------------------------------------------------
  774. char *V_strncat(char *pDest, const char *pSrc, size_t destBufferSize, int max_chars_to_copy )
  775. {
  776. size_t charstocopy = (size_t)0;
  777. Assert( (ptrdiff_t)destBufferSize >= 0 );
  778. AssertValidStringPtr( pDest);
  779. AssertValidStringPtr( pSrc );
  780. size_t len = strlen(pDest);
  781. size_t srclen = strlen( pSrc );
  782. if ( max_chars_to_copy <= COPY_ALL_CHARACTERS )
  783. {
  784. charstocopy = srclen;
  785. }
  786. else
  787. {
  788. charstocopy = (size_t)min( max_chars_to_copy, (int)srclen );
  789. }
  790. if ( len + charstocopy >= destBufferSize )
  791. {
  792. charstocopy = destBufferSize - len - 1;
  793. }
  794. if ( (int)charstocopy <= 0 )
  795. {
  796. return pDest;
  797. }
  798. 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'
  799. char *pOut = strncat( pDest, pSrc, charstocopy );
  800. return pOut;
  801. }
  802. wchar_t *V_wcsncat( INOUT_Z_CAP(cchDest) wchar_t *pDest, const wchar_t *pSrc, size_t cchDest, int max_chars_to_copy )
  803. {
  804. size_t charstocopy = (size_t)0;
  805. Assert( (ptrdiff_t)cchDest >= 0 );
  806. size_t len = wcslen(pDest);
  807. size_t srclen = wcslen( pSrc );
  808. if ( max_chars_to_copy <= COPY_ALL_CHARACTERS )
  809. {
  810. charstocopy = srclen;
  811. }
  812. else
  813. {
  814. charstocopy = (size_t)min( max_chars_to_copy, (int)srclen );
  815. }
  816. if ( len + charstocopy >= cchDest )
  817. {
  818. charstocopy = cchDest - len - 1;
  819. }
  820. if ( (int)charstocopy <= 0 )
  821. {
  822. return pDest;
  823. }
  824. 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'
  825. wchar_t *pOut = wcsncat( pDest, pSrc, charstocopy );
  826. return pOut;
  827. }
  828. //-----------------------------------------------------------------------------
  829. // Purpose: Converts value into x.xx MB/ x.xx KB, x.xx bytes format, including commas
  830. // Input : value -
  831. // 2 -
  832. // false -
  833. // Output : char
  834. //-----------------------------------------------------------------------------
  835. #define NUM_PRETIFYMEM_BUFFERS 8
  836. char *V_pretifymem( float value, int digitsafterdecimal /*= 2*/, bool usebinaryonek /*= false*/ )
  837. {
  838. static char output[ NUM_PRETIFYMEM_BUFFERS ][ 32 ];
  839. static int current;
  840. float onekb = usebinaryonek ? 1024.0f : 1000.0f;
  841. float onemb = onekb * onekb;
  842. char *out = output[ current ];
  843. current = ( current + 1 ) & ( NUM_PRETIFYMEM_BUFFERS -1 );
  844. char suffix[ 8 ];
  845. // First figure out which bin to use
  846. if ( value > onemb )
  847. {
  848. value /= onemb;
  849. V_snprintf( suffix, sizeof( suffix ), " MB" );
  850. }
  851. else if ( value > onekb )
  852. {
  853. value /= onekb;
  854. V_snprintf( suffix, sizeof( suffix ), " KB" );
  855. }
  856. else
  857. {
  858. V_snprintf( suffix, sizeof( suffix ), " bytes" );
  859. }
  860. char val[ 32 ];
  861. // Clamp to >= 0
  862. digitsafterdecimal = max( digitsafterdecimal, 0 );
  863. // If it's basically integral, don't do any decimals
  864. if ( FloatMakePositive( value - (int)value ) < 0.00001 )
  865. {
  866. V_snprintf( val, sizeof( val ), "%i%s", (int)value, suffix );
  867. }
  868. else
  869. {
  870. char fmt[ 32 ];
  871. // Otherwise, create a format string for the decimals
  872. V_snprintf( fmt, sizeof( fmt ), "%%.%if%s", digitsafterdecimal, suffix );
  873. V_snprintf( val, sizeof( val ), fmt, value );
  874. }
  875. // Copy from in to out
  876. char *i = val;
  877. char *o = out;
  878. // Search for decimal or if it was integral, find the space after the raw number
  879. char *dot = strstr( i, "." );
  880. if ( !dot )
  881. {
  882. dot = strstr( i, " " );
  883. }
  884. // Compute position of dot
  885. int pos = dot - i;
  886. // Don't put a comma if it's <= 3 long
  887. pos -= 3;
  888. while ( *i )
  889. {
  890. // If pos is still valid then insert a comma every third digit, except if we would be
  891. // putting one in the first spot
  892. if ( pos >= 0 && !( pos % 3 ) )
  893. {
  894. // Never in first spot
  895. if ( o != out )
  896. {
  897. *o++ = ',';
  898. }
  899. }
  900. // Count down comma position
  901. pos--;
  902. // Copy rest of data as normal
  903. *o++ = *i++;
  904. }
  905. // Terminate
  906. *o = 0;
  907. return out;
  908. }
  909. //-----------------------------------------------------------------------------
  910. // Purpose: Returns a string representation of an integer with commas
  911. // separating the 1000s (ie, 37,426,421)
  912. // Input : value - Value to convert
  913. // Output : Pointer to a static buffer containing the output
  914. //-----------------------------------------------------------------------------
  915. #define NUM_PRETIFYNUM_BUFFERS 8 // Must be a power of two
  916. char *V_pretifynum( int64 inputValue )
  917. {
  918. static char output[ NUM_PRETIFYMEM_BUFFERS ][ 32 ];
  919. static int current;
  920. // Point to the output buffer.
  921. char * const out = output[ current ];
  922. // Track the output buffer end for easy calculation of bytes-remaining.
  923. const char* const outEnd = out + sizeof( output[ current ] );
  924. // Point to the current output location in the output buffer.
  925. char *pchRender = out;
  926. // Move to the next output pointer.
  927. current = ( current + 1 ) & ( NUM_PRETIFYMEM_BUFFERS -1 );
  928. *out = 0;
  929. // In order to handle the most-negative int64 we need to negate it
  930. // into a uint64.
  931. uint64 value;
  932. // Render the leading minus sign, if necessary
  933. if ( inputValue < 0 )
  934. {
  935. V_snprintf( pchRender, 32, "-" );
  936. value = (uint64)-inputValue;
  937. // Advance our output pointer.
  938. pchRender += V_strlen( pchRender );
  939. }
  940. else
  941. {
  942. value = (uint64)inputValue;
  943. }
  944. // Now let's find out how big our number is. The largest number we can fit
  945. // into 63 bits is about 9.2e18. So, there could potentially be six
  946. // three-digit groups.
  947. // We need the initial value of 'divisor' to be big enough to divide our
  948. // number down to 1-999 range.
  949. uint64 divisor = 1;
  950. // Loop more than six times to avoid integer overflow.
  951. for ( int i = 0; i < 6; ++i )
  952. {
  953. // If our divisor is already big enough then stop.
  954. if ( value < divisor * 1000 )
  955. break;
  956. divisor *= 1000;
  957. }
  958. // Print the leading batch of one to three digits.
  959. int toPrint = value / divisor;
  960. V_snprintf( pchRender, outEnd - pchRender, "%d", toPrint );
  961. for (;;)
  962. {
  963. // Advance our output pointer.
  964. pchRender += V_strlen( pchRender );
  965. // Adjust our value to be printed and our divisor.
  966. value -= toPrint * divisor;
  967. divisor /= 1000;
  968. if ( !divisor )
  969. break;
  970. // The remaining blocks of digits always include a comma and three digits.
  971. toPrint = value / divisor;
  972. V_snprintf( pchRender, outEnd - pchRender, ",%03d", toPrint );
  973. }
  974. return out;
  975. }
  976. //-----------------------------------------------------------------------------
  977. // Purpose: returns true if a wide character is a "mean" space; that is,
  978. // if it is technically a space or punctuation, but causes disruptive
  979. // behavior when used in names, web pages, chat windows, etc.
  980. //
  981. // characters in this set are removed from the beginning and/or end of strings
  982. // by Q_AggressiveStripPrecedingAndTrailingWhitespaceW()
  983. //-----------------------------------------------------------------------------
  984. bool Q_IsMeanSpaceW( wchar_t wch )
  985. {
  986. bool bIsMean = false;
  987. switch ( wch )
  988. {
  989. case L'\x0082': // BREAK PERMITTED HERE
  990. case L'\x0083': // NO BREAK PERMITTED HERE
  991. case L'\x00A0': // NO-BREAK SPACE
  992. case L'\x034F': // COMBINING GRAPHEME JOINER
  993. case L'\x2000': // EN QUAD
  994. case L'\x2001': // EM QUAD
  995. case L'\x2002': // EN SPACE
  996. case L'\x2003': // EM SPACE
  997. case L'\x2004': // THICK SPACE
  998. case L'\x2005': // MID SPACE
  999. case L'\x2006': // SIX SPACE
  1000. case L'\x2007': // figure space
  1001. case L'\x2008': // PUNCTUATION SPACE
  1002. case L'\x2009': // THIN SPACE
  1003. case L'\x200A': // HAIR SPACE
  1004. case L'\x200B': // ZERO-WIDTH SPACE
  1005. case L'\x200C': // ZERO-WIDTH NON-JOINER
  1006. case L'\x200D': // ZERO WIDTH JOINER
  1007. case L'\x200E': // LEFT-TO-RIGHT MARK
  1008. case L'\x2028': // LINE SEPARATOR
  1009. case L'\x2029': // PARAGRAPH SEPARATOR
  1010. case L'\x202F': // NARROW NO-BREAK SPACE
  1011. case L'\x2060': // word joiner
  1012. case L'\xFEFF': // ZERO-WIDTH NO BREAK SPACE
  1013. case L'\xFFFC': // OBJECT REPLACEMENT CHARACTER
  1014. bIsMean = true;
  1015. break;
  1016. }
  1017. return bIsMean;
  1018. }
  1019. //-----------------------------------------------------------------------------
  1020. // Purpose: strips trailing whitespace; returns pointer inside string just past
  1021. // any leading whitespace.
  1022. //
  1023. // bAggresive = true causes this function to also check for "mean" spaces,
  1024. // which we don't want in persona names or chat strings as they're disruptive
  1025. // to the user experience.
  1026. //-----------------------------------------------------------------------------
  1027. static wchar_t *StripWhitespaceWorker( int cchLength, wchar_t *pwch, bool *pbStrippedWhitespace, bool bAggressive )
  1028. {
  1029. // walk backwards from the end of the string, killing any whitespace
  1030. *pbStrippedWhitespace = false;
  1031. wchar_t *pwchEnd = pwch + cchLength;
  1032. while ( --pwchEnd >= pwch )
  1033. {
  1034. if ( !iswspace( *pwchEnd ) && ( !bAggressive || !Q_IsMeanSpaceW( *pwchEnd ) ) )
  1035. break;
  1036. *pwchEnd = 0;
  1037. *pbStrippedWhitespace = true;
  1038. }
  1039. // walk forward in the string
  1040. while ( pwch < pwchEnd )
  1041. {
  1042. if ( !iswspace( *pwch ) )
  1043. break;
  1044. *pbStrippedWhitespace = true;
  1045. pwch++;
  1046. }
  1047. return pwch;
  1048. }
  1049. //-----------------------------------------------------------------------------
  1050. // Purpose: Strips all evil characters (ie. zero-width no-break space)
  1051. // from a string.
  1052. //-----------------------------------------------------------------------------
  1053. bool Q_RemoveAllEvilCharacters( char *pch )
  1054. {
  1055. // convert to unicode
  1056. int cch = Q_strlen( pch );
  1057. int cubDest = (cch + 1 ) * sizeof( wchar_t );
  1058. wchar_t *pwch = (wchar_t *)stackalloc( cubDest );
  1059. int cwch = Q_UTF8ToUnicode( pch, pwch, cubDest ) / sizeof( wchar_t );
  1060. bool bStrippedWhitespace = false;
  1061. // Walk through and skip over evil characters
  1062. int nWalk = 0;
  1063. for( int i=0; i<cwch; ++i )
  1064. {
  1065. if( !Q_IsMeanSpaceW( pwch[i] ) )
  1066. {
  1067. pwch[nWalk] = pwch[i];
  1068. ++nWalk;
  1069. }
  1070. else
  1071. {
  1072. bStrippedWhitespace = true;
  1073. }
  1074. }
  1075. // Null terminate
  1076. pwch[nWalk-1] = L'\0';
  1077. // copy back, if necessary
  1078. if ( bStrippedWhitespace )
  1079. {
  1080. Q_UnicodeToUTF8( pwch, pch, cch );
  1081. }
  1082. return bStrippedWhitespace;
  1083. }
  1084. //-----------------------------------------------------------------------------
  1085. // Purpose: strips leading and trailing whitespace
  1086. //-----------------------------------------------------------------------------
  1087. bool Q_StripPrecedingAndTrailingWhitespaceW( wchar_t *pwch )
  1088. {
  1089. int cch = Q_wcslen( pwch );
  1090. // Early out and don't convert if we don't have any chars or leading/trailing ws.
  1091. if ( ( cch < 1 ) || ( !iswspace( pwch[ 0 ] ) && !iswspace( pwch[ cch - 1 ] ) ) )
  1092. return false;
  1093. // duplicate on stack
  1094. int cubDest = ( cch + 1 ) * sizeof( wchar_t );
  1095. wchar_t *pwchT = (wchar_t *)stackalloc( cubDest );
  1096. Q_wcsncpy( pwchT, pwch, cubDest );
  1097. bool bStrippedWhitespace = false;
  1098. pwchT = StripWhitespaceWorker( cch, pwch, &bStrippedWhitespace, false /* not aggressive */ );
  1099. // copy back, if necessary
  1100. if ( bStrippedWhitespace )
  1101. {
  1102. Q_wcsncpy( pwch, pwchT, cubDest );
  1103. }
  1104. return bStrippedWhitespace;
  1105. }
  1106. //-----------------------------------------------------------------------------
  1107. // Purpose: strips leading and trailing whitespace,
  1108. // and also strips punctuation and formatting characters with "clear"
  1109. // representations.
  1110. //-----------------------------------------------------------------------------
  1111. bool Q_AggressiveStripPrecedingAndTrailingWhitespaceW( wchar_t *pwch )
  1112. {
  1113. // duplicate on stack
  1114. int cch = Q_wcslen( pwch );
  1115. int cubDest = ( cch + 1 ) * sizeof( wchar_t );
  1116. wchar_t *pwchT = (wchar_t *)stackalloc( cubDest );
  1117. Q_wcsncpy( pwchT, pwch, cubDest );
  1118. bool bStrippedWhitespace = false;
  1119. pwchT = StripWhitespaceWorker( cch, pwch, &bStrippedWhitespace, true /* is aggressive */ );
  1120. // copy back, if necessary
  1121. if ( bStrippedWhitespace )
  1122. {
  1123. Q_wcsncpy( pwch, pwchT, cubDest );
  1124. }
  1125. return bStrippedWhitespace;
  1126. }
  1127. //-----------------------------------------------------------------------------
  1128. // Purpose: strips leading and trailing whitespace
  1129. //-----------------------------------------------------------------------------
  1130. bool Q_StripPrecedingAndTrailingWhitespace( char *pch )
  1131. {
  1132. int cch = Q_strlen( pch );
  1133. // Early out and don't convert if we don't have any chars or leading/trailing ws.
  1134. if ( ( cch < 1 ) || ( !isspace( (unsigned char)pch[ 0 ] ) && !isspace( (unsigned char)pch[ cch - 1 ] ) ) )
  1135. return false;
  1136. // convert to unicode
  1137. int cubDest = (cch + 1 ) * sizeof( wchar_t );
  1138. wchar_t *pwch = (wchar_t *)stackalloc( cubDest );
  1139. int cwch = Q_UTF8ToUnicode( pch, pwch, cubDest ) / sizeof( wchar_t );
  1140. bool bStrippedWhitespace = false;
  1141. pwch = StripWhitespaceWorker( cwch-1, pwch, &bStrippedWhitespace, false /* not aggressive */ );
  1142. // copy back, if necessary
  1143. if ( bStrippedWhitespace )
  1144. {
  1145. Q_UnicodeToUTF8( pwch, pch, cch );
  1146. }
  1147. return bStrippedWhitespace;
  1148. }
  1149. //-----------------------------------------------------------------------------
  1150. // Purpose: strips leading and trailing whitespace
  1151. //-----------------------------------------------------------------------------
  1152. bool Q_AggressiveStripPrecedingAndTrailingWhitespace( char *pch )
  1153. {
  1154. // convert to unicode
  1155. int cch = Q_strlen( pch );
  1156. int cubDest = (cch + 1 ) * sizeof( wchar_t );
  1157. wchar_t *pwch = (wchar_t *)stackalloc( cubDest );
  1158. int cwch = Q_UTF8ToUnicode( pch, pwch, cubDest ) / sizeof( wchar_t );
  1159. bool bStrippedWhitespace = false;
  1160. pwch = StripWhitespaceWorker( cwch-1, pwch, &bStrippedWhitespace, true /* is aggressive */ );
  1161. // copy back, if necessary
  1162. if ( bStrippedWhitespace )
  1163. {
  1164. Q_UnicodeToUTF8( pwch, pch, cch );
  1165. }
  1166. return bStrippedWhitespace;
  1167. }
  1168. //-----------------------------------------------------------------------------
  1169. // Purpose: Converts a ucs2 string to a unicode (wchar_t) one, no-op on win32
  1170. //-----------------------------------------------------------------------------
  1171. int _V_UCS2ToUnicode( const ucs2 *pUCS2, wchar_t *pUnicode, int cubDestSizeInBytes )
  1172. {
  1173. Assert( cubDestSizeInBytes >= sizeof( *pUnicode ) );
  1174. AssertValidWritePtr(pUnicode);
  1175. AssertValidReadPtr(pUCS2);
  1176. pUnicode[0] = 0;
  1177. #ifdef _WIN32
  1178. int cchResult = V_wcslen( pUCS2 );
  1179. V_memcpy( pUnicode, pUCS2, cubDestSizeInBytes );
  1180. #else
  1181. iconv_t conv_t = iconv_open( "UCS-4LE", "UCS-2LE" );
  1182. int cchResult = -1;
  1183. size_t nLenUnicde = cubDestSizeInBytes;
  1184. size_t nMaxUTF8 = cubDestSizeInBytes;
  1185. char *pIn = (char *)pUCS2;
  1186. char *pOut = (char *)pUnicode;
  1187. if ( conv_t > 0 )
  1188. {
  1189. cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8 );
  1190. iconv_close( conv_t );
  1191. if ( (int)cchResult < 0 )
  1192. cchResult = 0;
  1193. else
  1194. cchResult = nMaxUTF8;
  1195. }
  1196. #endif
  1197. pUnicode[(cubDestSizeInBytes / sizeof(wchar_t)) - 1] = 0;
  1198. return cchResult;
  1199. }
  1200. #ifdef _PREFAST_
  1201. #pragma warning( pop ) // Restore the /analyze warnings
  1202. #endif
  1203. //-----------------------------------------------------------------------------
  1204. // Purpose: Converts a wchar_t string into a UCS2 string -noop on windows
  1205. //-----------------------------------------------------------------------------
  1206. int _V_UnicodeToUCS2( const wchar_t *pUnicode, int cubSrcInBytes, char *pUCS2, int cubDestSizeInBytes )
  1207. {
  1208. #ifdef _WIN32
  1209. // Figure out which buffer is smaller and convert from bytes to character
  1210. // counts.
  1211. int cchResult = min( (size_t)cubSrcInBytes/sizeof(wchar_t), cubDestSizeInBytes/sizeof(wchar_t) );
  1212. wchar_t *pDest = (wchar_t*)pUCS2;
  1213. wcsncpy( pDest, pUnicode, cchResult );
  1214. // Make sure we NULL-terminate.
  1215. pDest[ cchResult - 1 ] = 0;
  1216. #elif defined (POSIX)
  1217. iconv_t conv_t = iconv_open( "UCS-2LE", "UTF-32LE" );
  1218. size_t cchResult = -1;
  1219. size_t nLenUnicde = cubSrcInBytes;
  1220. size_t nMaxUCS2 = cubDestSizeInBytes;
  1221. char *pIn = (char*)pUnicode;
  1222. char *pOut = pUCS2;
  1223. if ( conv_t > 0 )
  1224. {
  1225. cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUCS2 );
  1226. iconv_close( conv_t );
  1227. if ( (int)cchResult < 0 )
  1228. cchResult = 0;
  1229. else
  1230. cchResult = cubSrcInBytes / sizeof( wchar_t );
  1231. }
  1232. #else
  1233. #error Must be implemented for this platform
  1234. #endif
  1235. return cchResult;
  1236. }
  1237. //-----------------------------------------------------------------------------
  1238. // Purpose: Converts a ucs-2 (windows wchar_t) string into a UTF8 (standard) string
  1239. //-----------------------------------------------------------------------------
  1240. int _V_UCS2ToUTF8( const ucs2 *pUCS2, char *pUTF8, int cubDestSizeInBytes )
  1241. {
  1242. AssertValidStringPtr(pUTF8, cubDestSizeInBytes);
  1243. AssertValidReadPtr(pUCS2);
  1244. pUTF8[0] = 0;
  1245. #ifdef _WIN32
  1246. // under win32 wchar_t == ucs2, sigh
  1247. int cchResult = WideCharToMultiByte( CP_UTF8, 0, pUCS2, -1, pUTF8, cubDestSizeInBytes, NULL, NULL );
  1248. #elif defined(POSIX)
  1249. iconv_t conv_t = iconv_open( "UTF-8", "UCS-2LE" );
  1250. size_t cchResult = -1;
  1251. // pUCS2 will be null-terminated so use that to work out the input
  1252. // buffer size. Note that we shouldn't assume iconv will stop when it
  1253. // finds a zero, and nLenUnicde should be given in bytes, so we multiply
  1254. // it by sizeof( ucs2 ) at the end.
  1255. size_t nLenUnicde = 0;
  1256. while ( pUCS2[nLenUnicde] )
  1257. {
  1258. ++nLenUnicde;
  1259. }
  1260. nLenUnicde *= sizeof( ucs2 );
  1261. // Calculate number of bytes we want iconv to write, leaving space
  1262. // for the null-terminator
  1263. size_t nMaxUTF8 = cubDestSizeInBytes - 1;
  1264. char *pIn = (char *)pUCS2;
  1265. char *pOut = (char *)pUTF8;
  1266. if ( conv_t > 0 )
  1267. {
  1268. const size_t nBytesToWrite = nMaxUTF8;
  1269. cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8 );
  1270. // Calculate how many bytes were actually written and use that to
  1271. // null-terminate our output string.
  1272. const size_t nBytesWritten = nBytesToWrite - nMaxUTF8;
  1273. pUTF8[nBytesWritten] = 0;
  1274. iconv_close( conv_t );
  1275. if ( (int)cchResult < 0 )
  1276. cchResult = 0;
  1277. else
  1278. cchResult = nMaxUTF8;
  1279. }
  1280. #endif
  1281. pUTF8[cubDestSizeInBytes - 1] = 0;
  1282. return cchResult;
  1283. }
  1284. //-----------------------------------------------------------------------------
  1285. // Purpose: Converts a UTF8 to ucs-2 (windows wchar_t)
  1286. //-----------------------------------------------------------------------------
  1287. int _V_UTF8ToUCS2( const char *pUTF8, int cubSrcInBytes, ucs2 *pUCS2, int cubDestSizeInBytes )
  1288. {
  1289. Assert( cubDestSizeInBytes >= sizeof(pUCS2[0]) );
  1290. AssertValidStringPtr(pUTF8, cubDestSizeInBytes);
  1291. AssertValidReadPtr(pUCS2);
  1292. pUCS2[0] = 0;
  1293. #ifdef _WIN32
  1294. // under win32 wchar_t == ucs2, sigh
  1295. int cchResult = MultiByteToWideChar( CP_UTF8, 0, pUTF8, -1, pUCS2, cubDestSizeInBytes / sizeof(wchar_t) );
  1296. #elif defined( _PS3 ) // bugbug JLB
  1297. int cchResult = 0;
  1298. Assert( 0 );
  1299. #elif defined(POSIX)
  1300. iconv_t conv_t = iconv_open( "UCS-2LE", "UTF-8" );
  1301. size_t cchResult = -1;
  1302. size_t nLenUnicde = cubSrcInBytes;
  1303. size_t nMaxUTF8 = cubDestSizeInBytes;
  1304. char *pIn = (char *)pUTF8;
  1305. char *pOut = (char *)pUCS2;
  1306. if ( conv_t > 0 )
  1307. {
  1308. cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8 );
  1309. iconv_close( conv_t );
  1310. if ( (int)cchResult < 0 )
  1311. cchResult = 0;
  1312. else
  1313. cchResult = cubSrcInBytes;
  1314. }
  1315. #endif
  1316. pUCS2[ (cubDestSizeInBytes/sizeof(ucs2)) - 1] = 0;
  1317. return cchResult;
  1318. }
  1319. //-----------------------------------------------------------------------------
  1320. // Purpose: Returns the 4 bit nibble for a hex character
  1321. // Input : c -
  1322. // Output : unsigned char
  1323. //-----------------------------------------------------------------------------
  1324. unsigned char V_nibble( char c )
  1325. {
  1326. if ( ( c >= '0' ) &&
  1327. ( c <= '9' ) )
  1328. {
  1329. return (unsigned char)(c - '0');
  1330. }
  1331. if ( ( c >= 'A' ) &&
  1332. ( c <= 'F' ) )
  1333. {
  1334. return (unsigned char)(c - 'A' + 0x0a);
  1335. }
  1336. if ( ( c >= 'a' ) &&
  1337. ( c <= 'f' ) )
  1338. {
  1339. return (unsigned char)(c - 'a' + 0x0a);
  1340. }
  1341. return '0';
  1342. }
  1343. //-----------------------------------------------------------------------------
  1344. // Purpose:
  1345. // Input : *in -
  1346. // numchars -
  1347. // *out -
  1348. // maxoutputbytes -
  1349. //-----------------------------------------------------------------------------
  1350. void V_hextobinary( char const *in, int numchars, byte *out, int maxoutputbytes )
  1351. {
  1352. int len = V_strlen( in );
  1353. numchars = min( len, numchars );
  1354. // Make sure it's even
  1355. numchars = ( numchars ) & ~0x1;
  1356. // Must be an even # of input characters (two chars per output byte)
  1357. Assert( numchars >= 2 );
  1358. memset( out, 0x00, maxoutputbytes );
  1359. byte *p;
  1360. int i;
  1361. p = out;
  1362. for ( i = 0;
  1363. ( i < numchars ) && ( ( p - out ) < maxoutputbytes );
  1364. i+=2, p++ )
  1365. {
  1366. *p = ( V_nibble( in[i] ) << 4 ) | V_nibble( in[i+1] );
  1367. }
  1368. }
  1369. //-----------------------------------------------------------------------------
  1370. // Purpose:
  1371. // Input : *in -
  1372. // inputbytes -
  1373. // *out -
  1374. // outsize -
  1375. //-----------------------------------------------------------------------------
  1376. void V_binarytohex( const byte *in, int inputbytes, char *out, int outsize )
  1377. {
  1378. Assert( outsize >= 1 );
  1379. char doublet[10];
  1380. int i;
  1381. out[0]=0;
  1382. for ( i = 0; i < inputbytes; i++ )
  1383. {
  1384. unsigned char c = in[i];
  1385. V_snprintf( doublet, sizeof( doublet ), "%02x", c );
  1386. V_strncat( out, doublet, outsize, COPY_ALL_CHARACTERS );
  1387. }
  1388. }
  1389. // Even though \ on Posix (Linux&Mac) isn't techincally a path separator we are
  1390. // now counting it as one even Posix since so many times our filepaths aren't actual
  1391. // paths but rather text strings passed in from data files, treating \ as a pathseparator
  1392. // covers the full range of cases
  1393. bool PATHSEPARATOR( char c )
  1394. {
  1395. return c == '\\' || c == '/';
  1396. }
  1397. //-----------------------------------------------------------------------------
  1398. // Purpose: Extracts the base name of a file (no path, no extension, assumes '/' or '\' as path separator)
  1399. // Input : *in -
  1400. // *out -
  1401. // maxlen -
  1402. //-----------------------------------------------------------------------------
  1403. void V_FileBase( const char *in, char *out, int maxlen )
  1404. {
  1405. Assert( maxlen >= 1 );
  1406. Assert( in );
  1407. Assert( out );
  1408. if ( !in || !in[ 0 ] )
  1409. {
  1410. *out = 0;
  1411. return;
  1412. }
  1413. int len, start, end;
  1414. len = V_strlen( in );
  1415. // scan backward for '.'
  1416. end = len - 1;
  1417. while ( end&& in[end] != '.' && !PATHSEPARATOR( in[end] ) )
  1418. {
  1419. end--;
  1420. }
  1421. if ( in[end] != '.' ) // no '.', copy to end
  1422. {
  1423. end = len-1;
  1424. }
  1425. else
  1426. {
  1427. end--; // Found ',', copy to left of '.'
  1428. }
  1429. // Scan backward for '/'
  1430. start = len-1;
  1431. while ( start >= 0 && !PATHSEPARATOR( in[start] ) )
  1432. {
  1433. start--;
  1434. }
  1435. if ( start < 0 || !PATHSEPARATOR( in[start] ) )
  1436. {
  1437. start = 0;
  1438. }
  1439. else
  1440. {
  1441. start++;
  1442. }
  1443. // Length of new sting
  1444. len = end - start + 1;
  1445. int maxcopy = min( len + 1, maxlen );
  1446. // Copy partial string
  1447. V_strncpy( out, &in[start], maxcopy );
  1448. }
  1449. //-----------------------------------------------------------------------------
  1450. // Purpose:
  1451. // Input : *ppath -
  1452. //-----------------------------------------------------------------------------
  1453. void V_StripTrailingSlash( char *ppath )
  1454. {
  1455. Assert( ppath );
  1456. int len = V_strlen( ppath );
  1457. if ( len > 0 )
  1458. {
  1459. if ( PATHSEPARATOR( ppath[ len - 1 ] ) )
  1460. {
  1461. ppath[ len - 1 ] = 0;
  1462. }
  1463. }
  1464. }
  1465. //-----------------------------------------------------------------------------
  1466. // Purpose:
  1467. // Input : *ppline -
  1468. //-----------------------------------------------------------------------------
  1469. void V_StripTrailingWhitespace( char *ppline )
  1470. {
  1471. Assert( ppline );
  1472. int len = V_strlen( ppline );
  1473. while ( len > 0 )
  1474. {
  1475. if ( !V_isspace( ppline[ len - 1 ] ) )
  1476. break;
  1477. ppline[ len - 1 ] = 0;
  1478. len--;
  1479. }
  1480. }
  1481. //-----------------------------------------------------------------------------
  1482. // Purpose:
  1483. // Input : *ppline -
  1484. //-----------------------------------------------------------------------------
  1485. void V_StripLeadingWhitespace( char *ppline )
  1486. {
  1487. Assert( ppline );
  1488. // Skip past initial whitespace
  1489. int skip = 0;
  1490. while( V_isspace( ppline[ skip ] ) )
  1491. skip++;
  1492. // Shuffle the rest of the string back (including the NULL-terminator)
  1493. if ( skip )
  1494. {
  1495. while( ( ppline[0] = ppline[skip] ) != 0 )
  1496. ppline++;
  1497. }
  1498. }
  1499. //-----------------------------------------------------------------------------
  1500. // Purpose:
  1501. // Input : *ppline -
  1502. //-----------------------------------------------------------------------------
  1503. void V_StripSurroundingQuotes( char *ppline )
  1504. {
  1505. Assert( ppline );
  1506. int len = V_strlen( ppline ) - 2;
  1507. if ( ( ppline[0] == '"' ) && ( len >= 0 ) && ( ppline[len+1] == '"' ) )
  1508. {
  1509. for ( int i = 0; i < len; i++ )
  1510. ppline[i] = ppline[i+1];
  1511. ppline[len] = 0;
  1512. }
  1513. }
  1514. //-----------------------------------------------------------------------------
  1515. // Purpose:
  1516. // Input : *in -
  1517. // *out -
  1518. // outSize -
  1519. //-----------------------------------------------------------------------------
  1520. void V_StripExtension( const char *in, char *out, int outSize )
  1521. {
  1522. // Find the last dot. If it's followed by a dot or a slash, then it's part of a
  1523. // directory specifier like ../../somedir/./blah.
  1524. // scan backward for '.'
  1525. int end = V_strlen( in ) - 1;
  1526. while ( end > 0 && in[end] != '.' && !PATHSEPARATOR( in[end] ) )
  1527. {
  1528. --end;
  1529. }
  1530. if (end > 0 && !PATHSEPARATOR( in[end] ) && end < outSize)
  1531. {
  1532. int nChars = min( end, outSize-1 );
  1533. if ( out != in )
  1534. {
  1535. memcpy( out, in, nChars );
  1536. }
  1537. out[nChars] = 0;
  1538. }
  1539. else
  1540. {
  1541. // nothing found
  1542. if ( out != in )
  1543. {
  1544. V_strncpy( out, in, outSize );
  1545. }
  1546. }
  1547. }
  1548. //-----------------------------------------------------------------------------
  1549. // Purpose:
  1550. // Input : *path -
  1551. // *extension -
  1552. // pathStringLength -
  1553. //-----------------------------------------------------------------------------
  1554. void V_DefaultExtension( char *path, const char *extension, int pathStringLength )
  1555. {
  1556. Assert( path );
  1557. Assert( pathStringLength >= 1 );
  1558. Assert( extension );
  1559. Assert( extension[0] == '.' );
  1560. char *src;
  1561. // if path doesn't have a .EXT, append extension
  1562. // (extension should include the .)
  1563. src = path + V_strlen(path) - 1;
  1564. while ( !PATHSEPARATOR( *src ) && ( src > path ) )
  1565. {
  1566. if (*src == '.')
  1567. {
  1568. // it has an extension
  1569. return;
  1570. }
  1571. src--;
  1572. }
  1573. // Concatenate the desired extension
  1574. V_strncat( path, extension, pathStringLength, COPY_ALL_CHARACTERS );
  1575. }
  1576. //-----------------------------------------------------------------------------
  1577. // Purpose: Force extension...
  1578. // Input : *path -
  1579. // *extension -
  1580. // pathStringLength -
  1581. //-----------------------------------------------------------------------------
  1582. void V_SetExtension( char *path, const char *extension, int pathStringLength )
  1583. {
  1584. V_StripExtension( path, path, pathStringLength );
  1585. // We either had an extension and stripped it, or didn't have an extension
  1586. // at all. Either way, we need to concatenate our extension now.
  1587. // extension is not required to start with '.', so if it's not there,
  1588. // then append that first.
  1589. if ( extension[0] != '.' )
  1590. {
  1591. V_strncat( path, ".", pathStringLength, COPY_ALL_CHARACTERS );
  1592. }
  1593. V_strncat( path, extension, pathStringLength, COPY_ALL_CHARACTERS );
  1594. }
  1595. //-----------------------------------------------------------------------------
  1596. // Purpose: Remove final filename from string
  1597. // Input : *path -
  1598. // Output : void V_StripFilename
  1599. //-----------------------------------------------------------------------------
  1600. void V_StripFilename (char *path)
  1601. {
  1602. int length;
  1603. length = V_strlen( path )-1;
  1604. if ( length <= 0 )
  1605. return;
  1606. while ( length > 0 &&
  1607. !PATHSEPARATOR( path[length] ) )
  1608. {
  1609. length--;
  1610. }
  1611. path[ length ] = 0;
  1612. }
  1613. #ifdef _WIN32
  1614. #define CORRECT_PATH_SEPARATOR '\\'
  1615. #define INCORRECT_PATH_SEPARATOR '/'
  1616. #elif POSIX
  1617. #define CORRECT_PATH_SEPARATOR '/'
  1618. #define INCORRECT_PATH_SEPARATOR '\\'
  1619. #endif
  1620. //-----------------------------------------------------------------------------
  1621. // Purpose: Changes all '/' or '\' characters into separator
  1622. // Input : *pname -
  1623. // separator -
  1624. //-----------------------------------------------------------------------------
  1625. void V_FixSlashes( char *pname, char separator /* = CORRECT_PATH_SEPARATOR */ )
  1626. {
  1627. while ( *pname )
  1628. {
  1629. if ( *pname == INCORRECT_PATH_SEPARATOR || *pname == CORRECT_PATH_SEPARATOR )
  1630. {
  1631. *pname = separator;
  1632. }
  1633. pname++;
  1634. }
  1635. }
  1636. //-----------------------------------------------------------------------------
  1637. // Purpose: This function fixes cases of filenames like materials\\blah.vmt or somepath\otherpath\\ and removes the extra double slash.
  1638. //-----------------------------------------------------------------------------
  1639. void V_FixDoubleSlashes( char *pStr )
  1640. {
  1641. int len = V_strlen( pStr );
  1642. for ( int i=1; i < len-1; i++ )
  1643. {
  1644. if ( (pStr[i] == '/' || pStr[i] == '\\') && (pStr[i+1] == '/' || pStr[i+1] == '\\') )
  1645. {
  1646. // This means there's a double slash somewhere past the start of the filename. That
  1647. // can happen in Hammer if they use a material in the root directory. You'll get a filename
  1648. // that looks like 'materials\\blah.vmt'
  1649. V_memmove( &pStr[i], &pStr[i+1], len - i );
  1650. --len;
  1651. }
  1652. }
  1653. }
  1654. //-----------------------------------------------------------------------------
  1655. // Purpose: Strip off the last directory from dirName
  1656. // Input : *dirName -
  1657. // maxlen -
  1658. // Output : Returns true on success, false on failure.
  1659. //-----------------------------------------------------------------------------
  1660. bool V_StripLastDir( char *dirName, int maxlen )
  1661. {
  1662. if( dirName[0] == 0 ||
  1663. !V_stricmp( dirName, "./" ) ||
  1664. !V_stricmp( dirName, ".\\" ) )
  1665. return false;
  1666. int len = V_strlen( dirName );
  1667. Assert( len < maxlen );
  1668. // skip trailing slash
  1669. if ( PATHSEPARATOR( dirName[len-1] ) )
  1670. {
  1671. len--;
  1672. }
  1673. while ( len > 0 )
  1674. {
  1675. if ( PATHSEPARATOR( dirName[len-1] ) )
  1676. {
  1677. dirName[len] = 0;
  1678. V_FixSlashes( dirName, CORRECT_PATH_SEPARATOR );
  1679. return true;
  1680. }
  1681. len--;
  1682. }
  1683. // Allow it to return an empty string and true. This can happen if something like "tf2/" is passed in.
  1684. // The correct behavior is to strip off the last directory ("tf2") and return true.
  1685. if( len == 0 )
  1686. {
  1687. V_snprintf( dirName, maxlen, ".%c", CORRECT_PATH_SEPARATOR );
  1688. return true;
  1689. }
  1690. return true;
  1691. }
  1692. //-----------------------------------------------------------------------------
  1693. // Purpose: Returns a pointer to the beginning of the unqualified file name
  1694. // (no path information)
  1695. // Input: in - file name (may be unqualified, relative or absolute path)
  1696. // Output: pointer to unqualified file name
  1697. //-----------------------------------------------------------------------------
  1698. const char * V_UnqualifiedFileName( const char * in )
  1699. {
  1700. // back up until the character after the first path separator we find,
  1701. // or the beginning of the string
  1702. const char * out = in + strlen( in ) - 1;
  1703. while ( ( out > in ) && ( !PATHSEPARATOR( *( out-1 ) ) ) )
  1704. out--;
  1705. return out;
  1706. }
  1707. //-----------------------------------------------------------------------------
  1708. // Purpose: Composes a path and filename together, inserting a path separator
  1709. // if need be
  1710. // Input: path - path to use
  1711. // filename - filename to use
  1712. // dest - buffer to compose result in
  1713. // destSize - size of destination buffer
  1714. //-----------------------------------------------------------------------------
  1715. void V_ComposeFileName( const char *path, const char *filename, char *dest, int destSize )
  1716. {
  1717. V_strncpy( dest, path, destSize );
  1718. V_FixSlashes( dest );
  1719. V_AppendSlash( dest, destSize );
  1720. V_strncat( dest, filename, destSize, COPY_ALL_CHARACTERS );
  1721. V_FixSlashes( dest );
  1722. }
  1723. //-----------------------------------------------------------------------------
  1724. // Purpose:
  1725. // Input : *path -
  1726. // *dest -
  1727. // destSize -
  1728. // Output : void V_ExtractFilePath
  1729. //-----------------------------------------------------------------------------
  1730. bool V_ExtractFilePath (const char *path, char *dest, int destSize )
  1731. {
  1732. Assert( destSize >= 1 );
  1733. if ( destSize < 1 )
  1734. {
  1735. return false;
  1736. }
  1737. // Last char
  1738. int len = V_strlen(path);
  1739. const char *src = path + (len ? len-1 : 0);
  1740. // back up until a \ or the start
  1741. while ( src != path && !PATHSEPARATOR( *(src-1) ) )
  1742. {
  1743. src--;
  1744. }
  1745. int copysize = min( (int)((ptrdiff_t)src - (ptrdiff_t)path), destSize - 1 );
  1746. memcpy( dest, path, copysize );
  1747. dest[copysize] = 0;
  1748. return copysize != 0 ? true : false;
  1749. }
  1750. //-----------------------------------------------------------------------------
  1751. // Purpose:
  1752. // Input : *path -
  1753. // *dest -
  1754. // destSize -
  1755. // Output : void V_ExtractFileExtension
  1756. //-----------------------------------------------------------------------------
  1757. void V_ExtractFileExtension( const char *path, char *dest, int destSize )
  1758. {
  1759. *dest = NULL;
  1760. const char * extension = V_GetFileExtension( path );
  1761. if ( NULL != extension )
  1762. V_strncpy( dest, extension, destSize );
  1763. }
  1764. //-----------------------------------------------------------------------------
  1765. // Purpose: Returns a pointer to the file extension within a file name string
  1766. // Input: in - file name
  1767. // Output: pointer to beginning of extension (after the "."), or NULL
  1768. // if there is no extension
  1769. //-----------------------------------------------------------------------------
  1770. const char * V_GetFileExtension( const char * path )
  1771. {
  1772. const char *src;
  1773. src = path + strlen(path) - 1;
  1774. //
  1775. // back up until a . or the start
  1776. //
  1777. while (src != path && *(src-1) != '.' )
  1778. src--;
  1779. // check to see if the '.' is part of a pathname
  1780. if (src == path || PATHSEPARATOR( *src ) )
  1781. {
  1782. return NULL; // no extension
  1783. }
  1784. return src;
  1785. }
  1786. //-----------------------------------------------------------------------------
  1787. // Purpose: Returns a pointer to the filename part of a path string
  1788. // Input: in - file name
  1789. // Output: pointer to beginning of filename (after the "/"). If there were no /,
  1790. // output is identical to input
  1791. //-----------------------------------------------------------------------------
  1792. const char * V_GetFileName( const char * path )
  1793. {
  1794. return V_UnqualifiedFileName( path );
  1795. }
  1796. bool V_RemoveDotSlashes( char *pFilename, char separator, bool bRemoveDoubleSlashes /* = true */ )
  1797. {
  1798. char *pIn = pFilename;
  1799. char *pOut = pFilename;
  1800. bool bRetVal = true;
  1801. bool bBoundary = true;
  1802. while ( *pIn )
  1803. {
  1804. if ( bBoundary && pIn[0] == '.' && pIn[1] == '.' && ( PATHSEPARATOR( pIn[2] ) || !pIn[2] ) )
  1805. {
  1806. // Get rid of /../ or trailing /.. by backing pOut up to previous separator
  1807. // Eat the last separator (or repeated separators) we wrote out
  1808. while ( pOut != pFilename && pOut[-1] == separator )
  1809. {
  1810. --pOut;
  1811. }
  1812. while ( true )
  1813. {
  1814. if ( pOut == pFilename )
  1815. {
  1816. bRetVal = false; // backwards compat. return value, even though we continue handling
  1817. break;
  1818. }
  1819. --pOut;
  1820. if ( *pOut == separator )
  1821. {
  1822. break;
  1823. }
  1824. }
  1825. // Skip the '..' but not the slash, next loop iteration will handle separator
  1826. pIn += 2;
  1827. bBoundary = ( pOut == pFilename );
  1828. }
  1829. else if ( bBoundary && pIn[0] == '.' && ( PATHSEPARATOR( pIn[1] ) || !pIn[1] ) )
  1830. {
  1831. // Handle "./" by simply skipping this sequence. bBoundary is unchanged.
  1832. if ( PATHSEPARATOR( pIn[1] ) )
  1833. {
  1834. pIn += 2;
  1835. }
  1836. else
  1837. {
  1838. // Special case: if trailing "." is preceded by separator, eg "path/.",
  1839. // then the final separator should also be stripped. bBoundary may then
  1840. // be in an incorrect state, but we are at the end of processing anyway
  1841. // so we don't really care (the processing loop is about to terminate).
  1842. if ( pOut != pFilename && pOut[-1] == separator )
  1843. {
  1844. --pOut;
  1845. }
  1846. pIn += 1;
  1847. }
  1848. }
  1849. else if ( PATHSEPARATOR( pIn[0] ) )
  1850. {
  1851. *pOut = separator;
  1852. pOut += 1 - (bBoundary & bRemoveDoubleSlashes & (pOut != pFilename));
  1853. pIn += 1;
  1854. bBoundary = true;
  1855. }
  1856. else
  1857. {
  1858. if ( pOut != pIn )
  1859. {
  1860. *pOut = *pIn;
  1861. }
  1862. pOut += 1;
  1863. pIn += 1;
  1864. bBoundary = false;
  1865. }
  1866. }
  1867. *pOut = 0;
  1868. return bRetVal;
  1869. }
  1870. void V_AppendSlash( char *pStr, int strSize )
  1871. {
  1872. int len = V_strlen( pStr );
  1873. if ( len > 0 && !PATHSEPARATOR(pStr[len-1]) )
  1874. {
  1875. if ( len+1 >= strSize )
  1876. Error( "V_AppendSlash: ran out of space on %s.", pStr );
  1877. pStr[len] = CORRECT_PATH_SEPARATOR;
  1878. pStr[len+1] = 0;
  1879. }
  1880. }
  1881. void V_MakeAbsolutePath( char *pOut, int outLen, const char *pPath, const char *pStartingDir )
  1882. {
  1883. if ( V_IsAbsolutePath( pPath ) )
  1884. {
  1885. // pPath is not relative.. just copy it.
  1886. V_strncpy( pOut, pPath, outLen );
  1887. }
  1888. else
  1889. {
  1890. // Make sure the starting directory is absolute..
  1891. if ( pStartingDir && V_IsAbsolutePath( pStartingDir ) )
  1892. {
  1893. V_strncpy( pOut, pStartingDir, outLen );
  1894. }
  1895. else
  1896. {
  1897. if ( !_getcwd( pOut, outLen ) )
  1898. Error( "V_MakeAbsolutePath: _getcwd failed." );
  1899. if ( pStartingDir )
  1900. {
  1901. V_AppendSlash( pOut, outLen );
  1902. V_strncat( pOut, pStartingDir, outLen, COPY_ALL_CHARACTERS );
  1903. }
  1904. }
  1905. // Concatenate the paths.
  1906. V_AppendSlash( pOut, outLen );
  1907. V_strncat( pOut, pPath, outLen, COPY_ALL_CHARACTERS );
  1908. }
  1909. if ( !V_RemoveDotSlashes( pOut ) )
  1910. Error( "V_MakeAbsolutePath: tried to \"..\" past the root." );
  1911. //V_FixSlashes( pOut ); - handled by V_RemoveDotSlashes
  1912. }
  1913. //-----------------------------------------------------------------------------
  1914. // Makes a relative path
  1915. //-----------------------------------------------------------------------------
  1916. bool V_MakeRelativePath( const char *pFullPath, const char *pDirectory, char *pRelativePath, int nBufLen )
  1917. {
  1918. pRelativePath[0] = 0;
  1919. const char *pPath = pFullPath;
  1920. const char *pDir = pDirectory;
  1921. // Strip out common parts of the path
  1922. const char *pLastCommonPath = NULL;
  1923. const char *pLastCommonDir = NULL;
  1924. while ( *pPath && ( FastToLower( *pPath ) == FastToLower( *pDir ) ||
  1925. ( PATHSEPARATOR( *pPath ) && ( PATHSEPARATOR( *pDir ) || (*pDir == 0) ) ) ) )
  1926. {
  1927. if ( PATHSEPARATOR( *pPath ) )
  1928. {
  1929. pLastCommonPath = pPath + 1;
  1930. pLastCommonDir = pDir + 1;
  1931. }
  1932. if ( *pDir == 0 )
  1933. {
  1934. --pLastCommonDir;
  1935. break;
  1936. }
  1937. ++pDir; ++pPath;
  1938. }
  1939. // Nothing in common
  1940. if ( !pLastCommonPath )
  1941. return false;
  1942. // For each path separator remaining in the dir, need a ../
  1943. int nOutLen = 0;
  1944. bool bLastCharWasSeparator = true;
  1945. for ( ; *pLastCommonDir; ++pLastCommonDir )
  1946. {
  1947. if ( PATHSEPARATOR( *pLastCommonDir ) )
  1948. {
  1949. pRelativePath[nOutLen++] = '.';
  1950. pRelativePath[nOutLen++] = '.';
  1951. pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR;
  1952. bLastCharWasSeparator = true;
  1953. }
  1954. else
  1955. {
  1956. bLastCharWasSeparator = false;
  1957. }
  1958. }
  1959. // Deal with relative paths not specified with a trailing slash
  1960. if ( !bLastCharWasSeparator )
  1961. {
  1962. pRelativePath[nOutLen++] = '.';
  1963. pRelativePath[nOutLen++] = '.';
  1964. pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR;
  1965. }
  1966. // Copy the remaining part of the relative path over, fixing the path separators
  1967. for ( ; *pLastCommonPath; ++pLastCommonPath )
  1968. {
  1969. if ( PATHSEPARATOR( *pLastCommonPath ) )
  1970. {
  1971. pRelativePath[nOutLen++] = CORRECT_PATH_SEPARATOR;
  1972. }
  1973. else
  1974. {
  1975. pRelativePath[nOutLen++] = *pLastCommonPath;
  1976. }
  1977. // Check for overflow
  1978. if ( nOutLen == nBufLen - 1 )
  1979. break;
  1980. }
  1981. pRelativePath[nOutLen] = 0;
  1982. return true;
  1983. }
  1984. //-----------------------------------------------------------------------------
  1985. // small helper function shared by lots of modules
  1986. //-----------------------------------------------------------------------------
  1987. bool V_IsAbsolutePath( const char *pStr )
  1988. {
  1989. bool bIsAbsolute = ( pStr[0] && pStr[1] == ':' ) || pStr[0] == '/' || pStr[0] == '\\';
  1990. if ( IsX360() && !bIsAbsolute )
  1991. {
  1992. bIsAbsolute = ( V_stristr( pStr, ":" ) != NULL );
  1993. }
  1994. return bIsAbsolute;
  1995. }
  1996. // Copies at most nCharsToCopy bytes from pIn into pOut.
  1997. // Returns false if it would have overflowed pOut's buffer.
  1998. static bool CopyToMaxChars( char *pOut, int outSize, const char *pIn, int nCharsToCopy )
  1999. {
  2000. if ( outSize == 0 )
  2001. return false;
  2002. int iOut = 0;
  2003. while ( *pIn && nCharsToCopy > 0 )
  2004. {
  2005. if ( iOut == (outSize-1) )
  2006. {
  2007. pOut[iOut] = 0;
  2008. return false;
  2009. }
  2010. pOut[iOut] = *pIn;
  2011. ++iOut;
  2012. ++pIn;
  2013. --nCharsToCopy;
  2014. }
  2015. pOut[iOut] = 0;
  2016. return true;
  2017. }
  2018. //-----------------------------------------------------------------------------
  2019. // Fixes up a file name, removing dot slashes, fixing slashes, converting to lowercase, etc.
  2020. //-----------------------------------------------------------------------------
  2021. void V_FixupPathName( char *pOut, size_t nOutLen, const char *pPath )
  2022. {
  2023. V_strncpy( pOut, pPath, nOutLen );
  2024. V_RemoveDotSlashes( pOut, CORRECT_PATH_SEPARATOR, true );
  2025. #ifdef WIN32
  2026. V_strlower( pOut );
  2027. #endif
  2028. }
  2029. // Returns true if it completed successfully.
  2030. // If it would overflow pOut, it fills as much as it can and returns false.
  2031. bool V_StrSubst(
  2032. const char *pIn,
  2033. const char *pMatch,
  2034. const char *pReplaceWith,
  2035. char *pOut,
  2036. int outLen,
  2037. bool bCaseSensitive
  2038. )
  2039. {
  2040. int replaceFromLen = strlen( pMatch );
  2041. int replaceToLen = strlen( pReplaceWith );
  2042. const char *pInStart = pIn;
  2043. char *pOutPos = pOut;
  2044. pOutPos[0] = 0;
  2045. while ( 1 )
  2046. {
  2047. int nRemainingOut = outLen - (pOutPos - pOut);
  2048. const char *pTestPos = ( bCaseSensitive ? strstr( pInStart, pMatch ) : V_stristr( pInStart, pMatch ) );
  2049. if ( pTestPos )
  2050. {
  2051. // Found an occurence of pMatch. First, copy whatever leads up to the string.
  2052. int copyLen = pTestPos - pInStart;
  2053. if ( !CopyToMaxChars( pOutPos, nRemainingOut, pInStart, copyLen ) )
  2054. return false;
  2055. // Did we hit the end of the output string?
  2056. if ( copyLen > nRemainingOut-1 )
  2057. return false;
  2058. pOutPos += strlen( pOutPos );
  2059. nRemainingOut = outLen - (pOutPos - pOut);
  2060. // Now add the replacement string.
  2061. if ( !CopyToMaxChars( pOutPos, nRemainingOut, pReplaceWith, replaceToLen ) )
  2062. return false;
  2063. pInStart += copyLen + replaceFromLen;
  2064. pOutPos += replaceToLen;
  2065. }
  2066. else
  2067. {
  2068. // We're at the end of pIn. Copy whatever remains and get out.
  2069. int copyLen = strlen( pInStart );
  2070. V_strncpy( pOutPos, pInStart, nRemainingOut );
  2071. return ( copyLen <= nRemainingOut-1 );
  2072. }
  2073. }
  2074. }
  2075. char* AllocString( const char *pStr, int nMaxChars )
  2076. {
  2077. int allocLen;
  2078. if ( nMaxChars == -1 )
  2079. allocLen = strlen( pStr ) + 1;
  2080. else
  2081. allocLen = min( (int)strlen(pStr), nMaxChars ) + 1;
  2082. char *pOut = new char[allocLen];
  2083. V_strncpy( pOut, pStr, allocLen );
  2084. return pOut;
  2085. }
  2086. void V_SplitString2( const char *pString, const char **pSeparators, int nSeparators, CUtlVector<char*> &outStrings )
  2087. {
  2088. outStrings.Purge();
  2089. const char *pCurPos = pString;
  2090. while ( 1 )
  2091. {
  2092. int iFirstSeparator = -1;
  2093. const char *pFirstSeparator = 0;
  2094. for ( int i=0; i < nSeparators; i++ )
  2095. {
  2096. const char *pTest = V_stristr( pCurPos, pSeparators[i] );
  2097. if ( pTest && (!pFirstSeparator || pTest < pFirstSeparator) )
  2098. {
  2099. iFirstSeparator = i;
  2100. pFirstSeparator = pTest;
  2101. }
  2102. }
  2103. if ( pFirstSeparator )
  2104. {
  2105. // Split on this separator and continue on.
  2106. int separatorLen = strlen( pSeparators[iFirstSeparator] );
  2107. if ( pFirstSeparator > pCurPos )
  2108. {
  2109. outStrings.AddToTail( AllocString( pCurPos, pFirstSeparator-pCurPos ) );
  2110. }
  2111. pCurPos = pFirstSeparator + separatorLen;
  2112. }
  2113. else
  2114. {
  2115. // Copy the rest of the string
  2116. if ( strlen( pCurPos ) )
  2117. {
  2118. outStrings.AddToTail( AllocString( pCurPos, -1 ) );
  2119. }
  2120. return;
  2121. }
  2122. }
  2123. }
  2124. void V_SplitString( const char *pString, const char *pSeparator, CUtlVector<char*> &outStrings )
  2125. {
  2126. V_SplitString2( pString, &pSeparator, 1, outStrings );
  2127. }
  2128. bool V_GetCurrentDirectory( char *pOut, int maxLen )
  2129. {
  2130. return _getcwd( pOut, maxLen ) == pOut;
  2131. }
  2132. bool V_SetCurrentDirectory( const char *pDirName )
  2133. {
  2134. return _chdir( pDirName ) == 0;
  2135. }
  2136. // This function takes a slice out of pStr and stores it in pOut.
  2137. // It follows the Python slice convention:
  2138. // Negative numbers wrap around the string (-1 references the last character).
  2139. // Numbers are clamped to the end of the string.
  2140. void V_StrSlice( const char *pStr, int firstChar, int lastCharNonInclusive, char *pOut, int outSize )
  2141. {
  2142. if ( outSize == 0 )
  2143. return;
  2144. int length = strlen( pStr );
  2145. // Fixup the string indices.
  2146. if ( firstChar < 0 )
  2147. {
  2148. firstChar = length - (-firstChar % length);
  2149. }
  2150. else if ( firstChar >= length )
  2151. {
  2152. pOut[0] = 0;
  2153. return;
  2154. }
  2155. if ( lastCharNonInclusive < 0 )
  2156. {
  2157. lastCharNonInclusive = length - (-lastCharNonInclusive % length);
  2158. }
  2159. else if ( lastCharNonInclusive > length )
  2160. {
  2161. lastCharNonInclusive %= length;
  2162. }
  2163. if ( lastCharNonInclusive <= firstChar )
  2164. {
  2165. pOut[0] = 0;
  2166. return;
  2167. }
  2168. int copyLen = lastCharNonInclusive - firstChar;
  2169. if ( copyLen <= (outSize-1) )
  2170. {
  2171. memcpy( pOut, &pStr[firstChar], copyLen );
  2172. pOut[copyLen] = 0;
  2173. }
  2174. else
  2175. {
  2176. memcpy( pOut, &pStr[firstChar], outSize-1 );
  2177. pOut[outSize-1] = 0;
  2178. }
  2179. }
  2180. void V_StrLeft( const char *pStr, int nChars, char *pOut, int outSize )
  2181. {
  2182. if ( nChars == 0 )
  2183. {
  2184. if ( outSize != 0 )
  2185. pOut[0] = 0;
  2186. return;
  2187. }
  2188. V_StrSlice( pStr, 0, nChars, pOut, outSize );
  2189. }
  2190. void V_StrRight( const char *pStr, int nChars, char *pOut, int outSize )
  2191. {
  2192. int len = strlen( pStr );
  2193. if ( nChars >= len )
  2194. {
  2195. V_strncpy( pOut, pStr, outSize );
  2196. }
  2197. else
  2198. {
  2199. V_StrSlice( pStr, -nChars, strlen( pStr ), pOut, outSize );
  2200. }
  2201. }
  2202. //-----------------------------------------------------------------------------
  2203. // Convert multibyte to wchar + back
  2204. //-----------------------------------------------------------------------------
  2205. void V_strtowcs( const char *pString, int nInSize, wchar_t *pWString, int nOutSizeInBytes )
  2206. {
  2207. Assert( nOutSizeInBytes >= sizeof(pWString[0]) );
  2208. #ifdef _WIN32
  2209. int nOutSizeInChars = nOutSizeInBytes / sizeof(pWString[0]);
  2210. int result = MultiByteToWideChar( CP_UTF8, 0, pString, nInSize, pWString, nOutSizeInChars );
  2211. // If the string completely fails to fit then MultiByteToWideChar will return 0.
  2212. // If the string exactly fits but with no room for a null-terminator then MultiByteToWideChar
  2213. // will happily fill the buffer and omit the null-terminator, returning nOutSizeInChars.
  2214. // Either way we need to return an empty string rather than a bogus and possibly not
  2215. // null-terminated result.
  2216. if ( result <= 0 || result >= nOutSizeInChars )
  2217. {
  2218. // If nInSize includes the null-terminator then a result of nOutSizeInChars is
  2219. // legal. We check this by seeing if the last character in the output buffer is
  2220. // a zero.
  2221. if ( result == nOutSizeInChars && pWString[ nOutSizeInChars - 1 ] == 0)
  2222. {
  2223. // We're okay! Do nothing.
  2224. }
  2225. else
  2226. {
  2227. // The string completely to fit. Null-terminate the buffer.
  2228. *pWString = L'\0';
  2229. }
  2230. }
  2231. else
  2232. {
  2233. // We have successfully converted our string. Now we need to null-terminate it, because
  2234. // MultiByteToWideChar will only do that if nInSize includes the source null-terminator!
  2235. pWString[ result ] = 0;
  2236. }
  2237. #elif POSIX
  2238. if ( mbstowcs( pWString, pString, nOutSizeInBytes / sizeof(pWString[0]) ) <= 0 )
  2239. {
  2240. *pWString = 0;
  2241. }
  2242. #endif
  2243. }
  2244. void V_wcstostr( const wchar_t *pWString, int nInSize, char *pString, int nOutSizeInChars )
  2245. {
  2246. #ifdef _WIN32
  2247. int result = WideCharToMultiByte( CP_UTF8, 0, pWString, nInSize, pString, nOutSizeInChars, NULL, NULL );
  2248. // If the string completely fails to fit then MultiByteToWideChar will return 0.
  2249. // If the string exactly fits but with no room for a null-terminator then MultiByteToWideChar
  2250. // will happily fill the buffer and omit the null-terminator, returning nOutSizeInChars.
  2251. // Either way we need to return an empty string rather than a bogus and possibly not
  2252. // null-terminated result.
  2253. if ( result <= 0 || result >= nOutSizeInChars )
  2254. {
  2255. // If nInSize includes the null-terminator then a result of nOutSizeInChars is
  2256. // legal. We check this by seeing if the last character in the output buffer is
  2257. // a zero.
  2258. if ( result == nOutSizeInChars && pWString[ nOutSizeInChars - 1 ] == 0)
  2259. {
  2260. // We're okay! Do nothing.
  2261. }
  2262. else
  2263. {
  2264. *pString = '\0';
  2265. }
  2266. }
  2267. else
  2268. {
  2269. // We have successfully converted our string. Now we need to null-terminate it, because
  2270. // MultiByteToWideChar will only do that if nInSize includes the source null-terminator!
  2271. pString[ result ] = '\0';
  2272. }
  2273. #elif POSIX
  2274. if ( wcstombs( pString, pWString, nOutSizeInChars ) <= 0 )
  2275. {
  2276. *pString = '\0';
  2277. }
  2278. #endif
  2279. }
  2280. //--------------------------------------------------------------------------------
  2281. // backslashification
  2282. //--------------------------------------------------------------------------------
  2283. static char s_BackSlashMap[]="\tt\nn\rr\"\"\\\\";
  2284. char *V_AddBackSlashesToSpecialChars( char const *pSrc )
  2285. {
  2286. // first, count how much space we are going to need
  2287. int nSpaceNeeded = 0;
  2288. for( char const *pScan = pSrc; *pScan; pScan++ )
  2289. {
  2290. nSpaceNeeded++;
  2291. for(char const *pCharSet=s_BackSlashMap; *pCharSet; pCharSet += 2 )
  2292. {
  2293. if ( *pCharSet == *pScan )
  2294. nSpaceNeeded++; // we need to store a bakslash
  2295. }
  2296. }
  2297. char *pRet = new char[ nSpaceNeeded + 1 ]; // +1 for null
  2298. char *pOut = pRet;
  2299. for( char const *pScan = pSrc; *pScan; pScan++ )
  2300. {
  2301. bool bIsSpecial = false;
  2302. for(char const *pCharSet=s_BackSlashMap; *pCharSet; pCharSet += 2 )
  2303. {
  2304. if ( *pCharSet == *pScan )
  2305. {
  2306. *( pOut++ ) = '\\';
  2307. *( pOut++ ) = pCharSet[1];
  2308. bIsSpecial = true;
  2309. break;
  2310. }
  2311. }
  2312. if (! bIsSpecial )
  2313. {
  2314. *( pOut++ ) = *pScan;
  2315. }
  2316. }
  2317. *( pOut++ ) = 0;
  2318. return pRet;
  2319. }
  2320. //-----------------------------------------------------------------------------
  2321. // Purpose: Helper for converting a numeric value to a hex digit, value should be 0-15.
  2322. //-----------------------------------------------------------------------------
  2323. char cIntToHexDigit( int nValue )
  2324. {
  2325. Assert( nValue >= 0 && nValue <= 15 );
  2326. return "0123456789ABCDEF"[ nValue & 15 ];
  2327. }
  2328. //-----------------------------------------------------------------------------
  2329. // Purpose: Helper for converting a hex char value to numeric, return -1 if the char
  2330. // is not a valid hex digit.
  2331. //-----------------------------------------------------------------------------
  2332. int iHexCharToInt( char cValue )
  2333. {
  2334. int32 iValue = cValue;
  2335. if ( (uint32)( iValue - '0' ) < 10 )
  2336. return iValue - '0';
  2337. iValue |= 0x20;
  2338. if ( (uint32)( iValue - 'a' ) < 6 )
  2339. return iValue - 'a' + 10;
  2340. return -1;
  2341. }
  2342. //-----------------------------------------------------------------------------
  2343. // Purpose: Internal implementation of encode, works in the strict RFC manner, or
  2344. // with spaces turned to + like HTML form encoding.
  2345. //-----------------------------------------------------------------------------
  2346. void Q_URLEncodeInternal( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen, bool bUsePlusForSpace )
  2347. {
  2348. if ( nDestLen < 3*nSourceLen )
  2349. {
  2350. pchDest[0] = '\0';
  2351. AssertMsg( false, "Target buffer for Q_URLEncode needs to be 3 times larger than source to guarantee enough space\n" );
  2352. return;
  2353. }
  2354. int iDestPos = 0;
  2355. for ( int i=0; i < nSourceLen; ++i )
  2356. {
  2357. // We allow only a-z, A-Z, 0-9, period, underscore, and hyphen to pass through unescaped.
  2358. // These are the characters allowed by both the original RFC 1738 and the latest RFC 3986.
  2359. // Current specs also allow '~', but that is forbidden under original RFC 1738.
  2360. if ( !( pchSource[i] >= 'a' && pchSource[i] <= 'z' ) && !( pchSource[i] >= 'A' && pchSource[i] <= 'Z' ) && !(pchSource[i] >= '0' && pchSource[i] <= '9' )
  2361. && pchSource[i] != '-' && pchSource[i] != '_' && pchSource[i] != '.'
  2362. )
  2363. {
  2364. if ( bUsePlusForSpace && pchSource[i] == ' ' )
  2365. {
  2366. pchDest[iDestPos++] = '+';
  2367. }
  2368. else
  2369. {
  2370. pchDest[iDestPos++] = '%';
  2371. uint8 iValue = pchSource[i];
  2372. if ( iValue == 0 )
  2373. {
  2374. pchDest[iDestPos++] = '0';
  2375. pchDest[iDestPos++] = '0';
  2376. }
  2377. else
  2378. {
  2379. char cHexDigit1 = cIntToHexDigit( iValue % 16 );
  2380. iValue /= 16;
  2381. char cHexDigit2 = cIntToHexDigit( iValue );
  2382. pchDest[iDestPos++] = cHexDigit2;
  2383. pchDest[iDestPos++] = cHexDigit1;
  2384. }
  2385. }
  2386. }
  2387. else
  2388. {
  2389. pchDest[iDestPos++] = pchSource[i];
  2390. }
  2391. }
  2392. // Null terminate
  2393. pchDest[iDestPos++] = 0;
  2394. }
  2395. //-----------------------------------------------------------------------------
  2396. // Purpose: Internal implementation of decode, works in the strict RFC manner, or
  2397. // with spaces turned to + like HTML form encoding.
  2398. //
  2399. // Returns the amount of space used in the output buffer.
  2400. //-----------------------------------------------------------------------------
  2401. size_t Q_URLDecodeInternal( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen, bool bUsePlusForSpace )
  2402. {
  2403. if ( nDecodeDestLen < nEncodedSourceLen )
  2404. {
  2405. AssertMsg( false, "Q_URLDecode needs a dest buffer at least as large as the source" );
  2406. return 0;
  2407. }
  2408. int iDestPos = 0;
  2409. for( int i=0; i < nEncodedSourceLen; ++i )
  2410. {
  2411. if ( bUsePlusForSpace && pchEncodedSource[i] == '+' )
  2412. {
  2413. pchDecodeDest[ iDestPos++ ] = ' ';
  2414. }
  2415. else if ( pchEncodedSource[i] == '%' )
  2416. {
  2417. // Percent signifies an encoded value, look ahead for the hex code, convert to numeric, and use that
  2418. // First make sure we have 2 more chars
  2419. if ( i < nEncodedSourceLen - 2 )
  2420. {
  2421. char cHexDigit1 = pchEncodedSource[i+1];
  2422. char cHexDigit2 = pchEncodedSource[i+2];
  2423. // Turn the chars into a hex value, if they are not valid, then we'll
  2424. // just place the % and the following two chars direct into the string,
  2425. // even though this really shouldn't happen, who knows what bad clients
  2426. // may do with encoding.
  2427. bool bValid = false;
  2428. int iValue = iHexCharToInt( cHexDigit1 );
  2429. if ( iValue != -1 )
  2430. {
  2431. iValue *= 16;
  2432. int iValue2 = iHexCharToInt( cHexDigit2 );
  2433. if ( iValue2 != -1 )
  2434. {
  2435. iValue += iValue2;
  2436. pchDecodeDest[ iDestPos++ ] = iValue;
  2437. bValid = true;
  2438. }
  2439. }
  2440. if ( !bValid )
  2441. {
  2442. pchDecodeDest[ iDestPos++ ] = '%';
  2443. pchDecodeDest[ iDestPos++ ] = cHexDigit1;
  2444. pchDecodeDest[ iDestPos++ ] = cHexDigit2;
  2445. }
  2446. }
  2447. // Skip ahead
  2448. i += 2;
  2449. }
  2450. else
  2451. {
  2452. pchDecodeDest[ iDestPos++ ] = pchEncodedSource[i];
  2453. }
  2454. }
  2455. // We may not have extra room to NULL terminate, since this can be used on raw data, but if we do
  2456. // go ahead and do it as this can avoid bugs.
  2457. if ( iDestPos < nDecodeDestLen )
  2458. {
  2459. pchDecodeDest[iDestPos] = 0;
  2460. }
  2461. return (size_t)iDestPos;
  2462. }
  2463. //-----------------------------------------------------------------------------
  2464. // Purpose: Encodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2.
  2465. // This version of the call isn't a strict RFC implementation, but uses + for space as is
  2466. // the standard in HTML form encoding, despite it not being part of the RFC.
  2467. //
  2468. // Dest buffer should be at least as large as source buffer to guarantee room for decode.
  2469. //-----------------------------------------------------------------------------
  2470. void Q_URLEncode( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen )
  2471. {
  2472. return Q_URLEncodeInternal( pchDest, nDestLen, pchSource, nSourceLen, true );
  2473. }
  2474. //-----------------------------------------------------------------------------
  2475. // Purpose: Decodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2.
  2476. // This version of the call isn't a strict RFC implementation, but uses + for space as is
  2477. // the standard in HTML form encoding, despite it not being part of the RFC.
  2478. //
  2479. // Dest buffer should be at least as large as source buffer to guarantee room for decode.
  2480. // Dest buffer being the same as the source buffer (decode in-place) is explicitly allowed.
  2481. //-----------------------------------------------------------------------------
  2482. size_t Q_URLDecode( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen )
  2483. {
  2484. return Q_URLDecodeInternal( pchDecodeDest, nDecodeDestLen, pchEncodedSource, nEncodedSourceLen, true );
  2485. }
  2486. //-----------------------------------------------------------------------------
  2487. // Purpose: Encodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2.
  2488. // This version will not encode space as + (which HTML form encoding uses despite not being part of the RFC)
  2489. //
  2490. // Dest buffer should be at least as large as source buffer to guarantee room for decode.
  2491. //-----------------------------------------------------------------------------
  2492. void Q_URLEncodeRaw( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen )
  2493. {
  2494. return Q_URLEncodeInternal( pchDest, nDestLen, pchSource, nSourceLen, false );
  2495. }
  2496. //-----------------------------------------------------------------------------
  2497. // Purpose: Decodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2.
  2498. // This version will not recognize + as a space (which HTML form encoding uses despite not being part of the RFC)
  2499. //
  2500. // Dest buffer should be at least as large as source buffer to guarantee room for decode.
  2501. // Dest buffer being the same as the source buffer (decode in-place) is explicitly allowed.
  2502. //-----------------------------------------------------------------------------
  2503. size_t Q_URLDecodeRaw( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen )
  2504. {
  2505. return Q_URLDecodeInternal( pchDecodeDest, nDecodeDestLen, pchEncodedSource, nEncodedSourceLen, false );
  2506. }
  2507. #if defined( LINUX ) || defined( _PS3 )
  2508. extern "C" void qsort_s( void *base, size_t num, size_t width, int (*compare )(void *, const void *, const void *), void * context );
  2509. #endif
  2510. void V_qsort_s( void *base, size_t num, size_t width, int ( __cdecl *compare )(void *, const void *, const void *), void * context )
  2511. {
  2512. #if defined OSX
  2513. // the arguments are swapped 'round on the mac - awesome, huh?
  2514. return qsort_r( base, num, width, context, compare );
  2515. #else
  2516. return qsort_s( base, num, width, compare, context );
  2517. #endif
  2518. }
  2519. //-----------------------------------------------------------------------------
  2520. // Purpose: format the time and/or date with the user's current locale
  2521. // If timeVal is 0, gets the current time
  2522. //
  2523. // This is generally for use with chatroom dialogs, etc. which need to be
  2524. // able to say "Last message received: %date% at %time%"
  2525. //
  2526. // Note that this uses time_t because RTime32 is not hooked-up on the client
  2527. //-----------------------------------------------------------------------------
  2528. bool BGetLocalFormattedDateAndTime( time_t timeVal, char *pchDate, int cubDate, char *pchTime, int cubTime )
  2529. {
  2530. if ( 0 == timeVal || timeVal < 0 )
  2531. {
  2532. // get the current time
  2533. time( &timeVal );
  2534. }
  2535. if ( timeVal )
  2536. {
  2537. // Convert it to our local time
  2538. struct tm tmStruct;
  2539. struct tm tmToDisplay = *( Plat_localtime( ( const time_t* )&timeVal, &tmStruct ) );
  2540. #ifdef POSIX
  2541. if ( pchDate != NULL )
  2542. {
  2543. pchDate[ 0 ] = 0;
  2544. if ( 0 == strftime( pchDate, cubDate, "%A %b %d", &tmToDisplay ) )
  2545. return false;
  2546. }
  2547. if ( pchTime != NULL )
  2548. {
  2549. pchTime[ 0 ] = 0;
  2550. if ( 0 == strftime( pchTime, cubTime - 6, "%I:%M ", &tmToDisplay ) )
  2551. return false;
  2552. // append am/pm in lower case (since strftime doesn't have a lowercase formatting option)
  2553. if (tmToDisplay.tm_hour >= 12)
  2554. {
  2555. Q_strcat( pchTime, "p.m.", cubTime );
  2556. }
  2557. else
  2558. {
  2559. Q_strcat( pchTime, "a.m.", cubTime );
  2560. }
  2561. }
  2562. #else // WINDOWS
  2563. // convert time_t to a SYSTEMTIME
  2564. SYSTEMTIME st;
  2565. st.wHour = tmToDisplay.tm_hour;
  2566. st.wMinute = tmToDisplay.tm_min;
  2567. st.wSecond = tmToDisplay.tm_sec;
  2568. st.wDay = tmToDisplay.tm_mday;
  2569. st.wMonth = tmToDisplay.tm_mon + 1;
  2570. st.wYear = tmToDisplay.tm_year + 1900;
  2571. st.wDayOfWeek = tmToDisplay.tm_wday;
  2572. st.wMilliseconds = 0;
  2573. WCHAR rgwch[ MAX_PATH ];
  2574. if ( pchDate != NULL )
  2575. {
  2576. pchDate[ 0 ] = 0;
  2577. if ( !GetDateFormatW( LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, rgwch, MAX_PATH ) )
  2578. return false;
  2579. Q_strncpy( pchDate, CStrAutoEncode( rgwch ).ToString(), cubDate );
  2580. }
  2581. if ( pchTime != NULL )
  2582. {
  2583. pchTime[ 0 ] = 0;
  2584. if ( !GetTimeFormatW( LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, rgwch, MAX_PATH ) )
  2585. return false;
  2586. Q_strncpy( pchTime, CStrAutoEncode( rgwch ).ToString(), cubTime );
  2587. }
  2588. #endif
  2589. return true;
  2590. }
  2591. return false;
  2592. }
  2593. // And a couple of helpers so people don't have to remember the order of the parameters in the above function
  2594. bool BGetLocalFormattedDate( time_t timeVal, char *pchDate, int cubDate )
  2595. {
  2596. return BGetLocalFormattedDateAndTime( timeVal, pchDate, cubDate, NULL, 0 );
  2597. }
  2598. bool BGetLocalFormattedTime( time_t timeVal, char *pchTime, int cubTime )
  2599. {
  2600. return BGetLocalFormattedDateAndTime( timeVal, NULL, 0, pchTime, cubTime );
  2601. }
  2602. // Prints out a memory dump where stuff that's ascii is human readable, etc.
  2603. void V_LogMultiline( bool input, char const *label, const char *data, size_t len, CUtlString &output )
  2604. {
  2605. static const char HEX[] = "0123456789abcdef";
  2606. const char * direction = (input ? " << " : " >> ");
  2607. const size_t LINE_SIZE = 24;
  2608. char hex_line[LINE_SIZE * 9 / 4 + 2], asc_line[LINE_SIZE + 1];
  2609. while (len > 0)
  2610. {
  2611. V_memset(asc_line, ' ', sizeof(asc_line));
  2612. V_memset(hex_line, ' ', sizeof(hex_line));
  2613. size_t line_len = MIN(len, LINE_SIZE);
  2614. for (size_t i=0; i<line_len; ++i) {
  2615. unsigned char ch = static_cast<unsigned char>(data[i]);
  2616. asc_line[i] = ( V_isprint(ch) && !V_iscntrl(ch) ) ? data[i] : '.';
  2617. hex_line[i*2 + i/4] = HEX[ch >> 4];
  2618. hex_line[i*2 + i/4 + 1] = HEX[ch & 0xf];
  2619. }
  2620. asc_line[sizeof(asc_line)-1] = 0;
  2621. hex_line[sizeof(hex_line)-1] = 0;
  2622. output += CFmtStr( "%s %s %s %s\n", label, direction, asc_line, hex_line );
  2623. data += line_len;
  2624. len -= line_len;
  2625. }
  2626. }
  2627. #ifdef WIN32
  2628. // Win32 CRT doesn't support the full range of UChar32, has no extended planes
  2629. inline int V_iswspace( int c ) { return ( c <= 0xFFFF ) ? iswspace( (wint_t)c ) : 0; }
  2630. #else
  2631. #define V_iswspace(x) iswspace(x)
  2632. #endif
  2633. //-----------------------------------------------------------------------------
  2634. // Purpose: Slightly modified strtok. Does not modify the input string. Does
  2635. // not skip over more than one separator at a time. This allows parsing
  2636. // strings where tokens between separators may or may not be present:
  2637. //
  2638. // Door01,,,0 would be parsed as "Door01" "" "" "0"
  2639. // Door01,Open,,0 would be parsed as "Door01" "Open" "" "0"
  2640. //
  2641. // Input : token - Returns with a token, or zero length if the token was missing.
  2642. // str - String to parse.
  2643. // sep - Character to use as separator. UNDONE: allow multiple separator chars
  2644. // Output : Returns a pointer to the next token to be parsed.
  2645. //-----------------------------------------------------------------------------
  2646. const char *nexttoken(char *token, size_t nMaxTokenLen, const char *str, char sep)
  2647. {
  2648. if (nMaxTokenLen < 1)
  2649. {
  2650. Assert(nMaxTokenLen > 0);
  2651. return NULL;
  2652. }
  2653. if ((str == NULL) || (*str == '\0'))
  2654. {
  2655. *token = '\0';
  2656. return(NULL);
  2657. }
  2658. char *pTokenLast = token + nMaxTokenLen - 1;
  2659. //
  2660. // Copy everything up to the first separator into the return buffer.
  2661. // Do not include separators in the return buffer.
  2662. //
  2663. while ((*str != sep) && (*str != '\0') && (token < pTokenLast))
  2664. {
  2665. *token++ = *str++;
  2666. }
  2667. *token = '\0';
  2668. //
  2669. // Advance the pointer unless we hit the end of the input string.
  2670. //
  2671. if (*str == '\0')
  2672. {
  2673. return(str);
  2674. }
  2675. return(++str);
  2676. }
  2677. int V_StrTrim( char *pStr )
  2678. {
  2679. char *pSource = pStr;
  2680. char *pDest = pStr;
  2681. // skip white space at the beginning
  2682. while ( *pSource != 0 && V_isspace( *pSource ) )
  2683. {
  2684. pSource++;
  2685. }
  2686. // copy everything else
  2687. char *pLastWhiteBlock = NULL;
  2688. char *pStart = pDest;
  2689. while ( *pSource != 0 )
  2690. {
  2691. *pDest = *pSource++;
  2692. if ( V_isspace( *pDest ) )
  2693. {
  2694. if ( pLastWhiteBlock == NULL )
  2695. pLastWhiteBlock = pDest;
  2696. }
  2697. else
  2698. {
  2699. pLastWhiteBlock = NULL;
  2700. }
  2701. pDest++;
  2702. }
  2703. *pDest = 0;
  2704. // did we end in a whitespace block?
  2705. if ( pLastWhiteBlock != NULL )
  2706. {
  2707. // yep; shorten the string
  2708. pDest = pLastWhiteBlock;
  2709. *pLastWhiteBlock = 0;
  2710. }
  2711. return pDest - pStart;
  2712. }
  2713. #ifdef _WIN32
  2714. int64 V_strtoi64( const char *nptr, char **endptr, int base )
  2715. {
  2716. return _strtoi64( nptr, endptr, base );
  2717. }
  2718. uint64 V_strtoui64( const char *nptr, char **endptr, int base )
  2719. {
  2720. return _strtoui64( nptr, endptr, base );
  2721. }
  2722. #elif POSIX
  2723. int64 V_strtoi64( const char *nptr, char **endptr, int base )
  2724. {
  2725. return strtoll( nptr, endptr, base );
  2726. }
  2727. uint64 V_strtoui64( const char *nptr, char **endptr, int base )
  2728. {
  2729. return strtoull( nptr, endptr, base );
  2730. }
  2731. #endif
  2732. struct HtmlEntity_t
  2733. {
  2734. unsigned short uCharCode;
  2735. const char *pchEntity;
  2736. int nEntityLength;
  2737. };
  2738. const static HtmlEntity_t g_BasicHTMLEntities[] = {
  2739. { '"', "&quot;", 6 },
  2740. { '\'', "&#039;", 6 },
  2741. { '<', "&lt;", 4 },
  2742. { '>', "&gt;", 4 },
  2743. { '&', "&amp;", 5 },
  2744. { 0, NULL, 0 } // sentinel for end of array
  2745. };
  2746. const static HtmlEntity_t g_WhitespaceEntities[] = {
  2747. { ' ', "&nbsp;", 6 },
  2748. { '\n', "<br>", 4 },
  2749. { 0, NULL, 0 } // sentinel for end of array
  2750. };
  2751. struct Tier1FullHTMLEntity_t
  2752. {
  2753. uchar32 uCharCode;
  2754. const char *pchEntity;
  2755. int nEntityLength;
  2756. };
  2757. #pragma warning( push )
  2758. #pragma warning( disable : 4428 ) // universal-character-name encountered in source
  2759. const Tier1FullHTMLEntity_t g_Tier1_FullHTMLEntities[] =
  2760. {
  2761. { L'"', "&quot;", 6 },
  2762. { L'\'', "&apos;", 6 },
  2763. { L'&', "&amp;", 5 },
  2764. { L'<', "&lt;", 4 },
  2765. { L'>', "&gt;", 4 },
  2766. { L' ', "&nbsp;", 6 },
  2767. { L'\u2122', "&trade;", 7 },
  2768. { L'\u00A9', "&copy;", 6 },
  2769. { L'\u00AE', "&reg;", 5 },
  2770. { L'\u2013', "&ndash;", 7 },
  2771. { L'\u2014', "&mdash;", 7 },
  2772. { L'\u20AC', "&euro;", 6 },
  2773. { L'\u00A1', "&iexcl;", 7 },
  2774. { L'\u00A2', "&cent;", 6 },
  2775. { L'\u00A3', "&pound;", 7 },
  2776. { L'\u00A4', "&curren;", 8 },
  2777. { L'\u00A5', "&yen;", 5 },
  2778. { L'\u00A6', "&brvbar;", 8 },
  2779. { L'\u00A7', "&sect;", 6 },
  2780. { L'\u00A8', "&uml;", 5 },
  2781. { L'\u00AA', "&ordf;", 6 },
  2782. { L'\u00AB', "&laquo;", 7 },
  2783. { L'\u00AC', "&not;", 8 },
  2784. { L'\u00AD', "&shy;", 5 },
  2785. { L'\u00AF', "&macr;", 6 },
  2786. { L'\u00B0', "&deg;", 5 },
  2787. { L'\u00B1', "&plusmn;", 8 },
  2788. { L'\u00B2', "&sup2;", 6 },
  2789. { L'\u00B3', "&sup3;", 6 },
  2790. { L'\u00B4', "&acute;", 7 },
  2791. { L'\u00B5', "&micro;", 7 },
  2792. { L'\u00B6', "&para;", 6 },
  2793. { L'\u00B7', "&middot;", 8 },
  2794. { L'\u00B8', "&cedil;", 7 },
  2795. { L'\u00B9', "&sup1;", 6 },
  2796. { L'\u00BA', "&ordm;", 6 },
  2797. { L'\u00BB', "&raquo;", 7 },
  2798. { L'\u00BC', "&frac14;", 8 },
  2799. { L'\u00BD', "&frac12;", 8 },
  2800. { L'\u00BE', "&frac34;", 8 },
  2801. { L'\u00BF', "&iquest;", 8 },
  2802. { L'\u00D7', "&times;", 7 },
  2803. { L'\u00F7', "&divide;", 8 },
  2804. { L'\u00C0', "&Agrave;", 8 },
  2805. { L'\u00C1', "&Aacute;", 8 },
  2806. { L'\u00C2', "&Acirc;", 7 },
  2807. { L'\u00C3', "&Atilde;", 8 },
  2808. { L'\u00C4', "&Auml;", 6 },
  2809. { L'\u00C5', "&Aring;", 7 },
  2810. { L'\u00C6', "&AElig;", 7 },
  2811. { L'\u00C7', "&Ccedil;", 8 },
  2812. { L'\u00C8', "&Egrave;", 8 },
  2813. { L'\u00C9', "&Eacute;", 8 },
  2814. { L'\u00CA', "&Ecirc;", 7 },
  2815. { L'\u00CB', "&Euml;", 6 },
  2816. { L'\u00CC', "&Igrave;", 8 },
  2817. { L'\u00CD', "&Iacute;", 8 },
  2818. { L'\u00CE', "&Icirc;", 7 },
  2819. { L'\u00CF', "&Iuml;", 6 },
  2820. { L'\u00D0', "&ETH;", 5 },
  2821. { L'\u00D1', "&Ntilde;", 8 },
  2822. { L'\u00D2', "&Ograve;", 8 },
  2823. { L'\u00D3', "&Oacute;", 8 },
  2824. { L'\u00D4', "&Ocirc;", 7 },
  2825. { L'\u00D5', "&Otilde;", 8 },
  2826. { L'\u00D6', "&Ouml;", 6 },
  2827. { L'\u00D8', "&Oslash;", 8 },
  2828. { L'\u00D9', "&Ugrave;", 8 },
  2829. { L'\u00DA', "&Uacute;", 8 },
  2830. { L'\u00DB', "&Ucirc;", 7 },
  2831. { L'\u00DC', "&Uuml;", 6 },
  2832. { L'\u00DD', "&Yacute;", 8 },
  2833. { L'\u00DE', "&THORN;", 7 },
  2834. { L'\u00DF', "&szlig;", 7 },
  2835. { L'\u00E0', "&agrave;", 8 },
  2836. { L'\u00E1', "&aacute;", 8 },
  2837. { L'\u00E2', "&acirc;", 7 },
  2838. { L'\u00E3', "&atilde;", 8 },
  2839. { L'\u00E4', "&auml;", 6 },
  2840. { L'\u00E5', "&aring;", 7 },
  2841. { L'\u00E6', "&aelig;", 7 },
  2842. { L'\u00E7', "&ccedil;", 8 },
  2843. { L'\u00E8', "&egrave;", 8 },
  2844. { L'\u00E9', "&eacute;", 8 },
  2845. { L'\u00EA', "&ecirc;", 7 },
  2846. { L'\u00EB', "&euml;", 6 },
  2847. { L'\u00EC', "&igrave;", 8 },
  2848. { L'\u00ED', "&iacute;", 8 },
  2849. { L'\u00EE', "&icirc;", 7 },
  2850. { L'\u00EF', "&iuml;", 6 },
  2851. { L'\u00F0', "&eth;", 5 },
  2852. { L'\u00F1', "&ntilde;", 8 },
  2853. { L'\u00F2', "&ograve;", 8 },
  2854. { L'\u00F3', "&oacute;", 8 },
  2855. { L'\u00F4', "&ocirc;", 7 },
  2856. { L'\u00F5', "&otilde;", 8 },
  2857. { L'\u00F6', "&ouml;", 6 },
  2858. { L'\u00F8', "&oslash;", 8 },
  2859. { L'\u00F9', "&ugrave;", 8 },
  2860. { L'\u00FA', "&uacute;", 8 },
  2861. { L'\u00FB', "&ucirc;", 7 },
  2862. { L'\u00FC', "&uuml;", 6 },
  2863. { L'\u00FD', "&yacute;", 8 },
  2864. { L'\u00FE', "&thorn;", 7 },
  2865. { L'\u00FF', "&yuml;", 6 },
  2866. { 0, NULL, 0 } // sentinel for end of array
  2867. };
  2868. #pragma warning( pop )
  2869. bool V_BasicHtmlEntityEncode( char *pDest, const int nDestSize, char const *pIn, const int nInSize, bool bPreserveWhitespace /*= false*/ )
  2870. {
  2871. Assert( nDestSize == 0 || pDest != NULL );
  2872. int iOutput = 0;
  2873. for ( int iInput = 0; iInput < nInSize; ++iInput )
  2874. {
  2875. bool bReplacementDone = false;
  2876. // See if the current char matches any of the basic entities
  2877. for ( int i = 0; g_BasicHTMLEntities[ i ].uCharCode != 0; ++i )
  2878. {
  2879. if ( pIn[ iInput ] == g_BasicHTMLEntities[ i ].uCharCode )
  2880. {
  2881. bReplacementDone = true;
  2882. for ( int j = 0; j < g_BasicHTMLEntities[ i ].nEntityLength; ++j )
  2883. {
  2884. if ( iOutput >= nDestSize - 1 )
  2885. {
  2886. pDest[ nDestSize - 1 ] = 0;
  2887. return false;
  2888. }
  2889. pDest[ iOutput++ ] = g_BasicHTMLEntities[ i ].pchEntity[ j ];
  2890. }
  2891. }
  2892. }
  2893. if ( bPreserveWhitespace && !bReplacementDone )
  2894. {
  2895. // See if the current char matches any of the basic entities
  2896. for ( int i = 0; g_WhitespaceEntities[ i ].uCharCode != 0; ++i )
  2897. {
  2898. if ( pIn[ iInput ] == g_WhitespaceEntities[ i ].uCharCode )
  2899. {
  2900. bReplacementDone = true;
  2901. for ( int j = 0; j < g_WhitespaceEntities[ i ].nEntityLength; ++j )
  2902. {
  2903. if ( iOutput >= nDestSize - 1 )
  2904. {
  2905. pDest[ nDestSize - 1 ] = 0;
  2906. return false;
  2907. }
  2908. pDest[ iOutput++ ] = g_WhitespaceEntities[ i ].pchEntity[ j ];
  2909. }
  2910. }
  2911. }
  2912. }
  2913. if ( !bReplacementDone )
  2914. {
  2915. pDest[ iOutput++ ] = pIn[ iInput ];
  2916. }
  2917. }
  2918. // Null terminate the output
  2919. pDest[ iOutput ] = 0;
  2920. return true;
  2921. }
  2922. bool V_HtmlEntityDecodeToUTF8( char *pDest, const int nDestSize, char const *pIn, const int nInSize )
  2923. {
  2924. Assert( nDestSize == 0 || pDest != NULL );
  2925. int iOutput = 0;
  2926. for ( int iInput = 0; iInput < nInSize && iOutput < nDestSize; ++iInput )
  2927. {
  2928. bool bReplacementDone = false;
  2929. if ( pIn[ iInput ] == '&' )
  2930. {
  2931. bReplacementDone = true;
  2932. uchar32 wrgchReplacement[ 2 ] = { 0, 0 };
  2933. char rgchReplacement[ 8 ];
  2934. rgchReplacement[ 0 ] = 0;
  2935. const char *pchEnd = Q_strstr( pIn + iInput + 1, ";" );
  2936. if ( pchEnd )
  2937. {
  2938. if ( iInput + 1 < nInSize && pIn[ iInput + 1 ] == '#' )
  2939. {
  2940. // Numeric
  2941. int iBase = 10;
  2942. int iOffset = 2;
  2943. if ( iInput + 3 < nInSize && pIn[ iInput + 2 ] == 'x' )
  2944. {
  2945. iBase = 16;
  2946. iOffset = 3;
  2947. }
  2948. wrgchReplacement[ 0 ] = (uchar32)V_strtoi64( pIn + iInput + iOffset, NULL, iBase );
  2949. if ( !Q_UTF32ToUTF8( wrgchReplacement, rgchReplacement, sizeof( rgchReplacement ) ) )
  2950. {
  2951. rgchReplacement[ 0 ] = 0;
  2952. }
  2953. }
  2954. else
  2955. {
  2956. // Lookup in map
  2957. const Tier1FullHTMLEntity_t *pFullEntities = g_Tier1_FullHTMLEntities;
  2958. for ( int i = 0; pFullEntities[ i ].uCharCode != 0; ++i )
  2959. {
  2960. if ( nInSize - iInput - 1 >= pFullEntities[ i ].nEntityLength )
  2961. {
  2962. if ( Q_memcmp( pIn + iInput, pFullEntities[ i ].pchEntity, pFullEntities[ i ].nEntityLength ) == 0 )
  2963. {
  2964. wrgchReplacement[ 0 ] = pFullEntities[ i ].uCharCode;
  2965. if ( !Q_UTF32ToUTF8( wrgchReplacement, rgchReplacement, sizeof( rgchReplacement ) ) )
  2966. {
  2967. rgchReplacement[ 0 ] = 0;
  2968. }
  2969. break;
  2970. }
  2971. }
  2972. }
  2973. }
  2974. // make sure we found a replacement. If not, skip
  2975. int cchReplacement = V_strlen( rgchReplacement );
  2976. if ( cchReplacement > 0 )
  2977. {
  2978. if ( (int)cchReplacement + iOutput < nDestSize )
  2979. {
  2980. for ( int i = 0; rgchReplacement[ i ] != 0; ++i )
  2981. {
  2982. pDest[ iOutput++ ] = rgchReplacement[ i ];
  2983. }
  2984. }
  2985. // Skip extra space that we passed
  2986. iInput += pchEnd - ( pIn + iInput );
  2987. }
  2988. else
  2989. {
  2990. bReplacementDone = false;
  2991. }
  2992. }
  2993. }
  2994. if ( !bReplacementDone )
  2995. {
  2996. pDest[ iOutput++ ] = pIn[ iInput ];
  2997. }
  2998. }
  2999. // Null terminate the output
  3000. if ( iOutput < nDestSize )
  3001. {
  3002. pDest[ iOutput ] = 0;
  3003. }
  3004. else
  3005. {
  3006. pDest[ nDestSize - 1 ] = 0;
  3007. }
  3008. return true;
  3009. }
  3010. static const char *g_pszSimpleBBCodeReplacements[] = {
  3011. "[b]", "<b>",
  3012. "[/b]", "</b>",
  3013. "[i]", "<i>",
  3014. "[/i]", "</i>",
  3015. "[u]", "<u>",
  3016. "[/u]", "</u>",
  3017. "[s]", "<s>",
  3018. "[/s]", "</s>",
  3019. "[code]", "<pre>",
  3020. "[/code]", "</pre>",
  3021. "[h1]", "<h1>",
  3022. "[/h1]", "</h1>",
  3023. "[list]", "<ul>",
  3024. "[/list]", "</ul>",
  3025. "[*]", "<li>",
  3026. "[/url]", "</a>",
  3027. "[img]", "<img src=\"",
  3028. "[/img]", "\"></img>",
  3029. };
  3030. // Converts BBCode tags to HTML tags
  3031. bool V_BBCodeToHTML( OUT_Z_CAP( nDestSize ) char *pDest, const int nDestSize, char const *pIn, const int nInSize )
  3032. {
  3033. Assert( nDestSize == 0 || pDest != NULL );
  3034. int iOutput = 0;
  3035. for ( int iInput = 0; iInput < nInSize && iOutput < nDestSize && pIn[ iInput ]; ++iInput )
  3036. {
  3037. if ( pIn[ iInput ] == '[' )
  3038. {
  3039. // check simple replacements
  3040. bool bFoundReplacement = false;
  3041. for ( int r = 0; r < ARRAYSIZE( g_pszSimpleBBCodeReplacements ); r += 2 )
  3042. {
  3043. int nBBCodeLength = V_strlen( g_pszSimpleBBCodeReplacements[ r ] );
  3044. if ( !V_strnicmp( &pIn[ iInput ], g_pszSimpleBBCodeReplacements[ r ], nBBCodeLength ) )
  3045. {
  3046. int nHTMLReplacementLength = V_strlen( g_pszSimpleBBCodeReplacements[ r + 1 ] );
  3047. for ( int c = 0; c < nHTMLReplacementLength && iOutput < nDestSize; c++ )
  3048. {
  3049. pDest[ iOutput ] = g_pszSimpleBBCodeReplacements[ r + 1 ][ c ];
  3050. iOutput++;
  3051. }
  3052. iInput += nBBCodeLength - 1;
  3053. bFoundReplacement = true;
  3054. break;
  3055. }
  3056. }
  3057. // check URL replacement
  3058. if ( !bFoundReplacement && !V_strnicmp( &pIn[ iInput ], "[url=", 5 ) && nDestSize - iOutput > 9 )
  3059. {
  3060. iInput += 5;
  3061. pDest[ iOutput++ ] = '<';
  3062. pDest[ iOutput++ ] = 'a';
  3063. pDest[ iOutput++ ] = ' ';
  3064. pDest[ iOutput++ ] = 'h';
  3065. pDest[ iOutput++ ] = 'r';
  3066. pDest[ iOutput++ ] = 'e';
  3067. pDest[ iOutput++ ] = 'f';
  3068. pDest[ iOutput++ ] = '=';
  3069. pDest[ iOutput++ ] = '\"';
  3070. // copy all characters up to the closing square bracket
  3071. while ( pIn[ iInput ] != ']' && iInput < nInSize && iOutput < nDestSize )
  3072. {
  3073. pDest[ iOutput++ ] = pIn[ iInput++ ];
  3074. }
  3075. if ( pIn[ iInput ] == ']' && nDestSize - iOutput > 2 )
  3076. {
  3077. pDest[ iOutput++ ] = '\"';
  3078. pDest[ iOutput++ ] = '>';
  3079. }
  3080. bFoundReplacement = true;
  3081. }
  3082. // otherwise, skip over everything up to the closing square bracket
  3083. if ( !bFoundReplacement )
  3084. {
  3085. while ( pIn[ iInput ] != ']' && iInput < nInSize )
  3086. {
  3087. iInput++;
  3088. }
  3089. }
  3090. }
  3091. else if ( pIn[ iInput ] == '\r' && pIn[ iInput + 1 ] == '\n' )
  3092. {
  3093. // convert carriage return and newline to a <br>
  3094. if ( nDestSize - iOutput > 4 )
  3095. {
  3096. pDest[ iOutput++ ] = '<';
  3097. pDest[ iOutput++ ] = 'b';
  3098. pDest[ iOutput++ ] = 'r';
  3099. pDest[ iOutput++ ] = '>';
  3100. }
  3101. iInput++;
  3102. }
  3103. else if ( pIn[ iInput ] == '\n' )
  3104. {
  3105. // convert newline to a <br>
  3106. if ( nDestSize - iOutput > 4 )
  3107. {
  3108. pDest[ iOutput++ ] = '<';
  3109. pDest[ iOutput++ ] = 'b';
  3110. pDest[ iOutput++ ] = 'r';
  3111. pDest[ iOutput++ ] = '>';
  3112. }
  3113. }
  3114. else
  3115. {
  3116. // copy character to destination
  3117. pDest[ iOutput++ ] = pIn[ iInput ];
  3118. }
  3119. }
  3120. // always terminate string
  3121. if ( iOutput >= nDestSize )
  3122. {
  3123. iOutput = nDestSize - 1;
  3124. }
  3125. pDest[ iOutput ] = 0;
  3126. return true;
  3127. }
  3128. //-----------------------------------------------------------------------------
  3129. // Purpose: returns true if a wide character is a "mean" space; that is,
  3130. // if it is technically a space or punctuation, but causes disruptive
  3131. // behavior when used in names, web pages, chat windows, etc.
  3132. //
  3133. // characters in this set are removed from the beginning and/or end of strings
  3134. // by Q_AggressiveStripPrecedingAndTrailingWhitespaceW()
  3135. //-----------------------------------------------------------------------------
  3136. bool V_IsMeanUnderscoreW( wchar_t wch )
  3137. {
  3138. bool bIsMean = false;
  3139. switch ( wch )
  3140. {
  3141. case L'\x005f': // low line (normal underscore)
  3142. case L'\xff3f': // fullwidth low line
  3143. case L'\x0332': // combining low line
  3144. bIsMean = true;
  3145. break;
  3146. default:
  3147. break;
  3148. }
  3149. return bIsMean;
  3150. }
  3151. //-----------------------------------------------------------------------------
  3152. // Purpose: returns true if a wide character is a "mean" space; that is,
  3153. // if it is technically a space or punctuation, but causes disruptive
  3154. // behavior when used in names, web pages, chat windows, etc.
  3155. //
  3156. // characters in this set are removed from the beginning and/or end of strings
  3157. // by Q_AggressiveStripPrecedingAndTrailingWhitespaceW()
  3158. //-----------------------------------------------------------------------------
  3159. bool V_IsMeanSpaceW( wchar_t wch )
  3160. {
  3161. bool bIsMean = false;
  3162. switch ( wch )
  3163. {
  3164. case L'\x0080': // PADDING CHARACTER
  3165. case L'\x0081': // HIGH OCTET PRESET
  3166. case L'\x0082': // BREAK PERMITTED HERE
  3167. case L'\x0083': // NO BREAK PERMITTED HERE
  3168. case L'\x0084': // INDEX
  3169. case L'\x0085': // NEXT LINE
  3170. case L'\x0086': // START OF SELECTED AREA
  3171. case L'\x0087': // END OF SELECTED AREA
  3172. case L'\x0088': // CHARACTER TABULATION SET
  3173. case L'\x0089': // CHARACTER TABULATION WITH JUSTIFICATION
  3174. case L'\x008A': // LINE TABULATION SET
  3175. case L'\x008B': // PARTIAL LINE FORWARD
  3176. case L'\x008C': // PARTIAL LINE BACKWARD
  3177. case L'\x008D': // REVERSE LINE FEED
  3178. case L'\x008E': // SINGLE SHIFT 2
  3179. case L'\x008F': // SINGLE SHIFT 3
  3180. case L'\x0090': // DEVICE CONTROL STRING
  3181. case L'\x0091': // PRIVATE USE
  3182. case L'\x0092': // PRIVATE USE
  3183. case L'\x0093': // SET TRANSMIT STATE
  3184. case L'\x0094': // CANCEL CHARACTER
  3185. case L'\x0095': // MESSAGE WAITING
  3186. case L'\x0096': // START OF PROTECTED AREA
  3187. case L'\x0097': // END OF PROTECED AREA
  3188. case L'\x0098': // START OF STRING
  3189. case L'\x0099': // SINGLE GRAPHIC CHARACTER INTRODUCER
  3190. case L'\x009A': // SINGLE CHARACTER INTRODUCER
  3191. case L'\x009B': // CONTROL SEQUENCE INTRODUCER
  3192. case L'\x009C': // STRING TERMINATOR
  3193. case L'\x009D': // OPERATING SYSTEM COMMAND
  3194. case L'\x009E': // PRIVACY MESSAGE
  3195. case L'\x009F': // APPLICATION PROGRAM COMMAND
  3196. case L'\x00A0': // NO-BREAK SPACE
  3197. case L'\x034F': // COMBINING GRAPHEME JOINER
  3198. case L'\x2000': // EN QUAD
  3199. case L'\x2001': // EM QUAD
  3200. case L'\x2002': // EN SPACE
  3201. case L'\x2003': // EM SPACE
  3202. case L'\x2004': // THICK SPACE
  3203. case L'\x2005': // MID SPACE
  3204. case L'\x2006': // SIX SPACE
  3205. case L'\x2007': // figure space
  3206. case L'\x2008': // PUNCTUATION SPACE
  3207. case L'\x2009': // THIN SPACE
  3208. case L'\x200A': // HAIR SPACE
  3209. case L'\x200B': // ZERO-WIDTH SPACE
  3210. case L'\x200C': // ZERO-WIDTH NON-JOINER
  3211. case L'\x200D': // ZERO WIDTH JOINER
  3212. case L'\x2028': // LINE SEPARATOR
  3213. case L'\x2029': // PARAGRAPH SEPARATOR
  3214. case L'\x202F': // NARROW NO-BREAK SPACE
  3215. case L'\x2060': // word joiner
  3216. case L'\xFEFF': // ZERO-WIDTH NO BREAK SPACE
  3217. case L'\xFFFC': // OBJECT REPLACEMENT CHARACTER
  3218. bIsMean = true;
  3219. break;
  3220. }
  3221. return bIsMean;
  3222. }
  3223. //-----------------------------------------------------------------------------
  3224. // Purpose: tell us if a Unicode character is deprecated
  3225. //
  3226. // See Unicode Technical Report #20: http://www.unicode.org/reports/tr20/
  3227. //
  3228. // Some characters are difficult or unreliably rendered. These characters eventually
  3229. // fell out of the Unicode standard, but are abusable by users. For example,
  3230. // setting "RIGHT-TO-LEFT OVERRIDE" without popping or undoing the action causes
  3231. // the layout instruction to bleed into following characters in HTML renderings,
  3232. // or upset layout calculations in vgui panels.
  3233. //
  3234. // Many games don't cope with these characters well, and end up providing opportunities
  3235. // for griefing others. For example, a user might join a game with a malformed player
  3236. // name and it turns out that player name can't be selected or typed into the admin
  3237. // console or UI to mute, kick, or ban the disruptive player.
  3238. //
  3239. // Ideally, we'd perfectly support these end-to-end but we never realistically will.
  3240. // The benefit of doing so far outweighs the cost, anyway.
  3241. //-----------------------------------------------------------------------------
  3242. bool V_IsDeprecatedW( wchar_t wch )
  3243. {
  3244. bool bIsDeprecated = false;
  3245. switch ( wch )
  3246. {
  3247. case L'\x202A': // LEFT-TO-RIGHT EMBEDDING
  3248. case L'\x202B': // RIGHT-TO-LEFT EMBEDDING
  3249. case L'\x202C': // POP DIRECTIONAL FORMATTING
  3250. case L'\x202D': // LEFT-TO-RIGHT OVERRIDE
  3251. case L'\x202E': // RIGHT-TO-LEFT OVERRIDE
  3252. case L'\x206A': // INHIBIT SYMMETRIC SWAPPING
  3253. case L'\x206B': // ACTIVATE SYMMETRIC SWAPPING
  3254. case L'\x206C': // INHIBIT ARABIC FORM SHAPING
  3255. case L'\x206D': // ACTIVATE ARABIC FORM SHAPING
  3256. case L'\x206E': // NATIONAL DIGIT SHAPES
  3257. case L'\x206F': // NOMINAL DIGIT SHAPES
  3258. bIsDeprecated = true;
  3259. }
  3260. return bIsDeprecated;
  3261. }
  3262. //-----------------------------------------------------------------------------
  3263. // returns true if the character is allowed in a DNS doman name, false otherwise
  3264. //-----------------------------------------------------------------------------
  3265. bool V_IsValidDomainNameCharacter( const char *pch, int *pAdvanceBytes )
  3266. {
  3267. if ( pAdvanceBytes )
  3268. *pAdvanceBytes = 0;
  3269. // We allow unicode in Domain Names without the an encoding unless it corresponds to
  3270. // a whitespace or control sequence or something we think is an underscore looking thing.
  3271. // If this character is the start of a UTF-8 sequence, try decoding it.
  3272. unsigned char ch = (unsigned char)*pch;
  3273. if ( ( ch & 0xC0 ) == 0xC0 )
  3274. {
  3275. uchar32 rgch32Buf;
  3276. bool bError = false;
  3277. int iAdvance = Q_UTF8ToUChar32( pch, rgch32Buf, bError );
  3278. if ( bError || iAdvance == 0 )
  3279. {
  3280. // Invalid UTF8 sequence, lets consider that invalid
  3281. return false;
  3282. }
  3283. if ( pAdvanceBytes )
  3284. *pAdvanceBytes = iAdvance;
  3285. if ( iAdvance )
  3286. {
  3287. // Ick. Want uchar32 versions of unicode character classification functions.
  3288. // Really would like Q_IsWhitespace32 and Q_IsNonPrintable32, but this is OK.
  3289. if ( rgch32Buf < 0x10000 && ( V_IsMeanSpaceW( (wchar_t)rgch32Buf ) || V_IsDeprecatedW( (wchar_t)rgch32Buf ) || V_IsMeanUnderscoreW( (wchar_t)rgch32Buf ) ) )
  3290. {
  3291. return false;
  3292. }
  3293. return true;
  3294. }
  3295. else
  3296. {
  3297. // Unreachable but would be invalid utf8
  3298. return false;
  3299. }
  3300. }
  3301. else
  3302. {
  3303. // Was not unicode
  3304. if ( pAdvanceBytes )
  3305. *pAdvanceBytes = 1;
  3306. // The only allowable non-unicode chars are a-z A-Z 0-9 and -
  3307. if ( ( ch >= 'a' && ch <= 'z' ) || ( ch >= 'A' && ch <= 'Z' ) || ( ch >= '0' && ch <= '9' ) || ch == '-' || ch == '.' )
  3308. return true;
  3309. return false;
  3310. }
  3311. }
  3312. //-----------------------------------------------------------------------------
  3313. // returns true if the character is allowed in a URL, false otherwise
  3314. //-----------------------------------------------------------------------------
  3315. bool V_IsValidURLCharacter( const char *pch, int *pAdvanceBytes )
  3316. {
  3317. if ( pAdvanceBytes )
  3318. *pAdvanceBytes = 0;
  3319. // We allow unicode in URLs unless it corresponds to a whitespace or control sequence.
  3320. // If this character is the start of a UTF-8 sequence, try decoding it.
  3321. unsigned char ch = (unsigned char)*pch;
  3322. if ( ( ch & 0xC0 ) == 0xC0 )
  3323. {
  3324. uchar32 rgch32Buf;
  3325. bool bError = false;
  3326. int iAdvance = Q_UTF8ToUChar32( pch, rgch32Buf, bError );
  3327. if ( bError || iAdvance == 0 )
  3328. {
  3329. // Invalid UTF8 sequence, lets consider that invalid
  3330. return false;
  3331. }
  3332. if ( pAdvanceBytes )
  3333. *pAdvanceBytes = iAdvance;
  3334. if ( iAdvance )
  3335. {
  3336. // Ick. Want uchar32 versions of unicode character classification functions.
  3337. // Really would like Q_IsWhitespace32 and Q_IsNonPrintable32, but this is OK.
  3338. if ( rgch32Buf < 0x10000 && ( V_IsMeanSpaceW( (wchar_t)rgch32Buf ) || V_IsDeprecatedW( (wchar_t)rgch32Buf ) ) )
  3339. {
  3340. return false;
  3341. }
  3342. return true;
  3343. }
  3344. else
  3345. {
  3346. // Unreachable but would be invalid utf8
  3347. return false;
  3348. }
  3349. }
  3350. else
  3351. {
  3352. // Was not unicode
  3353. if ( pAdvanceBytes )
  3354. *pAdvanceBytes = 1;
  3355. // Spaces, control characters, quotes, and angle brackets are not legal URL characters.
  3356. if ( ch <= 32 || ch == 127 || ch == '"' || ch == '<' || ch == '>' )
  3357. return false;
  3358. return true;
  3359. }
  3360. }
  3361. //-----------------------------------------------------------------------------
  3362. // Purpose: helper function to get a domain from a url
  3363. // Checks both standard url and steam://openurl/<url>
  3364. //-----------------------------------------------------------------------------
  3365. bool V_ExtractDomainFromURL( const char *pchURL, char *pchDomain, int cchDomain )
  3366. {
  3367. pchDomain[ 0 ] = 0;
  3368. static const char *k_pchSteamOpenUrl = "steam://openurl/";
  3369. static const char *k_pchSteamOpenUrlExt = "steam://openurl_external/";
  3370. const char *pchOpenUrlSuffix = StringAfterPrefix( pchURL, k_pchSteamOpenUrl );
  3371. if ( pchOpenUrlSuffix == NULL )
  3372. pchOpenUrlSuffix = StringAfterPrefix( pchURL, k_pchSteamOpenUrlExt );
  3373. if ( pchOpenUrlSuffix )
  3374. pchURL = pchOpenUrlSuffix;
  3375. if ( !pchURL || pchURL[ 0 ] == '\0' )
  3376. return false;
  3377. const char *pchDoubleSlash = strstr( pchURL, "//" );
  3378. // Put the domain and everything after into pchDomain.
  3379. // We'll find where to terminate it later.
  3380. if ( pchDoubleSlash )
  3381. {
  3382. // Skip the slashes
  3383. pchDoubleSlash += 2;
  3384. // If that's all there was, then there's no domain here. Bail.
  3385. if ( *pchDoubleSlash == '\0' )
  3386. {
  3387. return false;
  3388. }
  3389. // Skip any extra slashes
  3390. // ex: http:///steamcommunity.com/
  3391. while ( *pchDoubleSlash == '/' )
  3392. {
  3393. pchDoubleSlash++;
  3394. }
  3395. Q_strncpy( pchDomain, pchDoubleSlash, cchDomain );
  3396. }
  3397. else
  3398. {
  3399. // No double slash, so pchURL has no protocol.
  3400. Q_strncpy( pchDomain, pchURL, cchDomain );
  3401. }
  3402. // First character has to be valid
  3403. if ( *pchDomain == '?' || *pchDomain == '\0' )
  3404. {
  3405. return false;
  3406. }
  3407. // terminate the domain after the first non domain char
  3408. int iAdvance = 0;
  3409. int iStrLen = 0;
  3410. char cLast = 0;
  3411. while ( pchDomain[ iStrLen ] )
  3412. {
  3413. if ( !V_IsValidDomainNameCharacter( pchDomain + iStrLen, &iAdvance ) || ( pchDomain[ iStrLen ] == '.' && cLast == '.' ) )
  3414. {
  3415. pchDomain[ iStrLen ] = 0;
  3416. break;
  3417. }
  3418. cLast = pchDomain[ iStrLen ];
  3419. iStrLen += iAdvance;
  3420. }
  3421. return ( pchDomain[ 0 ] != 0 );
  3422. }
  3423. //-----------------------------------------------------------------------------
  3424. // Purpose: helper function to get a domain from a url
  3425. //-----------------------------------------------------------------------------
  3426. bool V_URLContainsDomain( const char *pchURL, const char *pchDomain )
  3427. {
  3428. char rgchExtractedDomain[ 2048 ];
  3429. if ( V_ExtractDomainFromURL( pchURL, rgchExtractedDomain, sizeof( rgchExtractedDomain ) ) )
  3430. {
  3431. // see if the last part of the domain matches what we extracted
  3432. int cchExtractedDomain = V_strlen( rgchExtractedDomain );
  3433. if ( pchDomain[ 0 ] == '.' )
  3434. {
  3435. ++pchDomain; // If the domain has a leading '.', skip it. The test below assumes there is none.
  3436. }
  3437. int cchDomain = V_strlen( pchDomain );
  3438. if ( cchDomain > cchExtractedDomain )
  3439. {
  3440. return false;
  3441. }
  3442. else if ( cchExtractedDomain >= cchDomain )
  3443. {
  3444. // If the actual domain is longer than what we're searching for, the character previous
  3445. // to the domain we're searching for must be a period
  3446. if ( cchExtractedDomain > cchDomain && rgchExtractedDomain[ cchExtractedDomain - cchDomain - 1 ] != '.' )
  3447. return false;
  3448. if ( 0 == V_stricmp( rgchExtractedDomain + cchExtractedDomain - cchDomain, pchDomain ) )
  3449. return true;
  3450. }
  3451. }
  3452. return false;
  3453. }
  3454. //-----------------------------------------------------------------------------
  3455. // Purpose: Strips all HTML tags not specified in rgszPreserveTags
  3456. // Does some additional formatting, like turning <li> into * when not preserving that tag,
  3457. // and auto-closing unclosed tags if they aren't specified in rgszNoCloseTags
  3458. //-----------------------------------------------------------------------------
  3459. void V_StripAndPreserveHTMLCore( CUtlBuffer *pbuffer, const char *pchHTML, const char **rgszPreserveTags, uint cPreserveTags, const char **rgszNoCloseTags, uint cNoCloseTags, uint cMaxResultSize )
  3460. {
  3461. uint cHTMLCur = 0;
  3462. bool bStripNewLines = true;
  3463. if ( cPreserveTags > 0 )
  3464. {
  3465. for ( uint i = 0; i < cPreserveTags; ++i )
  3466. {
  3467. if ( !Q_stricmp( rgszPreserveTags[ i ], "\n" ) )
  3468. bStripNewLines = false;
  3469. }
  3470. }
  3471. //state-
  3472. bool bInStrippedTag = false;
  3473. bool bInStrippedContentTag = false;
  3474. bool bInPreservedTag = false;
  3475. bool bInListItemTag = false;
  3476. bool bLastCharWasWhitespace = true; //set to true to strip leading whitespace
  3477. bool bInComment = false;
  3478. bool bInDoubleQuote = false;
  3479. bool bInSingleQuote = false;
  3480. int nPreTagDepth = 0;
  3481. CUtlVector< const char* > vecTagStack;
  3482. for ( int iContents = 0; pchHTML[ iContents ] != '\0' && cHTMLCur < cMaxResultSize; iContents++ )
  3483. {
  3484. char c = pchHTML[ iContents ];
  3485. // If we are entering a comment, flag as such and skip past the begin comment tag
  3486. const char *pchCur = &pchHTML[ iContents ];
  3487. if ( !Q_strnicmp( pchCur, "<!--", 4 ) )
  3488. {
  3489. bInComment = true;
  3490. iContents += 3;
  3491. continue;
  3492. }
  3493. // If we are in a comment, check if we are exiting
  3494. if ( bInComment )
  3495. {
  3496. if ( !Q_strnicmp( pchCur, "-->", 3 ) )
  3497. {
  3498. bInComment = false;
  3499. iContents += 2;
  3500. continue;
  3501. }
  3502. else
  3503. {
  3504. continue;
  3505. }
  3506. }
  3507. if ( bInStrippedTag || bInPreservedTag )
  3508. {
  3509. // we're inside a tag, keep stripping/preserving until we get to a >
  3510. if ( bInPreservedTag )
  3511. pbuffer->PutChar( c );
  3512. // While inside a tag, ignore ending > properties if they are inside a property value in "" or ''
  3513. if ( c == '"' )
  3514. {
  3515. if ( bInDoubleQuote )
  3516. bInDoubleQuote = false;
  3517. else
  3518. bInDoubleQuote = true;
  3519. }
  3520. if ( c == '\'' )
  3521. {
  3522. if ( bInSingleQuote )
  3523. bInSingleQuote = false;
  3524. else
  3525. bInSingleQuote = true;
  3526. }
  3527. if ( !bInDoubleQuote && !bInSingleQuote && c == '>' )
  3528. {
  3529. if ( bInPreservedTag )
  3530. bLastCharWasWhitespace = false;
  3531. bInPreservedTag = false;
  3532. bInStrippedTag = false;
  3533. }
  3534. }
  3535. else if ( bInStrippedContentTag )
  3536. {
  3537. if ( c == '<' && !Q_strnicmp( pchCur, "</script>", 9 ) )
  3538. {
  3539. bInStrippedContentTag = false;
  3540. iContents += 8;
  3541. continue;
  3542. }
  3543. else
  3544. {
  3545. continue;
  3546. }
  3547. }
  3548. else if ( c & 0x80 && !bInStrippedContentTag )
  3549. {
  3550. // start/continuation of a multibyte sequence, copy to output.
  3551. int nMultibyteRemaining = 0;
  3552. if ( ( c & 0xF8 ) == 0xF0 ) // first 5 bits are 11110
  3553. nMultibyteRemaining = 3;
  3554. else if ( ( c & 0xF0 ) == 0xE0 ) // first 4 bits are 1110
  3555. nMultibyteRemaining = 2;
  3556. else if ( ( c & 0xE0 ) == 0xC0 ) // first 3 bits are 110
  3557. nMultibyteRemaining = 1;
  3558. // cHTMLCur is in characters, so just +1
  3559. cHTMLCur++;
  3560. pbuffer->Put( pchCur, 1 + nMultibyteRemaining );
  3561. iContents += nMultibyteRemaining;
  3562. // Need to determine if we just added whitespace or not
  3563. wchar_t rgwch[ 3 ] = { 0 };
  3564. Q_UTF8CharsToWString( pchCur, 1, rgwch, sizeof( rgwch ) );
  3565. if ( !V_iswspace( rgwch[ 0 ] ) )
  3566. bLastCharWasWhitespace = false;
  3567. else
  3568. bLastCharWasWhitespace = true;
  3569. }
  3570. else
  3571. {
  3572. //not in a multibyte sequence- do our parsing/stripping
  3573. if ( c == '<' )
  3574. {
  3575. if ( !rgszPreserveTags || cPreserveTags == 0 )
  3576. {
  3577. //not preserving any tags, just strip it
  3578. bInStrippedTag = true;
  3579. }
  3580. else
  3581. {
  3582. //look ahead, is this our kind of tag?
  3583. bool bPreserve = false;
  3584. bool bEndTag = false;
  3585. const char *szTagStart = &pchHTML[ iContents + 1 ];
  3586. // if it's a close tag, skip the /
  3587. if ( *szTagStart == '/' )
  3588. {
  3589. bEndTag = true;
  3590. szTagStart++;
  3591. }
  3592. if ( Q_strnicmp( "script", szTagStart, 6 ) == 0 )
  3593. {
  3594. bInStrippedTag = true;
  3595. bInStrippedContentTag = true;
  3596. }
  3597. else
  3598. {
  3599. //see if this tag is one we want to preserve
  3600. for ( uint iTag = 0; iTag < cPreserveTags; iTag++ )
  3601. {
  3602. const char *szTag = rgszPreserveTags[ iTag ];
  3603. int cchTag = Q_strlen( szTag );
  3604. //make sure characters match, and are followed by some non-alnum char
  3605. // so "i" can match <i> or <i class=...>, but not <img>
  3606. if ( Q_strnicmp( szTag, szTagStart, cchTag ) == 0 && !V_isalnum( szTagStart[ cchTag ] ) )
  3607. {
  3608. bPreserve = true;
  3609. if ( bEndTag )
  3610. {
  3611. // ending a paragraph tag is optional. If we were expecting to find one, and didn't, skip
  3612. if ( Q_stricmp( szTag, "p" ) != 0 )
  3613. {
  3614. while ( vecTagStack.Count() > 0 && Q_stricmp( vecTagStack[ vecTagStack.Count() - 1 ], "p" ) == 0 )
  3615. {
  3616. vecTagStack.Remove( vecTagStack.Count() - 1 );
  3617. }
  3618. }
  3619. if ( vecTagStack.Count() > 0 && vecTagStack[ vecTagStack.Count() - 1 ] == szTag )
  3620. {
  3621. vecTagStack.Remove( vecTagStack.Count() - 1 );
  3622. if ( Q_stricmp( szTag, "pre" ) == 0 )
  3623. {
  3624. nPreTagDepth--;
  3625. if ( nPreTagDepth < 0 )
  3626. {
  3627. nPreTagDepth = 0;
  3628. }
  3629. }
  3630. }
  3631. else
  3632. {
  3633. // don't preserve this unbalanced tag. All open tags will be closed at the end of the blurb
  3634. bPreserve = false;
  3635. }
  3636. }
  3637. else
  3638. {
  3639. bool bNoCloseTag = false;
  3640. for ( uint iNoClose = 0; iNoClose < cNoCloseTags; iNoClose++ )
  3641. {
  3642. if ( Q_stricmp( szTag, rgszNoCloseTags[ iNoClose ] ) == 0 )
  3643. {
  3644. bNoCloseTag = true;
  3645. break;
  3646. }
  3647. }
  3648. if ( !bNoCloseTag )
  3649. {
  3650. vecTagStack.AddToTail( szTag );
  3651. if ( Q_stricmp( szTag, "pre" ) == 0 )
  3652. {
  3653. nPreTagDepth++;
  3654. }
  3655. }
  3656. }
  3657. break;
  3658. }
  3659. }
  3660. if ( !bPreserve )
  3661. {
  3662. bInStrippedTag = true;
  3663. }
  3664. else
  3665. {
  3666. bInPreservedTag = true;
  3667. pbuffer->PutChar( c );
  3668. }
  3669. }
  3670. }
  3671. if ( bInStrippedTag )
  3672. {
  3673. const char *szTagStart = &pchHTML[ iContents ];
  3674. if ( Q_strnicmp( szTagStart, "<li>", Q_strlen( "<li>" ) ) == 0 )
  3675. {
  3676. if ( bInListItemTag )
  3677. {
  3678. pbuffer->PutChar( ';' );
  3679. cHTMLCur++;
  3680. bInListItemTag = false;
  3681. }
  3682. if ( !bLastCharWasWhitespace )
  3683. {
  3684. pbuffer->PutChar( ' ' );
  3685. cHTMLCur++;
  3686. }
  3687. pbuffer->PutChar( '*' );
  3688. pbuffer->PutChar( ' ' );
  3689. cHTMLCur += 2;
  3690. bInListItemTag = true;
  3691. }
  3692. else if ( !bLastCharWasWhitespace )
  3693. {
  3694. if ( bInListItemTag )
  3695. {
  3696. char cLastChar = ' ';
  3697. if ( pbuffer->TellPut() > 0 )
  3698. {
  3699. cLastChar = ( ( (char*)pbuffer->Base() ) + pbuffer->TellPut() - 1 )[ 0 ];
  3700. }
  3701. if ( cLastChar != '.' && cLastChar != '?' && cLastChar != '!' )
  3702. {
  3703. pbuffer->PutChar( ';' );
  3704. cHTMLCur++;
  3705. }
  3706. bInListItemTag = false;
  3707. }
  3708. //we're decided to remove a tag, simulate a space in the original text
  3709. pbuffer->PutChar( ' ' );
  3710. cHTMLCur++;
  3711. }
  3712. bLastCharWasWhitespace = true;
  3713. }
  3714. }
  3715. else
  3716. {
  3717. //just a normal character, nothin' special.
  3718. if ( nPreTagDepth == 0 && V_isspace( c ) && ( bStripNewLines || c != '\n' ) )
  3719. {
  3720. if ( !bLastCharWasWhitespace )
  3721. {
  3722. //replace any block of whitespace with a single space
  3723. cHTMLCur++;
  3724. pbuffer->PutChar( ' ' );
  3725. bLastCharWasWhitespace = true;
  3726. }
  3727. // don't put anything for whitespace if the previous character was whitespace
  3728. // (effectively trimming all blocks of whitespace down to a single ' ')
  3729. }
  3730. else
  3731. {
  3732. cHTMLCur++;
  3733. pbuffer->PutChar( c );
  3734. bLastCharWasWhitespace = false;
  3735. }
  3736. }
  3737. }
  3738. }
  3739. if ( cHTMLCur >= cMaxResultSize )
  3740. {
  3741. // we terminated because the blurb was full. Add a '...' to the end
  3742. pbuffer->Put( "...", 3 );
  3743. }
  3744. //close any preserved tags that were open at the end.
  3745. FOR_EACH_VEC_BACK( vecTagStack, iTagStack )
  3746. {
  3747. pbuffer->PutChar( '<' );
  3748. pbuffer->PutChar( '/' );
  3749. pbuffer->Put( vecTagStack[ iTagStack ], Q_strlen( vecTagStack[ iTagStack ] ) );
  3750. pbuffer->PutChar( '>' );
  3751. }
  3752. // Null terminate
  3753. pbuffer->PutChar( '\0' );
  3754. }
  3755. //-----------------------------------------------------------------------------
  3756. // Purpose: Strips all HTML tags not specified in rgszPreserveTags
  3757. // Does some additional formatting, like turning <li> into * when not preserving that tag
  3758. //-----------------------------------------------------------------------------
  3759. void V_StripAndPreserveHTML( CUtlBuffer *pbuffer, const char *pchHTML, const char **rgszPreserveTags, uint cPreserveTags, uint cMaxResultSize )
  3760. {
  3761. const char *rgszNoCloseTags[] = { "br", "img" };
  3762. V_StripAndPreserveHTMLCore( pbuffer, pchHTML, rgszPreserveTags, cPreserveTags, rgszNoCloseTags, V_ARRAYSIZE( rgszNoCloseTags ), cMaxResultSize );
  3763. }