csrd_name_filter.sp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /**
  2. * [CSRD] Name Filter
  3. *
  4. * An in-house plugin that tidies up names.
  5. * Made in response to a few script kiddies that are (ab)using newlines and other non-printing
  6. * characters in their name.
  7. *
  8. * If their existing name has no printable characters, their name will be changed to
  9. * "unprintable" plus their userid and their name will be locked.
  10. */
  11. #pragma semicolon 1
  12. #include <sourcemod>
  13. #include <sdktools_functions>
  14. #pragma newdecls required
  15. #define PLUGIN_VERSION "1.0.5"
  16. public Plugin myinfo = {
  17. name = "[CSRD] Name Filter",
  18. author = "nosoop",
  19. description = "Filters out rather unscrupulous characters from names and locks the name if "
  20. ... "they are using a completely non-printable name.",
  21. version = PLUGIN_VERSION,
  22. url = "https://pika.nom-nom-nom.us/git/"
  23. }
  24. #define UNPRINTABLE_NAME_USER_FORMAT "unprintable (%d)"
  25. int g_MultiByteNonPrinting[] = {
  26. 0xe2a080, // braille pattern blank U+2800
  27. 0
  28. };
  29. public void OnPluginStart() {
  30. for (int i = 1; i <= MaxClients; i++) {
  31. if (IsClientConnected(i)) {
  32. OnNameUpdated(i);
  33. }
  34. }
  35. }
  36. public void OnClientConnected(int client) {
  37. OnNameUpdated(client);
  38. }
  39. public void OnClientSettingsChanged(int client) {
  40. OnNameUpdated(client);
  41. }
  42. void OnNameUpdated(int client) {
  43. char currentName[MAX_NAME_LENGTH], newNameBuffer[MAX_NAME_LENGTH];
  44. GetClientName(client, currentName, sizeof(currentName));
  45. bool bNameChanged;
  46. /**
  47. * Copies valid characters into another buffer (while also dealing with Unicode).
  48. * The buffer is used if any characters are filtered.
  49. */
  50. for (int i = 0; i < strlen(currentName); /* ... */) {
  51. int fullChar, nCharBytes;
  52. fullChar = GetMultiByteCharacterAt(currentName, i, nCharBytes);
  53. bool bCharFiltered;
  54. /**
  55. * Filter out characters.
  56. *
  57. * Note that while Steam may filter out whitespace characters in its interface, other
  58. * external applications may be used to bypass this restriction.
  59. *
  60. * (We use `sm_rename` to test the filtering process.)
  61. */
  62. switch (nCharBytes) {
  63. case 3: {
  64. // filter unicode invisible characters
  65. bCharFiltered |= IsUnicodeCharInvisible(fullChar);
  66. }
  67. case 1: {
  68. // filter out newlines
  69. bCharFiltered |= (fullChar == 0x0d || fullChar == 0x0a);
  70. }
  71. default: {
  72. // unhandled
  73. }
  74. }
  75. if (!bCharFiltered) {
  76. // it's a character we're not filtering
  77. StrCatMultiByteChar(newNameBuffer, sizeof(newNameBuffer), fullChar, nCharBytes);
  78. }
  79. bNameChanged |= bCharFiltered;
  80. i += nCharBytes;
  81. }
  82. if (bNameChanged) {
  83. // Remove surrounding whitespace
  84. TrimString(newNameBuffer);
  85. // TODO remove duplicate whitespace
  86. // If there's no printable characters in the buffer, the player is a total dongbus.
  87. if (strlen(newNameBuffer) == 0) {
  88. Format(newNameBuffer, sizeof(newNameBuffer), UNPRINTABLE_NAME_USER_FORMAT,
  89. GetClientUserId(client));
  90. SetClientNameLock(client);
  91. }
  92. SetClientName(client, newNameBuffer);
  93. }
  94. // TODO else not name changed, but check if we can do other non-filter stuff? (trim)
  95. }
  96. /**
  97. * Determines if the specified character is non-printing (whitespace or not supported).
  98. *
  99. * References:
  100. * http://www.unicode.org/faq/unsup_char.html
  101. * https://en.wikipedia.org/wiki/Whitespace_character
  102. */
  103. bool IsUnicodeCharInvisible(int uchar) {
  104. // handle invisible math operators, &c. (U+205F ... U+206F)
  105. if (uchar >= 0xe2819f && uchar <= 0xe281af) {
  106. return true;
  107. }
  108. // handle other invisible characters
  109. int c;
  110. do {
  111. if (uchar == g_MultiByteNonPrinting[c]) {
  112. return true;
  113. }
  114. } while (g_MultiByteNonPrinting[++c] != 0);
  115. return false;
  116. }
  117. /**
  118. * Attempts to lock the specified client's name with the existing `namelockid` command.
  119. */
  120. stock bool SetClientNameLock(int client, bool locked = true) {
  121. if (CommandExists("namelockid")) {
  122. ServerCommand("namelockid %d %d", GetClientUserId(client), locked ? 0 : 1);
  123. return true;
  124. }
  125. return false;
  126. }
  127. /**
  128. * Gets a possibly multi-byte character at the specified position in a string as an integer.
  129. *
  130. * If the character is an ASCII character, the integer representation of the character will be
  131. * returned, and the number of characters read will be 1.
  132. *
  133. * This stock is made specifically for the purpose of handling strings with multi-byte
  134. * characters.
  135. */
  136. stock int GetMultiByteCharacterAt(const char[] text, int pos, int &read = 0) {
  137. int nCharBytes, charOutput;
  138. nCharBytes = GetCharBytes(text[pos]);
  139. for (int b = 0; b < nCharBytes; b++) {
  140. charOutput = charOutput << 8;
  141. // read in the current byte
  142. charOutput |= text[pos + b] & 0xFF;
  143. read++;
  144. }
  145. return charOutput;
  146. }
  147. /**
  148. * Appends a multi-byte character to a string.
  149. */
  150. stock void StrCatMultiByteChar(char[] buffer, int maxlen, int character, int size) {
  151. char[] appended = new char[size];
  152. MultiByteCharacterToCharArray(appended, size, character);
  153. StrCat(buffer, maxlen, appended);
  154. }
  155. /**
  156. * Converts a multi-byte character to a char array.
  157. */
  158. stock void MultiByteCharacterToCharArray(char[] buffer, int charsize, int character) {
  159. for (int i = 0; i < charsize; i++) {
  160. buffer[i] = (character >> ((charsize - i - 1) * 8 )) & 0xFF;
  161. }
  162. }