/** * [CSRD] Name Filter * * An in-house plugin that tidies up names. * Made in response to a few script kiddies that are (ab)using newlines and other non-printing * characters in their name. * * If their existing name has no printable characters, their name will be changed to * "unprintable" plus their userid and their name will be locked. */ #pragma semicolon 1 #include #include #pragma newdecls required #define PLUGIN_VERSION "1.0.2" public Plugin myinfo = { name = "[CSRD] Name Filter", author = "nosoop", description = "Filters out rather unscrupulous characters from names and locks the name if " ... "they are using a completely non-printable name.", version = PLUGIN_VERSION, url = "https://pika.nom-nom-nom.us/git/" } #define UNPRINTABLE_NAME_USER_FORMAT "unprintable (%d)" int g_FilteredMultiByteWhitespace[] = { 0xe2a080, // braille pattern blank U+2800 0xe2819f, // medium mathematical space U+205F /* invisible math operators, &c. (char & 0xe281a0) */ 0xe281a0, // word joiner U+2060 0xe281a1, // function application U+2061 0xe281a2, // invisible times U+2062 0xe281a3, // invisible separator U+2063 0xe281a4, // invisible plus U+2064 0xe281aa, // inhibit symmetric swapping U+206A 0xe281ab, // activate symmetric swapping U+206B 0xe281ac, // inhibit arabic form shaping U+206C 0xe281ad, // activate arabic form shaping U+206D 0xe281ae, // national digit shapes U+206E 0xe281af, // nominal digit shapes U+206F 0 }; public void OnPluginStart() { for (int i = 1; i <= MaxClients; i++) { if (IsClientConnected(i)) { OnNameUpdated(i); } } } public void OnClientConnected(int client) { OnNameUpdated(client); } public void OnClientSettingsChanged(int client) { OnNameUpdated(client); } void OnNameUpdated(int client) { char currentName[MAX_NAME_LENGTH], newNameBuffer[MAX_NAME_LENGTH]; GetClientName(client, currentName, sizeof(currentName)); bool bNameChanged; /** * Copies valid characters into another buffer (while also dealing with Unicode). * The buffer is used if any characters are filtered. */ for (int i = 0; i < strlen(currentName); /* ... */) { int fullChar, nCharBytes; fullChar = GetMultiByteCharacterAt(currentName, i, nCharBytes); bool bCharFiltered; // Filter out non-printing characters that can't be handled elsewhere. switch (nCharBytes) { case 3: { // filter unicode invisible characters bCharFiltered |= IsUnicodeCharInvisible(fullChar); } case 1: { // filter out newlines bCharFiltered |= (fullChar == 0x0d || fullChar == 0x0a); } default: { // unhandled } } if (!bCharFiltered) { // it's a character we're not filtering StrCatMultiByteChar(newNameBuffer, sizeof(newNameBuffer), fullChar, nCharBytes); } else { // we filtered this one // we could be as rude as they are in using unprintables, but we're nice // TODO check if the last character in new buffer is a space to prevent multiples StrCat(newNameBuffer, sizeof(newNameBuffer), " "); } bNameChanged |= bCharFiltered; i += nCharBytes; } if (bNameChanged) { // Remove surrounding whitespace TrimString(newNameBuffer); // If there's no printable characters in the buffer, the player is a total dongbus. if (strlen(newNameBuffer) == 0) { Format(newNameBuffer, sizeof(newNameBuffer), UNPRINTABLE_NAME_USER_FORMAT, GetClientUserId(client)); SetClientNameLock(client); } SetClientName(client, newNameBuffer); } // TODO else not name changed, but check if we can do other non-filter stuff? (trim) } bool IsUnicodeCharInvisible(int uchar) { int c; do { if (uchar == g_FilteredMultiByteWhitespace[c]) { return true; } } while (g_FilteredMultiByteWhitespace[++c] != 0); return false; } /** * Attempts to lock the specified client's name with the existing `namelockid` command. */ stock bool SetClientNameLock(int client, bool locked = true) { if (CommandExists("namelockid")) { ServerCommand("namelockid %d %d", GetClientUserId(client), locked ? 0 : 1); return true; } return false; } /** * Gets a possibly multi-byte character at the specified position in a string as an integer. * * If the character is an ASCII character, the integer representation of the character will be * returned, and the number of characters read will be 1. * * This stock is made specifically for the purpose of handling strings with multi-byte * characters. */ stock int GetMultiByteCharacterAt(const char[] text, int pos, int &read = 0) { int nCharBytes, charOutput; nCharBytes = GetCharBytes(text[pos]); for (int b = 0; b < nCharBytes; b++) { charOutput = charOutput << 8; // read in the current byte charOutput |= text[pos + b] & 0xFF; read++; } return charOutput; } /** * Appends a multi-byte character to a string. */ stock void StrCatMultiByteChar(char[] buffer, int maxlen, int character, int size) { char[] appended = new char[size]; MultiByteCharacterToCharArray(appended, size, character); StrCat(buffer, maxlen, appended); } /** * Converts a multi-byte character to a char array. */ stock void MultiByteCharacterToCharArray(char[] buffer, int charsize, int character) { for (int i = 0; i < charsize; i++) { buffer[i] = (character >> ((charsize - i - 1) * 8 )) & 0xFF; } }