/** * [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.5" 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_MultiByteNonPrinting[] = { 0xe2a080, // braille pattern blank U+2800 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 characters. * * Note that while Steam may filter out whitespace characters in its interface, other * external applications may be used to bypass this restriction. * * (We use `sm_rename` to test the filtering process.) */ 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); } bNameChanged |= bCharFiltered; i += nCharBytes; } if (bNameChanged) { // Remove surrounding whitespace TrimString(newNameBuffer); // TODO remove duplicate whitespace // 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) } /** * Determines if the specified character is non-printing (whitespace or not supported). * * References: * http://www.unicode.org/faq/unsup_char.html * https://en.wikipedia.org/wiki/Whitespace_character */ bool IsUnicodeCharInvisible(int uchar) { // handle invisible math operators, &c. (U+205F ... U+206F) if (uchar >= 0xe2819f && uchar <= 0xe281af) { return true; } // handle other invisible characters int c; do { if (uchar == g_MultiByteNonPrinting[c]) { return true; } } while (g_MultiByteNonPrinting[++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; } }