From e2fc4036f6c3a8ffb59717fce3d149732212db2d Mon Sep 17 00:00:00 2001 From: hubdom <26039831+hubdom@users.noreply.github.com> Date: Mon, 24 Aug 2020 11:50:39 +0200 Subject: [PATCH] VPN-Check: update --- VPN-Check/scripting/VPN-Check.sp | 210 +++-- VPN-Check/scripting/include/json.inc | 430 ++++++++++ .../scripting/include/json/decode_helpers.inc | 453 ++++++++++ .../scripting/include/json/definitions.inc | 112 +++ .../scripting/include/json/encode_helpers.inc | 168 ++++ VPN-Check/scripting/include/json/object.inc | 779 ++++++++++++++++++ .../scripting/include/json/string_helpers.inc | 85 ++ 7 files changed, 2127 insertions(+), 110 deletions(-) create mode 100644 VPN-Check/scripting/include/json.inc create mode 100644 VPN-Check/scripting/include/json/decode_helpers.inc create mode 100644 VPN-Check/scripting/include/json/definitions.inc create mode 100644 VPN-Check/scripting/include/json/encode_helpers.inc create mode 100644 VPN-Check/scripting/include/json/object.inc create mode 100644 VPN-Check/scripting/include/json/string_helpers.inc diff --git a/VPN-Check/scripting/VPN-Check.sp b/VPN-Check/scripting/VPN-Check.sp index a7107689..d5452791 100644 --- a/VPN-Check/scripting/VPN-Check.sp +++ b/VPN-Check/scripting/VPN-Check.sp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #undef REQUIRE_PLUGIN #tryinclude @@ -10,11 +10,14 @@ #pragma newdecls required #pragma semicolon 1 +#define APIKEY "30r040-2w8k51-711148-8118n3" + + #define STATUS_ERROR -2 #define STATUS_NONE -1 #define STATUS_SAFE 0 #define STATUS_BAD 1 -#define STATUS_WARNING 2 + ConVar g_cvBlockNoSteamVPN; @@ -32,7 +35,7 @@ public Plugin myinfo = name = "VPN-Check", author = "Neon", description = "", - version = "2.0.0" + version = "2.1.0" }; //---------------------------------------------------------------------------------------------------- @@ -54,8 +57,8 @@ public void SQL_OnTableCreated(Database db, DBResultSet results, const char[] er { for(int i = 1; i <= MaxClients; i++) { - if(IsValidClient(i)) - OnClientPostAdminCheck(i); + if(IsValidClient(i) && IsClientAuthorized(i)) + OnClientAuthorized(i, ""); } } @@ -80,7 +83,7 @@ public void SQL_OnDatabaseConnect(Database db, const char[] error, any data) g_hDatabase = db; - char sQuery[512]; + char sQuery[256]; Format(sQuery, sizeof(sQuery), "CREATE TABLE IF NOT EXISTS ip_table (`ip` varchar(32), `type` int(64), `last_check` int(64), PRIMARY KEY (`ip`))"); g_hDatabase.Query(SQL_OnTableCreated, sQuery, _, DBPrio_High); @@ -89,9 +92,9 @@ public void SQL_OnDatabaseConnect(Database db, const char[] error, any data) //---------------------------------------------------------------------------------------------------- // Purpose: //---------------------------------------------------------------------------------------------------- -public void OnClientPostAdminCheck(int client) +public void OnClientAuthorized(int client, const char[] auth) { - if (!IsValidClient(client)) + if (IsFakeClient(client)) return; char sIP[32]; @@ -108,8 +111,8 @@ public void OnClientPostAdminCheck(int client) //---------------------------------------------------------------------------------------------------- public void SQL_OnQueryCompleted(Database db, DBResultSet results, const char[] error, int iSerial) { - int client; - if ((client = GetClientFromSerial(iSerial)) == 0) + int client = GetClientFromSerial(iSerial); + if (!client) //Player disconnected. return; if (!db || strlen(error)) @@ -119,8 +122,6 @@ public void SQL_OnQueryCompleted(Database db, DBResultSet results, const char[] return; } - int iCurrentTime = GetTime(); - if (results.RowCount && results.FetchRow()) { int iFieldNum; @@ -133,10 +134,12 @@ public void SQL_OnQueryCompleted(Database db, DBResultSet results, const char[] delete results; - if ((iCurrentTime - iLastCheck) < 86400) + if ((GetTime() - iLastCheck) < (86400 * 2)) { g_bStatus[client] = iType; - NotifyAdmins(client); + if (g_bStatus[client] == STATUS_BAD) + TakeAction(client); + return; } } @@ -145,11 +148,11 @@ public void SQL_OnQueryCompleted(Database db, DBResultSet results, const char[] GetClientIP(client, sIP, sizeof(sIP)); char sRequest[256]; - FormatEx(sRequest, sizeof(sRequest), "http://v2.api.iphub.info/ip/%s", sIP); + FormatEx(sRequest, sizeof(sRequest), "https://proxycheck.io/v2/%s?key=%s&vpn=1", sIP, APIKEY); + //PrintToConsoleAll(sRequest); Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, sRequest); if (!hRequest || - !SteamWorks_SetHTTPRequestHeaderValue(hRequest, "X-Key", "NjA5MDpnZG52emJHWjNSQmdEVTMySUwxSlhTUXBEZGt6MWtabg==") || !SteamWorks_SetHTTPCallbacks(hRequest, OnTransferComplete) || !SteamWorks_SetHTTPRequestContextValue(hRequest, iSerial) || !SteamWorks_SendHTTPRequest(hRequest)) @@ -164,7 +167,6 @@ public void SQL_OnQueryCompleted(Database db, DBResultSet results, const char[] public int OnTransferComplete(Handle hRequest, bool bFailure, bool bSuccessful, EHTTPStatusCode eStatusCode, int iSerial) { int client = GetClientFromSerial(iSerial); - if (!client) //Player disconnected. { delete hRequest; @@ -188,66 +190,55 @@ public int OnTransferComplete(Handle hRequest, bool bFailure, bool bSuccessful, public int OnTransferResponse(char[] sData, int iSerial) { int client = GetClientFromSerial(iSerial); - if (!client) //Player disconnected. return; - JSONValue hJSON = json_load(sData); - if (hJSON == null) - { - delete hJSON; - g_bStatus[client] = STATUS_ERROR; - LogError("JSON-Error: Could not load: %s", sData); - return; - } - - if(!json_is_object(view_as(hJSON))) - { - delete hJSON; - g_bStatus[client] = STATUS_ERROR; - LogError("JSON-Error: Object not found"); - return; - } - - JSONObjectIterator hJSONIterator = json_object_iter_at(view_as(hJSON), "block"); - if (hJSONIterator == null) - { - delete hJSON; - delete hJSONIterator; - g_bStatus[client] = STATUS_ERROR; - LogError("JSON-Error: Key not found"); - return; - } - - char sKey[128]; - json_object_iter_key(hJSONIterator, sKey, sizeof(sKey)); - JSONValue hJSONValue = json_object_iter_value(hJSONIterator); - if(!json_is_integer(view_as(hJSONValue))) - { - delete hJSON; - delete hJSONIterator; - delete hJSONValue; - g_bStatus[client] = STATUS_ERROR; - LogError("JSON-Error: Integer value not found"); - return; - } - - g_bStatus[client] = json_integer_value(view_as(hJSONValue)); - NotifyAdmins(client); - - delete hJSON; - delete hJSONIterator; - delete hJSONValue; - char sIP[32]; GetClientIP(client, sIP, sizeof(sIP)); + JSON_Object obj = json_decode(sData); + + char sStatus[32]; + obj.GetString("status", sStatus, sizeof(sStatus)); + if (!StrEqual(sStatus, "ok") && !StrEqual(sStatus, "warning")) + { + char sMessage[256]; + obj.GetString("message", sMessage, sizeof(sMessage)); + LogError("API-Response: %s: %s", sStatus, sMessage); + g_bStatus[client] = STATUS_ERROR; + obj.Cleanup(); + delete obj; + return; + } + + JSON_Object ipobj = obj.GetObject(sIP); + char sProxy[16]; + ipobj.GetString("proxy", sProxy, sizeof(sProxy)); + if (StrEqual(sProxy, "no")) + g_bStatus[client] = STATUS_SAFE; + else + { + //char sType[64]; + //ipobj.GetString("type", sType, sizeof(sType)); + //if (StrEqual(sType, "Compromised Server")) + //g_bStatus[client] = STATUS_SAFE; + //else + //{ + g_bStatus[client] = STATUS_BAD; + TakeAction(client); + //} + } + int iCurrentTime = GetTime(); char sQuery[512]; Format(sQuery, sizeof(sQuery), "INSERT INTO ip_table (ip, type, last_check) VALUES ('%s', '%d', '%d') ON DUPLICATE KEY UPDATE type='%d', last_check='%d';", sIP, g_bStatus[client], iCurrentTime, g_bStatus[client], iCurrentTime); - g_hDatabase.Query(SQL_OnQueryCompleted, sQuery, _, DBPrio_Low); + + ipobj.Cleanup(); + delete ipobj; + obj.Cleanup(); + delete obj; } //---------------------------------------------------------------------------------------------------- @@ -287,23 +278,6 @@ public Action Command_CheckVPN(int client, int args) bFound = true; } - else if (g_bStatus[i] == STATUS_WARNING) - { - GetClientAuthId(i, AuthId_Steam2, sSteamID, sizeof(sSteamID)); - GetClientIP(i, sIP, sizeof(sIP)); - - if (g_bPMLoaded) - { - if (!PM_IsPlayerSteam(i)) - Format(sBuffer, sizeof(sBuffer), "%s\"%L\"[NOSTEAM] is maybe using a VPN (%s).\n", sBuffer, i, sIP); - else - Format(sBuffer, sizeof(sBuffer), "%s\"%L\"[STEAM] is maybe using a VPN (%s).\n", sBuffer, i, sIP); - } - else - Format(sBuffer, sizeof(sBuffer), "%s\"%L\" is maybe using a VPN (%s).\n", sBuffer, i, sIP); - - bFound = true; - } else if (g_bStatus[i] == STATUS_ERROR) { Format(sBuffer, sizeof(sBuffer), "%s\"%L\" Error: VPN-Check failed, check the error logs.\n", sBuffer, i); @@ -322,50 +296,66 @@ public Action Command_CheckVPN(int client, int args) //---------------------------------------------------------------------------------------------------- // Purpose: //---------------------------------------------------------------------------------------------------- -public void NotifyAdmins(int client) +public void TakeAction(int client) { - if (g_bStatus[client] == STATUS_BAD) + char sIP[32]; + GetClientIP(client, sIP, sizeof(sIP)); + + char sSteamID[32]; + GetClientAuthId(client, AuthId_Steam2, sSteamID, sizeof(sSteamID)); + + for(int i = 1; i <= MaxClients; i++) { - char sIP[32]; - GetClientIP(client, sIP, sizeof(sIP)); - - char sSteamID[32]; - GetClientAuthId(client, AuthId_Steam2, sSteamID, sizeof(sSteamID)); - - for(int i = 1; i <= MaxClients; i++) + if(IsValidClient(i) && CheckCommandAccess(i, "sm_vpn", ADMFLAG_RCON)) { - if(IsValidClient(i) && CheckCommandAccess(i, "sm_vpn", ADMFLAG_RCON)) + if (g_bPMLoaded) { - if (g_bPMLoaded) + if (!PM_IsPlayerSteam(client)) { - if (!PM_IsPlayerSteam(client)) + if (g_cvBlockNoSteamVPN.BoolValue) { - if (g_cvBlockNoSteamVPN.BoolValue) - { - CPrintToChat(i, "{green}[SM]{default} %L[NOSTEAM] is using a {red}VPN {default}(IP: %s). Client will be kicked.", client, sIP); - KickClient(client, "VPN not allowed"); - LogAction(client, -1, "\"%L\"[NOSTEAM] is using a VPN (IP: %s). Client got kicked.", client, sIP); - } - else - { - CPrintToChat(i, "{green}[SM]{default} %L[NOSTEAM] is using a {red}VPN {default}(IP: %s).", client, sIP); - LogMessage("%L[NOSTEAM] is using a VPN (IP: %s).", client, sIP); - } + CPrintToChat(i, "{green}[SM]{default} %L[NOSTEAM] is using a {red}VPN {default}(IP: %s). Client will be kicked.", client, sIP); } else { - CPrintToChat(i, "{green}[SM]{default} %L[STEAM] is using a {red}VPN {default}(IP: %s).", client, sIP); - LogMessage("%L[STEAM] is using a VPN (IP: %s).", client, sIP); + CPrintToChat(i, "{green}[SM]{default} %L[NOSTEAM] is using a {red}VPN {default}(IP: %s).", client, sIP); } } else { - CPrintToChat(i, "{green}[SM]{default} %L is using a {red}VPN {default}(IP: %s).", client, sIP); - LogMessage("%L is using a VPN (IP: %s).", client, sIP); + CPrintToChat(i, "{green}[SM]{default} %L[STEAM] is using a {red}VPN {default}(IP: %s).", client, sIP); } } + else + { + CPrintToChat(i, "{green}[SM]{default} %L is using a {red}VPN {default}(IP: %s).", client, sIP); + } } } + + if (g_bPMLoaded) + { + if (!PM_IsPlayerSteam(client)) + { + if (g_cvBlockNoSteamVPN.BoolValue) + { + LogAction(client, -1, "\"%L\"[NOSTEAM] is using a VPN (IP: %s). Client got kicked.", client, sIP); + KickClient(client, "VPN not allowed"); + } + else + { + LogMessage("%L[NOSTEAM] is using a VPN (IP: %s).", client, sIP); + } + } + else + { + LogMessage("%L[STEAM] is using a VPN (IP: %s).", client, sIP); + } + } + else + { + LogMessage("%L is using a VPN (IP: %s).", client, sIP); + } } //---------------------------------------------------------------------------------------------------- diff --git a/VPN-Check/scripting/include/json.inc b/VPN-Check/scripting/include/json.inc new file mode 100644 index 00000000..a5d014f7 --- /dev/null +++ b/VPN-Check/scripting/include/json.inc @@ -0,0 +1,430 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * Provides a pure SourcePawn implementation of JSON encoding and decoding. + * https://github.com/clugg/sm-json + * + * sm-json (C)2018 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_included + #endinput +#endif +#define _json_included + +#include +#include +#include +#include +#include +#include + +/** + * Encodes a JSON_Object into its string representation. + * + * @param obj Object to encode. + * @param output String buffer to store output. + * @param maxlen Maximum size of string buffer. + * @param pretty_print Should the output be pretty printed (newlines and spaces)? [optional, default: false] + * @param depth The current depth of the encoder. [optional, default: 0] + */ +stock void json_encode(JSON_Object obj, char[] output, int maxlen, bool pretty_print = false, int depth = 0) { + bool is_array = obj.IsArray; + bool is_empty = true; + int builder_size; + + // used in key iterator + int str_length = 1; + int int_value; + int cell_length = 0; + + strcopy(output, maxlen, (is_array) ? "[" : "{"); + + StringMapSnapshot snap = obj.Snapshot(); + int key_length = 0; + for (int i = 0; i < obj.Length; ++i) { + key_length = snap.KeyBufferSize(i); + char[] key = new char[key_length]; + + if (is_array) { + obj.GetIndexString(key, key_length, i); + } else { + snap.GetKey(i, key, key_length); + } + + // skip meta-keys + if (json_is_meta_key(key)) { + continue; + } + + // skip keys that are marked as hidden + if (obj.GetKeyHidden(key)) { + continue; + } + + // skip keys of unknown type + JSON_CELL_TYPE type = obj.GetKeyType(key); + if (type == Type_Invalid) { + continue; + } + + // if we are dealing with a string, prepare the str_value variable for fetching + if (type == Type_String) { + str_length = obj.GetKeyLength(key); + } + char[] str_value = new char[str_length + 1]; + + // determine the length of the char[] needed to represent our cell data + cell_length = 0; + switch (type) { + case Type_String: { + // get the string value early, as its cell_length is determined by its contents + obj.GetString(key, str_value, str_length + 1); + cell_length = json_cell_string_size(str_length); + } + case Type_Int: { + // get the int value early, as its cell_length is determined by its contents + int_value = obj.GetInt(key); + cell_length = json_cell_int_size(int_value); + } + case Type_Float: { + cell_length = json_cell_float_size(); + } + case Type_Bool: { + cell_length = json_cell_bool_size(); + } + case Type_Null: { + cell_length = json_cell_null_size(); + } + case Type_Object: { + cell_length = maxlen; + } + } + + // fit the contents into the cell + char[] cell = new char[cell_length]; + switch (type) { + case Type_String: { + json_cell_string(str_value, cell, cell_length); + } + case Type_Int: { + json_cell_int(int_value, cell, cell_length); + } + case Type_Float: { + float value = obj.GetFloat(key); + json_cell_float(value, cell, cell_length); + } + case Type_Bool: { + bool value = obj.GetBool(key); + json_cell_bool(value, cell, cell_length); + } + case Type_Null: { + json_cell_null(cell, cell_length); + } + case Type_Object: { + JSON_Object value = obj.GetObject(key); + json_encode(value, cell, cell_length, pretty_print, depth + 1); + } + } + + // make the builder fit our key:value + builder_size = cell_length + 1; // use previously determined cell length and +1 for comma + if (!is_array) { + builder_size += json_cell_string_size(strlen(key)) + 1; // get the length of the key, +1 for : + } + + if (pretty_print && !is_array) { + // 1 space after colon + builder_size += strlen(JSON_PP_AFTER_COLON); + } + + char[] builder = new char[builder_size]; + strcopy(builder, builder_size, ""); + // add the key if it's an object + if (!is_array) { + json_cell_string(key, builder, builder_size); + StrCat(builder, builder_size, ":"); + if (pretty_print) { + StrCat(builder, builder_size, JSON_PP_AFTER_COLON); + } + } + + // add the value and a trailing comma + StrCat(builder, builder_size, cell); + StrCat(builder, builder_size, ","); + + if (pretty_print) { + StrCat(output, maxlen, JSON_PP_NEWLINE); + for (int j = 0; j < depth + 1; ++j) { + StrCat(output, maxlen, JSON_PP_INDENT); + } + } + + // send builder to output + StrCat(output, maxlen, builder); + + is_empty = false; + } + + delete snap; + + if (!is_empty) { + // remove the final comma + output[strlen(output) - 1] = '\0'; + } + + if (pretty_print) { + StrCat(output, maxlen, JSON_PP_NEWLINE); + } + + if (pretty_print) { + for (int j = 0; j < depth; ++j) { + StrCat(output, maxlen, JSON_PP_INDENT); + } + } + + // append closing bracket + StrCat(output, maxlen, (is_array) ? "]" : "}"); +} + +/** + * Decodes a JSON string into its JSON_Object representation. + * + * @param buffer Buffer to decode. + * @param result Object to store output in. Setting this allows loading over an existing JSON_Object, 'refreshing' it as opposed to creating a new one. [optional, default: null] + * @param pos Current position of the decoder as a bytes offset into the buffer. + * @param depth Current depth of the decoder as child elements in the object. + * @returns JSON_Object representation of buffer, or null if decoding failed (buffer didn't contain valid JSON). + */ +stock JSON_Object json_decode(const char[] buffer, JSON_Object result = null, int &pos = 0, int depth = 0) { + int length = strlen(buffer); + bool is_array = false; + + // skip preceding whitespace + if (!json_skip_whitespace(buffer, length, pos)) { + //LogError("json_decode: buffer ended early at %d", pos); + return null; + } + + if (json_is_object(buffer[pos])) { + is_array = false; + } else if (json_is_array(buffer[pos])) { + is_array = true; + } else { + //LogError("json_decode: character not identified as object or array at %d", pos); + return null; + } + + if (result == null) { + result = new JSON_Object(is_array); + } + + bool empty_checked = false; + char[] key = new char[length]; + char[] cell = new char[length]; + + // while we haven't reached the end of our structure + while ((!is_array && !json_is_object_end(buffer[pos])) + || (is_array && !json_is_array_end(buffer[pos]))) { + // pos is either an opening structure or comma, so increment past it + ++pos; + + // if we are at the end of an object or array + // and haven't checked for empty yet, we can stop here (empty structure) + if (((!is_array && json_is_object_end(buffer[pos])) + || (is_array && json_is_array_end(buffer[pos]))) + && !empty_checked) { + break; + } + + empty_checked = true; + + // skip any whitespace preceding the element + if (!json_skip_whitespace(buffer, length, pos)) { + //LogError("json_decode: buffer ended early at %d", pos); + return null; + } + + // if dealing with an object, look for the key + if (!is_array) { + if (!json_is_string(buffer[pos])) { + //LogError("json_decode: expected key string at %d", pos); + return null; + } + + // extract the key from the buffer + json_extract_string(buffer, length, pos, key, length, is_array); + + // skip any whitespace following the key + if (!json_skip_whitespace(buffer, length, pos)) { + //LogError("json_decode: buffer ended early at %d", pos); + return null; + } + + // ensure that we find a colon + if (buffer[pos++] != ':') { + //LogError("json_decode: expected colon after key at %d", pos); + return null; + } + + // skip any whitespace following the colon + if (!json_skip_whitespace(buffer, length, pos)) { + //LogError("json_decode: buffer ended early at %d", pos); + return null; + } + } + + if (json_is_object(buffer[pos]) || json_is_array(buffer[pos])) { + // if we are dealing with an object or array + // fetch the existing object if one exists at the key + JSON_Object current = (!is_array) ? result.GetObject(key) : null; + + // decode recursively + JSON_Object value = json_decode(buffer, current, pos, depth + 1); + + // decoding failed, error will be logged in json_decode + if (value == null) { + return null; + } + + if (is_array) { + result.PushObject(value); + } else { + result.SetObject(key, value); + } + } else if (json_is_string(buffer[pos])) { + // if we are dealing with a string, attempt to extract it + if (!json_extract_string(buffer, length, pos, cell, length, is_array)) { + //LogError("json_decode: couldn't extract string at %d", pos); + return null; + } + + if (is_array) { + result.PushString(cell); + } else { + result.SetString(key, cell); + } + } else { + if (!json_extract_until_end(buffer, length, pos, cell, length, is_array)) { + //LogError("json_decode: couldn't extract until end at %d", pos); + return null; + } + + if (strlen(cell) == 0) { + //LogError("json_decode: empty cell encountered at %d", pos); + return null; + } + + if (json_is_int(cell)) { + int value = json_extract_int(cell); + if (is_array) { + result.PushInt(value); + } else { + result.SetInt(key, value); + } + } else if (json_is_float(cell)) { + float value = json_extract_float(cell); + if (is_array) { + result.PushFloat(value); + } else { + result.SetFloat(key, value); + } + } else if (json_is_bool(cell)) { + bool value = json_extract_bool(cell); + if (is_array) { + result.PushBool(value); + } else { + result.SetBool(key, value); + } + } else if (json_is_null(cell)) { + if (is_array) { + result.PushHandle(null); + } else { + result.SetHandle(key, null); + } + } else { + //LogError("json_decode: unknown type encountered at %d: %s", pos, cell); + return null; + } + } + + if (!json_skip_whitespace(buffer, length, pos)) { + //LogError("json_decode: buffer ended early at %d", pos); + return null; + } + } + + // skip remaining whitespace and ensure we're at the end of the buffer + ++pos; + if (json_skip_whitespace(buffer, length, pos) && depth == 0) { + //LogError("json_decode: unexpected data after end of structure at %d", pos); + return null; + } + + return result; +} + +/** + * Recursively cleans up JSON_Objects and any objects referenced within. + * + * @param obj JSON_Object to clean up. + */ +void json_cleanup(JSON_Object obj) { + bool is_array = obj.IsArray; + + int key_length = 0; + StringMapSnapshot snap = obj.Snapshot(); + for (int i = 0; i < snap.Length; ++i) { + key_length = snap.KeyBufferSize(i); + char[] key = new char[key_length]; + + // ignore meta keys + snap.GetKey(i, key, key_length); + if (json_is_meta_key(key)) { + continue; + } + + // only clean up objects + JSON_CELL_TYPE type = obj.GetKeyType(key); + if (type != Type_Object) { + continue; + } + + JSON_Object nested_obj = obj.GetObject(key); + if (nested_obj != null) { + nested_obj.Cleanup(); + delete nested_obj; + } + } + + obj.Clear(); + delete snap; + + if (is_array) { + obj.SetValue(JSON_ARRAY_INDEX_KEY, 0); + } +} diff --git a/VPN-Check/scripting/include/json/decode_helpers.inc b/VPN-Check/scripting/include/json/decode_helpers.inc new file mode 100644 index 00000000..960f0317 --- /dev/null +++ b/VPN-Check/scripting/include/json/decode_helpers.inc @@ -0,0 +1,453 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * Provides a pure SourcePawn implementation of JSON encoding and decoding. + * https://github.com/clugg/sm-json + * + * sm-json (C)2018 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_decode_helpers_included + #endinput +#endif +#define _json_decode_helpers_included + +#include + +/** + * @section Analysing format of incoming JSON cells. + */ + +/** + * Checks whether the character at the given + * position in the buffer is whitespace. + * + * @param buffer String buffer of data. + * @param pos Position to check in buffer. + * @returns True if buffer[pos] is whitespace, false otherwise. + */ +stock bool json_is_whitespace(const char[] buffer, int &pos) { + return buffer[pos] == ' ' + || buffer[pos] == '\t' + || buffer[pos] == '\r' + || buffer[pos] == '\n'; +} + +/** + * Checks whether the character at the beginning + * of the buffer is the start of a string. + * + * @param buffer String buffer of data. + * @returns True if buffer[0] is the start of a string, false otherwise. + */ +stock bool json_is_string(const char[] buffer) { + return buffer[0] == '"'; +} + +/** + * Checks whether the buffer provided contains an int. + * + * @param buffer String buffer of data. + * @returns True if buffer contains an int, false otherwise. + */ +stock bool json_is_int(const char[] buffer) { + bool starts_with_zero = false; + bool has_digit_gt_zero = false; + + int length = strlen(buffer); + for (int i = 0; i < length; ++i) { + // allow minus as first character only + if (i == 0 && buffer[i] == '-') { + continue; + } + + if (IsCharNumeric(buffer[i])) { + if (buffer[i] == '0') { + if (starts_with_zero) { + // detect repeating leading zeros + return false; + } else if (!has_digit_gt_zero) { + starts_with_zero = true; + } + } else { + has_digit_gt_zero = true; + } + } else { + return false; + } + } + + // buffer must start with zero and have no other numerics before decimal + // OR not start with zero and have other numerics + return ((starts_with_zero && !has_digit_gt_zero) || (!starts_with_zero && has_digit_gt_zero)); +} + +/** + * Checks whether the buffer provided contains a float. + * + * @param buffer String buffer of data. + * @returns True if buffer contains a float, false otherwise. + */ +stock bool json_is_float(const char[] buffer) { + bool starts_with_zero = false; + bool has_digit_gt_zero = false; + bool after_decimal = false; + bool has_digit_after_decimal = false; + bool after_exponent = false; + bool has_digit_after_exponent = false; + + int length = strlen(buffer); + for (int i = 0; i < length; ++i) { + // allow minus as first character only + if (i == 0 && buffer[i] == '-') { + continue; + } + + // if we haven't encountered a decimal or exponent yet + if (!after_decimal && !after_exponent) { + if (buffer[i] == '.') { + // if we encounter a decimal before any digits + if (!starts_with_zero && !has_digit_gt_zero) { + return false; + } + + after_decimal = true; + } else if (buffer[i] == 'e' || buffer[i] == 'E') { + // if we encounter an exponent before any non-zero digits + if (starts_with_zero && !has_digit_gt_zero) { + return false; + } + + after_exponent = true; + } else if (IsCharNumeric(buffer[i])) { + if (buffer[i] == '0') { + if (starts_with_zero) { + // detect repeating leading zeros + return false; + } else if (!has_digit_gt_zero) { + starts_with_zero = true; + } + } else { + has_digit_gt_zero = true; + } + } else { + return false; + } + } else if (after_decimal && !after_exponent) { + // after decimal has been encountered, allow any numerics + if (IsCharNumeric(buffer[i])) { + has_digit_after_decimal = true; + } else if (buffer[i] == 'e' || buffer[i] == 'E') { + if (!has_digit_after_decimal) { + // detect exponents directly after decimal + return false; + } + + after_exponent = true; + } else { + return false; + } + } else if (after_exponent) { + if ((buffer[i] == '+' || buffer[i] == '-') && (buffer[i - 1] == 'e' || buffer[i - 1] == 'E')) { + // allow + or - directly after exponent + continue; + } else if (IsCharNumeric(buffer[i])) { + has_digit_after_exponent = true; + } else { + return false; + } + } + } + + if (starts_with_zero && has_digit_gt_zero) { + // if buffer starts with zero, there should be no other digits before the decimal + return false; + } + + // if we have a decimal, there should be digit(s) after it + if (after_decimal) { + if (!has_digit_after_decimal) { + return false; + } + } + + // if we have an exponent, there should be digit(s) after it + if (after_exponent) { + if (!has_digit_after_exponent) { + return false; + } + } + + // we should have reached an exponent, decimal or both + // otherwise, this number can be handled by the int parser + return after_decimal || after_exponent; +} + +/** + * Checks whether the buffer provided contains a bool. + * + * @param buffer String buffer of data. + * @returns True if buffer contains a bool, false otherwise. + */ +stock bool json_is_bool(const char[] buffer) { + return StrEqual(buffer, "true") + || StrEqual(buffer, "false"); +} + +/** + * Checks whether the buffer provided contains null. + * + * @param buffer String buffer of data. + * @returns True if buffer contains null, false otherwise. + */ +stock bool json_is_null(const char[] buffer) { + return StrEqual(buffer, "null"); +} + +/** + * Checks whether the character at the beginning + * of the buffer is the start of an object. + * + * @param buffer String buffer of data. + * @returns True if buffer[0] is the start of an object, false otherwise. + */ +stock bool json_is_object(const char[] buffer) { + return buffer[0] == '{'; +} + +/** + * Checks whether the character at the beginning + * of the buffer is the end of an object. + * + * @param buffer String buffer of data. + * @returns True if buffer[0] is the end of an object, false otherwise. + */ +stock bool json_is_object_end(const char[] buffer) { + return buffer[0] == '}'; +} + +/** + * Checks whether the character at the beginning + * of the buffer is the start of an array. + * + * @param buffer String buffer of data. + * @returns True if buffer[0] is the start of an array, false otherwise. + */ +stock bool json_is_array(const char[] buffer) { + return buffer[0] == '['; +} + +/** + * Checks whether the character at the beginning + * of the buffer is the start of an array. + * + * @param buffer String buffer of data. + * @returns True if buffer[0] is the start of an array, false otherwise. + */ +stock bool json_is_array_end(const char[] buffer) { + return buffer[0] == ']'; +} + +/** + * Checks whether the character at the given position in the buffer + * is considered a valid 'end point' for some data, such as a + * colon (indicating a key), a comma (indicating a new element), + * or the end of an object or array. + * + * @param buffer String buffer of data. + * @param pos Position to check in buffer. + * @returns True if buffer[pos] is a valid data end point, false otherwise. + */ +stock bool json_is_at_end(const char[] buffer, int &pos, bool is_array) { + return buffer[pos] == ',' + || (!is_array && buffer[pos] == ':') + || json_is_object_end(buffer[pos]) + || json_is_array_end(buffer[pos]); +} + +/** + * Moves the position until it reaches a non-whitespace + * character or the end of the buffer's maximum size. + * + * @param buffer String buffer of data. + * @param maxlen Maximum size of string buffer. + * @param pos Position to increment. + * @returns True if pos is not at the end of the buffer, false otherwise. + */ +stock bool json_skip_whitespace(const char[] buffer, int maxlen, int &pos) { + while (json_is_whitespace(buffer, pos) && pos < maxlen) { + ++pos; + } + + return pos < maxlen; +} + +/** + * Extracts a JSON cell from the buffer until + * a valid end point is reached. + * + * @param buffer String buffer of data. + * @param maxlen Maximum size of string buffer. + * @param pos Position to increment. + * @param output String buffer to store output. + * @param output_maxlen Maximum size of output string buffer. + * @param is_array Whether the decoder is currently processing an array. + * @returns True if pos is not at the end of the buffer, false otherwise. + */ +stock bool json_extract_until_end(const char[] buffer, int maxlen, int &pos, char[] output, int output_maxlen, bool is_array) { + strcopy(output, output_maxlen, ""); + + int start = pos; + // while we haven't reached whitespace, a valid end point or the maxlen, increment position + while (!json_is_whitespace(buffer, pos) && !json_is_at_end(buffer, pos, is_array) && pos < maxlen) { + ++pos; + } + int end = pos; + + // skip any following whitespace + json_skip_whitespace(buffer, maxlen, pos); + + // if we aren't at a valid endpoint, extraction failed + if (!json_is_at_end(buffer, pos, is_array)) { + return false; + } + + // copy only from start with length end - start + NULL + strcopy(output, end - start + 1, buffer[start]); + + return pos < maxlen; +} + + +/** + * Extracts a JSON string from the buffer until + * a valid end point is reached. + * + * @param buffer String buffer of data. + * @param maxlen Maximum size of string buffer. + * @param pos Position to increment. + * @param output String buffer to store output. + * @param output_maxlen Maximum size of output string buffer. + * @param is_array Whether the decoder is currently processing an array. + * @returns True if pos is not at the end of the buffer, false otherwise. + */ +stock bool json_extract_string(const char[] buffer, int maxlen, int &pos, char[] output, int output_maxlen, bool is_array) { + // extracts a string which needs to be quote-escaped + strcopy(output, output_maxlen, ""); + + // increment past opening quote + ++pos; + + // set start to position of first character in string + int start = pos; + + // while we haven't hit the end of the buffer + while (pos < maxlen) { + // check for unescaped control characters + if (buffer[pos] == '\b' + || buffer[pos] == '\f' + || buffer[pos] == '\n' + || buffer[pos] == '\r' + || buffer[pos] == '\t') { + return false; + } + + // pass over any non-quote character + if (buffer[pos] != '"') { + ++pos; + continue; + } + + // count preceding backslashes to check if quote is escaped + int search_pos = pos; + int preceding_backslashes = 0; + while (search_pos > 0 && buffer[--search_pos] == '\\') { + ++preceding_backslashes; + } + + // if we have an even number of backslashes, the quote is not escaped + if (preceding_backslashes % 2 == 0) { + break; + } + + // otherwise, pass over the quote as it must be escaped + ++pos; + } + + // set end to the current position + int end = pos; + + // jump 1 ahead since we ended on " instead of an ending char + ++pos; + + // skip trailing whitespace + if (!json_skip_whitespace(buffer, maxlen, pos)) { + return false; + } + + // if we haven't reached an ending character at the end of the cell + // there is likely junk data not encapsulated by a string + if (!json_is_at_end(buffer, pos, is_array)) { + return false; + } + + // copy only from start with length end - start + NULL + strcopy(output, end - start + 1, buffer[start]); + json_unescape_string(output, maxlen); + + return pos < maxlen; +} + +/** + * Extracts an int from the buffer. + * + * @param buffer String buffer of data. + * @returns Int value of the buffer. + */ +stock int json_extract_int(const char[] buffer) { + return StringToInt(buffer); +} + +/** + * Extracts a float from the buffer. + * + * @param buffer String buffer of data. + * @returns Float value of the buffer. + */ +stock float json_extract_float(const char[] buffer) { + return StringToFloat(buffer); +} + +/** + * Extracts a bool from the buffer. + * + * @param buffer String buffer of data. + * @returns Bool value of the buffer. + */ +stock bool json_extract_bool(const char[] buffer) { + return StrEqual(buffer, "true"); +} diff --git a/VPN-Check/scripting/include/json/definitions.inc b/VPN-Check/scripting/include/json/definitions.inc new file mode 100644 index 00000000..49715800 --- /dev/null +++ b/VPN-Check/scripting/include/json/definitions.inc @@ -0,0 +1,112 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * Provides a pure SourcePawn implementation of JSON encoding and decoding. + * https://github.com/clugg/sm-json + * + * sm-json (C)2018 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_definitions_included + #endinput +#endif +#define _json_definitions_included + +#include +#include + +// you may need to tinker with these for large objects/floating point numbers +#define JSON_FLOAT_BUFFER_SIZE 32 +#define JSON_INDEX_BUFFER_SIZE 16 + +// meta-key defines +#define JSON_ARRAY_INDEX_KEY "__array_index" +#define JSON_META_TYPE_KEY ":type" +#define JSON_META_LENGTH_KEY ":length" +#define JSON_META_HIDDEN_KEY ":hidden" + +// pretty print constants +#define JSON_PP_AFTER_COLON " " +#define JSON_PP_INDENT " " +#define JSON_PP_NEWLINE "\n" + +/** + * Types of cells within a JSON object + */ +enum JSON_CELL_TYPE { + Type_Invalid = -1, + Type_String = 0, + Type_Int, + Type_Float, + Type_Bool, + Type_Null, + Type_Object +}; + +/** + * Multi-dimensional char array mapping characters to their escaped form + */ +char JSON_STRING_NORMAL[][] = {"\\", "\"", "/", "\b", "\f", "\n", "\r", "\t" }; +char JSON_STRING_ESCAPED[][] = {"\\\\", "\\\"", "\\/", "\\b", "\\f", "\\n", "\\r", "\\t"}; + +/** + * Escapes a string based on the rules defined in json/definitions.inc + * + * @param buffer String buffer. + * @param maxlength Maximum buffer length. + */ +public void json_escape_string(char[] buffer, int maxlen) { + for (int i = 0; i < sizeof(JSON_STRING_NORMAL); ++i) { + ReplaceString(buffer, maxlen, JSON_STRING_NORMAL[i], JSON_STRING_ESCAPED[i]); + } +} + +/** + * Unescapes a string based on the rules defined in json/definitions.inc + * + * @param buffer String buffer. + * @param maxlength Maximum buffer length. + */ +public void json_unescape_string(char[] buffer, int maxlen) { + for (int i = 0; i < sizeof(JSON_STRING_NORMAL); ++i) { + ReplaceString(buffer, maxlen, JSON_STRING_ESCAPED[i], JSON_STRING_NORMAL[i]); + } +} + +/** + * Checks whether the key provided is an meta-key that + * should only be used internally. + * + * @param key Key to check. + * @returns True when it is a meta-key, false otherwise. + */ +stock bool json_is_meta_key(char[] key) { + return json_string_endswith(key, JSON_META_TYPE_KEY) + || json_string_endswith(key, JSON_META_LENGTH_KEY) + || json_string_endswith(key, JSON_META_HIDDEN_KEY) + || StrEqual(key, JSON_ARRAY_INDEX_KEY); +} diff --git a/VPN-Check/scripting/include/json/encode_helpers.inc b/VPN-Check/scripting/include/json/encode_helpers.inc new file mode 100644 index 00000000..db0ff13b --- /dev/null +++ b/VPN-Check/scripting/include/json/encode_helpers.inc @@ -0,0 +1,168 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * Provides a pure SourcePawn implementation of JSON encoding and decoding. + * https://github.com/clugg/sm-json + * + * sm-json (C)2018 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_encode_helpers_included + #endinput +#endif +#define _json_encode_helpers_included + +#include + +/** + * @section Calculating buffer sizes for JSON cells. + */ + +/** + * Calculates the maximum buffer length required to + * store the JSON cell representation of a string. + * + * @param maxlen The string's current length or buffer size. + * @returns Maximum buffer length. + */ +stock int json_cell_string_size(int maxlen) { + return (maxlen * 2) + 3; // * 2 for potential escaping, + 2 for surrounding quotes + NULL +} + +/** + * Calculates the maximum buffer length required to + * store the JSON cell representation of an int. + * + * @param input The int. + * @returns Maximum buffer length. + */ +stock int json_cell_int_size(int input) { + if (input == 0) { + return 2; // "0" + NULL + } + + return ((input < 0) ? 1 : 0) + RoundToFloor(Logarithm(FloatAbs(float(input)), 10.0)) + 2; // neg sign + number of digits + NULL +} + +/** + * Calculates the maximum buffer length required to + * store the JSON cell representation of a float. + * + * @returns Maximum buffer length. + */ +stock int json_cell_float_size() { + return JSON_FLOAT_BUFFER_SIZE; // fixed-length +} + +/** + * Calculates the maximum buffer length required to + * store the JSON cell representation of a bool. + * + * @returns Maximum buffer length. + */ +stock int json_cell_bool_size() { + return 6; // "true|false" + NULL +} + +/** + * Calculates the maximum buffer length required to + * store the JSON cell representation of null. + * + * @returns Maximum buffer length. + */ +stock int json_cell_null_size() { + return 5; // "null" + NULL +} + +/** + * @section Generating JSON cells. + */ + +/** + * Generates the JSON cell representation of a string. + * + * @param input Value to generate output for. + * @param output String buffer to store output. + * @param maxlen Maximum size of string buffer. + */ +stock void json_cell_string(const char[] input, char[] output, int maxlen) { + strcopy(output, maxlen, "?"); // add dummy char at start so we can replace it with a quote later + StrCat(output, maxlen, input); // add input string to output + json_escape_string(output, maxlen); // run basic escapes on the string + + // surround string with quotations + output[0] = '"'; + StrCat(output, maxlen, "\""); +} + +/** + * Generates the JSON cell representation of an int. + * + * @param input Value to generate output for. + * @param output String buffer to store output. + * @param maxlen Maximum size of string buffer. + */ +stock void json_cell_int(int input, char[] output, int maxlen) { + IntToString(input, output, maxlen); +} + +/** + * Generates the JSON cell representation of a float. + * + * @param input Value to generate output for. + * @param output String buffer to store output. + * @param maxlen Maximum size of string buffer. + */ +stock void json_cell_float(float input, char[] output, int maxlen) { + FloatToString(input, output, maxlen); + // trim trailing 0s from float output + int last_char = strlen(output) - 1; + while (output[last_char] == '0' && output[last_char - 1] != '.') { + output[last_char--] = '\0'; + } +} + +/** + * Generates the JSON cell representation of a bool. + * + * @param input Value to generate output for. + * @param output String buffer to store output. + * @param maxlen Maximum size of string buffer. + */ +stock void json_cell_bool(bool input, char[] output, int maxlen) { + strcopy(output, maxlen, (input) ? "true" : "false"); +} + +/** + * Generates the JSON cell representation of null. + * + * @param output String buffer to store output. + * @param maxlen Maximum size of string buffer. + */ +stock void json_cell_null(char[] output, int maxlen) { + strcopy(output, maxlen, "null"); +} diff --git a/VPN-Check/scripting/include/json/object.inc b/VPN-Check/scripting/include/json/object.inc new file mode 100644 index 00000000..882020c5 --- /dev/null +++ b/VPN-Check/scripting/include/json/object.inc @@ -0,0 +1,779 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * Provides a pure SourcePawn implementation of JSON encoding and decoding. + * https://github.com/clugg/sm-json + * + * sm-json (C)2018 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_object_included + #endinput +#endif +#define _json_object_included + +#include +#include +#include +#include + +methodmap JSON_Object < StringMap { + /** + * Creates a new JSON_Object. + * + * @param is_array Should the object created be an array? [optional, default: false] + * @returns A new JSON_Object. + */ + public JSON_Object(bool is_array = false) { + StringMap obj = CreateTrie(); + if (is_array) { + obj.SetValue(JSON_ARRAY_INDEX_KEY, 0); + } + + return view_as(obj); + } + + /** + * Checks whether the object has a key. + * + * @param key Key to check existence of. + * @returns True if the key exists, false otherwise. + */ + public bool HasKey(const char[] key) { + int dummy_int; + char dummy_str[1]; + + return this.GetValue(key, dummy_int) + || this.GetString(key, dummy_str, sizeof(dummy_str)); + } + + /** + * @section Array helpers. + */ + + /** + * Whether the current object is an array. + */ + property bool IsArray { + public get() { + return this.HasKey(JSON_ARRAY_INDEX_KEY); + } + } + + /** + * The current index of the object if it is an array, or -1 otherwise. + */ + property int CurrentIndex { + public get() { + if (!this.IsArray) { + return -1; + } + + int result; + return (this.GetValue(JSON_ARRAY_INDEX_KEY, result)) ? result : -1; + } + } + + /** + * The number of items in the object if it is an array, + * or the number of keys (including meta-keys) otherwise. + */ + property int Length { + public get() { + StringMapSnapshot snap = this.Snapshot(); + int length = (this.IsArray) ? this.CurrentIndex : snap.Length; + delete snap; + + return length; + } + } + + /** + * Increments the current index of the object. + * + * @returns True on success, false if the current object is not an array. + */ + public bool IncrementIndex() { + int current = this.CurrentIndex; + if (current == -1) { + return false; + } + + return this.SetValue(JSON_ARRAY_INDEX_KEY, current + 1); + } + + /** + * Gets the string representation of an array index. + * + * @param output String buffer to store output. + * @param maxlen Maximum size of string buffer. + * @param key Key to get string for. [optional, default: current index] + * @returns True on success, false otherwise. + */ + public int GetIndexString(char[] output, int maxlen, int key = -1) { + key = (key == -1) ? this.CurrentIndex : key; + if (key == -1) { + return false; + } + + return IntToString(key, output, maxlen); + } + + /** + * Removes an item from the object by index. + * + * @param index Index of object to remove. + * @returns True on success, false if the current object + * is not an array or the index does not exist. + */ + public bool RemoveIndexed(int index) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return false; + } + + return this.Remove(key); + } + + /** + * @section Internal getters. + */ + + /** + * Gets the cell type stored at a key. + * + * @param key Key to get value type for. + * @returns Value type for key provided, or Type_Invalid if it does not exist. + */ + public JSON_CELL_TYPE GetKeyType(const char[] key) { + int maxlen = strlen(key) + strlen(JSON_META_TYPE_KEY) + 1; + char[] type_key = new char[maxlen]; + Format(type_key, maxlen, "%s%s", key, JSON_META_TYPE_KEY); + + JSON_CELL_TYPE type; + return (this.GetValue(type_key, type)) ? type : Type_Invalid; + } + + /** + * Gets the cell type stored at an index. + * + * @param index Index to get value type for. + * @returns Value type for index provided, or Type_Invalid if it does not exist. + */ + public JSON_CELL_TYPE GetKeyTypeIndexed(int index) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return Type_Invalid; + } + + return this.GetKeyType(key); + } + + /** + * Gets the length of the string stored at a key. + * + * @param key Key to get string length for. + * @returns Length of string at key provided, or -1 if it is not a string/does not exist. + */ + public int GetKeyLength(const char[] key) { + int maxlen = strlen(key) + strlen(JSON_META_LENGTH_KEY) + 1; + char[] length_key = new char[maxlen]; + Format(length_key, maxlen, "%s%s", key, JSON_META_LENGTH_KEY); + + int length; + return (this.GetValue(length_key, length)) ? length : -1; + } + + /** + * Gets the length of the string stored at an index. + * + * @param index Index to get string length for. + * @returns Length of string at index provided, or -1 if it is not a string/does not exist. + */ + public int GetKeyLengthIndexed(int index) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return -1; + } + + return this.GetKeyLength(key); + } + + /** + * Gets whether the key should be hidden from encoding. + * + * @param key Key to get hidden state for. + * @returns Whether or not the key should be hidden. + */ + public bool GetKeyHidden(const char[] key) { + int maxlen = strlen(key) + strlen(JSON_META_HIDDEN_KEY) + 1; + char[] length_key = new char[maxlen]; + Format(length_key, maxlen, "%s%s", key, JSON_META_HIDDEN_KEY); + + bool hidden; + return (this.GetValue(length_key, hidden)) ? hidden : false; + } + + /** + * Gets whether the index should be hidden from encoding. + * + * @param index Index to get hidden state for. + * @returns Whether or not the index should be hidden. + */ + public bool GetKeyHiddenIndexed(int index) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return false; + } + + return this.GetKeyHidden(key); + } + + /** + * @section Internal setters. + */ + + /** + * Sets the cell type stored at a key. + * + * @param key Key to set value type for. + * @param type Type to set key to. + * @returns True on success, false otherwise. + */ + public bool SetKeyType(const char[] key, JSON_CELL_TYPE type) { + int maxlen = strlen(key) + strlen(JSON_META_TYPE_KEY) + 1; + char[] type_key = new char[maxlen]; + Format(type_key, maxlen, "%s%s", key, JSON_META_TYPE_KEY); + + return this.SetValue(type_key, type); + } + + /** + * Sets the cell type stored at an index. + * + * @param index Index to set value type for. + * @param type Type to set index to. + * @returns True on success, false otherwise. + */ + public bool SetKeyTypeIndexed(int index, JSON_CELL_TYPE value) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return false; + } + + return this.SetKeyType(key, value); + } + + /** + * Sets the length of the string stored at a key. + * + * @param key Key to set string length for. + * @param length Length to set string to. + * @returns True on success, false otherwise. + */ + public bool SetKeyLength(const char[] key, int length) { + int maxlen = strlen(key) + strlen(JSON_META_LENGTH_KEY) + 1; + char[] length_key = new char[maxlen]; + Format(length_key, maxlen, "%s%s", key, JSON_META_LENGTH_KEY); + + return this.SetValue(length_key, length); + } + + /** + * Sets the length of the string stored at an index. + * + * @param index Index to set string length for. + * @param length Length to set string to. + * @returns True on success, false otherwise. + */ + public bool SetKeyLengthIndexed(int index, int length) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return false; + } + + return this.SetKeyLength(key, length); + } + + /** + * Sets whether the key should be hidden from encoding. + * + * @param key Key to set hidden state for. + * @param hidden Wheter or not the key should be hidden. + * @returns True on success, false otherwise. + */ + public bool SetKeyHidden(const char[] key, bool hidden) { + int maxlen = strlen(key) + strlen(JSON_META_HIDDEN_KEY) + 1; + char[] hidden_key = new char[maxlen]; + Format(hidden_key, maxlen, "%s%s", key, JSON_META_HIDDEN_KEY); + + return this.SetValue(hidden_key, hidden); + } + + /** + * Sets whether the index should be hidden from encoding. + * + * @param index Index to set hidden state for. + * @param hidden Wheter or not the index should be hidden. + * @returns True on success, false otherwise. + */ + public bool SetKeyHiddenIndexed(int index, bool hidden) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return false; + } + + return this.SetKeyHidden(key, hidden); + } + + /** + * @section Object getters. + */ + + // GetString is implemented natively by StringMap + + /** + * Retrieves the string stored at an index. + * + * @param index Index to retrieve string value for. + * @param value String buffer to store output. + * @param maxlen Maximum size of string buffer. + * @returns True on success. False if the key is not set, or the key is set as a value or array (not a string). + */ + public bool GetStringIndexed(int index, char[] value, int max_size) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return false; + } + + return this.GetString(key, value, max_size); + } + + /** + * Retrieves the int stored at a key. + * + * @param key Key to retrieve int value for. + * @returns Value stored at key. + */ + public int GetInt(const char[] key) { + int value; + return (this.GetValue(key, value)) ? value : -1; + } + + /** + * Retrieves the int stored at an index. + * + * @param index Index to retrieve int value for. + * @returns Value stored at index. + */ + public int GetIntIndexed(int index) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return -1; + } + + return this.GetInt(key); + } + + /** + * Retrieves the float stored at a key. + * + * @param key Key to retrieve float value for. + * @returns Value stored at key. + */ + public float GetFloat(const char[] key) { + float value; + return (this.GetValue(key, value)) ? value : -1.0; + } + + /** + * Retrieves the float stored at an index. + * + * @param index Index to retrieve float value for. + * @returns Value stored at index. + */ + public float GetFloatIndexed(int index) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return -1.0; + } + + return this.GetFloat(key); + } + + /** + * Retrieves the bool stored at a key. + * + * @param key Key to retrieve bool value for. + * @returns Value stored at key. + */ + public bool GetBool(const char[] key) { + bool value; + return (this.GetValue(key, value)) ? value : false; + } + + /** + * Retrieves the bool stored at an index. + * + * @param index Index to retrieve bool value for. + * @returns Value stored at index. + */ + public bool GetBoolIndexed(int index) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return false; + } + + return this.GetBool(key); + } + + /** + * Retrieves the handle stored at a key. + * + * @param key Key to retrieve handle value for. + * @returns Value stored at key. + */ + public Handle GetHandle(const char[] key) { + Handle value; + return (this.GetValue(key, value)) ? value : null; + } + + /** + * Retrieves the handle stored at an index. + * + * @param index Index to retrieve handle value for. + * @returns Value stored at index. + */ + public Handle GetHandleIndexed(int index) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return null; + } + + return this.GetHandle(key); + } + + /** + * Retrieves the object stored at a key. + * + * @param key Key to retrieve object value for. + * @returns Value stored at key. + */ + public JSON_Object GetObject(const char[] key) { + return view_as(this.GetHandle(key)); + } + + /** + * Retrieves the object stored at an index. + * + * @param index Index to retrieve object value for. + * @returns Value stored at index. + */ + public JSON_Object GetObjectIndexed(int index) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return null; + } + + return this.GetObject(key); + } + + /** + * @section Object setters. + */ + + /** + * Sets the string stored at a key. + * + * @param key Key to set to string value. + * @param value Value to set. + * @returns True on success, false otherwise. + */ + public bool SetString(const char[] key, const char[] value, bool replace = true) { + return this.SetString(key, value, replace) + && this.SetKeyType(key, Type_String) + && this.SetKeyLength(key, strlen(value)); + } + + /** + * Sets the string stored at an index. + * + * @param index Index to set to string value. + * @param value Value to set. + * @returns True on success, false otherwise. + */ + public bool SetStringIndexed(int index, const char[] value, bool replace = true) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return false; + } + + return this.SetString(key, value, replace); + } + + /** + * Sets the int stored at a key. + * + * @param key Key to set to int value. + * @param value Value to set. + * @returns True on success, false otherwise. + */ + public bool SetInt(const char[] key, int value, bool replace = true) { + return this.SetValue(key, value, replace) + && this.SetKeyType(key, Type_Int); + } + + /** + * Sets the int stored at an index. + * + * @param index Index to set to int value. + * @param value Value to set. + * @returns True on success, false otherwise. + */ + public bool SetIntIndexed(int index, int value, bool replace = true) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return false; + } + + return this.SetInt(key, value, replace); + } + + /** + * Sets the float stored at a key. + * + * @param key Key to set to float value. + * @param value Value to set. + * @returns True on success, false otherwise. + */ + public bool SetFloat(const char[] key, float value, bool replace = true) { + return this.SetValue(key, value, replace) + && this.SetKeyType(key, Type_Float); + } + + /** + * Sets the float stored at an index. + * + * @param index Index to set to float value. + * @param value Value to set. + * @returns True on success, false otherwise. + */ + public bool SetFloatIndexed(int index, float value, bool replace = true) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return false; + } + + return this.SetFloat(key, value, replace); + } + + /** + * Sets the bool stored at a key. + * + * @param key Key to set to bool value. + * @param value Value to set. + * @returns True on success, false otherwise. + */ + public bool SetBool(const char[] key, bool value, bool replace = true) { + return this.SetValue(key, value, replace) + && this.SetKeyType(key, Type_Bool); + } + + /** + * Sets the bool stored at an index. + * + * @param index Index to set to bool value. + * @param value Value to set. + * @returns True on success, false otherwise. + */ + public bool SetBoolIndexed(int index, bool value, bool replace = true) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return false; + } + + return this.SetBool(key, value, replace); + } + + /** + * Sets the handle stored at a key. + * + * @param key Key to set to handle value. + * @param value Value to set. + * @returns True on success, false otherwise. + */ + public bool SetHandle(const char[] key, Handle value = null, bool replace = true) { + return this.SetValue(key, value, replace) + && this.SetKeyType(key, Type_Null); + } + + /** + * Sets the handle stored at an index. + * + * @param index Index to set to handle value. + * @param value Value to set. + * @returns True on success, false otherwise. + */ + public bool SetHandleIndexed(int index, Handle value = null, bool replace = true) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return false; + } + + return this.SetHandle(key, value, replace); + } + + /** + * Sets the object stored at a key. + * + * @param key Key to set to object value. + * @param value Value to set. + * @returns True on success, false otherwise. + */ + public bool SetObject(const char[] key, JSON_Object value, bool replace = true) { + return this.SetValue(key, value, replace) + && this.SetKeyType(key, Type_Object); + } + + /** + * Sets the object stored at an index. + * + * @param index Index to set to object value. + * @param value Value to set. + * @returns True on success, false otherwise. + */ + public bool SetObjectIndexed(int index, JSON_Object value, bool replace = true) { + char key[JSON_INDEX_BUFFER_SIZE]; + if (!this.GetIndexString(key, sizeof(key), index)) { + return false; + } + + return this.SetObject(key, value, replace); + } + + /** + * @section Array setters. + */ + + /** + * Pushes a string to the end of the array. + * + * @param value Value to push. + * @returns True on success, false otherwise. + */ + public bool PushString(const char[] value) { + return this.SetStringIndexed(this.CurrentIndex, value) + && this.IncrementIndex(); + } + + /** + * Pushes an int to the end of the array. + * + * @param value Value to push. + * @returns True on success, false otherwise. + */ + public bool PushInt(int value) { + return this.SetIntIndexed(this.CurrentIndex, value) + && this.IncrementIndex(); + } + + /** + * Pushes a float to the end of the array. + * + * @param value Value to push. + * @returns True on success, false otherwise. + */ + public bool PushFloat(float value) { + return this.SetFloatIndexed(this.CurrentIndex, value) + && this.IncrementIndex(); + } + + /** + * Pushes a bool to the end of the array. + * + * @param value Value to push. + * @returns True on success, false otherwise. + */ + public bool PushBool(bool value) { + return this.SetBoolIndexed(this.CurrentIndex, value) + && this.IncrementIndex(); + } + + /** + * Pushes a handle to the end of the array. + * + * @param value Value to push. + * @returns True on success, false otherwise. + */ + public bool PushHandle(Handle value = null) { + return this.SetHandleIndexed(this.CurrentIndex, value) + && this.IncrementIndex(); + } + + /** + * Pushes an object to the end of the array. + * + * @param value Value to push. + * @returns True on success, false otherwise. + */ + public bool PushObject(JSON_Object value) { + return this.SetObjectIndexed(this.CurrentIndex, value) + && this.IncrementIndex(); + } + + /** + * @section Generic. + */ + + /** + * Encodes the object into its string representation. + * + * @param output String buffer to store output. + * @param maxlen Maximum size of string buffer. + * @param pretty_print Should the output be pretty printed (newlines and spaces)? [optional, default: false] + * @param depth The current depth of the encoder. [optional, default: 0] + */ + public void Encode(char[] output, int maxlen, bool pretty_print = false, int depth = 0) { + json_encode(this, output, maxlen, pretty_print, depth); + } + + /** + * Decodes a JSON string into this object. + * + * @param buffer Buffer to decode. + */ + public void Decode(const char[] buffer) { + json_decode(buffer, this); + } + + /** + * Recursively cleans up the object and any objects referenced within. + */ + public void Cleanup() { + json_cleanup(this); + } +}; diff --git a/VPN-Check/scripting/include/json/string_helpers.inc b/VPN-Check/scripting/include/json/string_helpers.inc new file mode 100644 index 00000000..c414e5d8 --- /dev/null +++ b/VPN-Check/scripting/include/json/string_helpers.inc @@ -0,0 +1,85 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * Provides a pure SourcePawn implementation of JSON encoding and decoding. + * https://github.com/clugg/sm-json + * + * sm-json (C)2018 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_string_helpers_included + #endinput +#endif +#define _json_string_helpers_included + +/** + * Checks if a string starts with another string. + * + * @param haystack String to check that starts with needle. + * @param maxlen Maximum size of string buffer. + * @param needle String to check that haystack starts with. + * @returns True if haystack begins with needle, false otherwise. + */ +stock bool json_string_startswith(const char[] haystack, const char[] needle) { + int haystack_length = strlen(haystack); + int needle_length = strlen(needle); + if (needle_length > haystack_length) { + return false; + } + + for (int i = 0; i < needle_length; ++i) { + if (haystack[i] != needle[i]) { + return false; + } + } + + return true; +} + +/** + * Checks if a string ends with another string. + * + * @param haystack String to check that ends with needle. + * @param maxlen Maximum size of string buffer. + * @param needle String to check that haystack ends with. + * @returns True if haystack ends with needle, false otherwise. + */ +stock bool json_string_endswith(const char[] haystack, const char[] needle) { + int haystack_length = strlen(haystack); + int needle_length = strlen(needle); + if (needle_length > haystack_length) { + return false; + } + + for (int i = 0; i < needle_length; ++i) { + if (haystack[haystack_length - needle_length + i] != needle[i]) { + return false; + } + } + + return true; +}