using AllocsFixes.JSON; using AllocsFixes.PersistentData; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text.RegularExpressions; namespace AllocsFixes.NetConnections.Servers.Web.API { public class GetPlayerList : WebAPI { private static Regex numberFilterMatcher = new Regex (@"^(>=|=>|>|<=|=<|<|==|=)?\s*([0-9]+(\.[0-9]*)?)$"); private enum NumberMatchType { Equal, Greater, GreaterEqual, Lesser, LesserEqual } public override void HandleRequest (HttpListenerRequest req, HttpListenerResponse resp, WebConnection user, int permissionLevel) { AdminTools admTools = GameManager.Instance.adminTools; user = user ?? new WebConnection ("", "", 0L); bool bViewAll = WebConnection.CanViewAllPlayers (permissionLevel); // TODO: Sort (and filter?) prior to converting to JSON ... hard as how to get the correct column's data? (i.e. column name matches JSON object field names, not source data) int rowsPerPage = 25; if (req.QueryString ["rowsperpage"] != null) { int.TryParse (req.QueryString ["rowsperpage"], out rowsPerPage); } int page = 0; if (req.QueryString ["page"] != null) { int.TryParse (req.QueryString ["page"], out page); } int firstEntry = page * rowsPerPage; Players playersList = PersistentContainer.Instance.Players; List playerList = new List (); foreach (string sid in playersList.SteamIDs) { Player p = playersList [sid, false]; ulong player_steam_ID = 0L; if (!ulong.TryParse (sid, out player_steam_ID)) player_steam_ID = 0L; if ((player_steam_ID == user.SteamID) || bViewAll) { JSONObject pos = new JSONObject (); pos.Add("x", new JSONNumber (p.LastPosition.x)); pos.Add("y", new JSONNumber (p.LastPosition.y)); pos.Add("z", new JSONNumber (p.LastPosition.z)); JSONObject pJson = new JSONObject (); pJson.Add("steamid", new JSONString (sid)); pJson.Add("entityid", new JSONNumber (p.EntityID)); pJson.Add("ip", new JSONString (p.IP)); pJson.Add("name", new JSONString (p.Name)); pJson.Add("online", new JSONBoolean (p.IsOnline)); pJson.Add("position", pos); pJson.Add ("totalplaytime", new JSONNumber (p.TotalPlayTime)); pJson.Add ("lastonline", new JSONString (p.LastOnline.ToUniversalTime ().ToString ("yyyy-MM-ddTHH:mm:ssZ"))); pJson.Add ("ping", new JSONNumber (p.IsOnline ? p.ClientInfo.ping : -1)); JSONBoolean banned = null; if (admTools != null) { banned = new JSONBoolean (admTools.IsBanned (sid)); } else { banned = new JSONBoolean (false); } pJson.Add ("banned", banned); playerList.Add (pJson); } } IEnumerable list = playerList; foreach (string key in req.QueryString.AllKeys) { if (!string.IsNullOrEmpty (key) && key.StartsWith ("filter[")) { string filterCol = key.Substring (key.IndexOf ('[') + 1); filterCol = filterCol.Substring (0, filterCol.Length - 1); string filterVal = req.QueryString.Get (key).Trim (); list = ExecuteFilter (list, filterCol, filterVal); } } int totalAfterFilter = list.Count (); foreach (string key in req.QueryString.AllKeys) { if (!string.IsNullOrEmpty (key) && key.StartsWith ("sort[")) { string sortCol = key.Substring (key.IndexOf ('[') + 1); sortCol = sortCol.Substring (0, sortCol.Length - 1); string sortVal = req.QueryString.Get (key); list = ExecuteSort (list, sortCol, sortVal == "0"); } } list = list.Skip (firstEntry); list = list.Take (rowsPerPage); JSONArray playersJsResult = new JSONArray (); foreach (JSONObject jsO in list) { playersJsResult.Add (jsO); } JSONObject result = new JSONObject (); result.Add ("total", new JSONNumber (totalAfterFilter)); result.Add ("totalUnfiltered", new JSONNumber (playerList.Count)); result.Add ("firstResult", new JSONNumber (firstEntry)); result.Add ("players", playersJsResult); WriteJSON (resp, result); } private IEnumerable ExecuteFilter (IEnumerable _list, string _filterCol, string _filterVal) { if (_list.Count () == 0) { return _list; } if (_list.First ().ContainsKey (_filterCol)) { Type colType = _list.First () [_filterCol].GetType (); if (colType == typeof(JSONNumber)) { return ExecuteNumberFilter (_list, _filterCol, _filterVal); } else if (colType == typeof(JSONBoolean)) { bool value = _filterVal.Trim ().ToLower () == "true"; return _list.Where (line => (line [_filterCol] as JSONBoolean).GetBool () == value); } else if (colType == typeof(JSONString)) { // regex-match whole ^string$, replace * by .*, ? by .?, + by .+ _filterVal = _filterVal.Replace ("*", ".*").Replace ("?", ".?").Replace ("+", ".+"); _filterVal = "^" + _filterVal + "$"; //Log.Out ("GetPlayerList: Filter on String with Regex '" + _filterVal + "'"); Regex matcher = new Regex (_filterVal, RegexOptions.IgnoreCase); return _list.Where (line => matcher.IsMatch ((line [_filterCol] as JSONString).GetString ())); } } return _list; } private IEnumerable ExecuteNumberFilter (IEnumerable _list, string _filterCol, string _filterVal) { // allow value (exact match), =, ==, >=, >, <=, < Match filterMatch = numberFilterMatcher.Match (_filterVal); if (filterMatch.Success) { double value = StringParsers.ParseDouble (filterMatch.Groups [2].Value); NumberMatchType matchType; double epsilon = value / 100000; switch (filterMatch.Groups [1].Value) { case "": case "=": case "==": matchType = NumberMatchType.Equal; break; case ">": matchType = NumberMatchType.Greater; break; case ">=": case "=>": matchType = NumberMatchType.GreaterEqual; break; case "<": matchType = NumberMatchType.Lesser; break; case "<=": case "=<": matchType = NumberMatchType.LesserEqual; break; default: matchType = NumberMatchType.Equal; break; } return _list.Where (delegate(JSONObject line) { double objVal = (line [_filterCol] as JSONNumber).GetDouble (); switch (matchType) { case NumberMatchType.Greater: return objVal > value; case NumberMatchType.GreaterEqual: return objVal >= value; case NumberMatchType.Lesser: return objVal < value; case NumberMatchType.LesserEqual: return objVal <= value; case NumberMatchType.Equal: default: return NearlyEqual (objVal, value, epsilon); } }); } else { Log.Out ("GetPlayerList: ignoring invalid filter for number-column '{0}': '{1}'", _filterCol, _filterVal); } return _list; } private IEnumerable ExecuteSort (IEnumerable _list, string _sortCol, bool _ascending) { if (_list.Count () == 0) { return _list; } if (_list.First ().ContainsKey (_sortCol)) { Type colType = _list.First () [_sortCol].GetType (); if (colType == typeof(JSONNumber)) { if (_ascending) { return _list.OrderBy (line => (line [_sortCol] as JSONNumber).GetDouble ()); } else { return _list.OrderByDescending (line => (line [_sortCol] as JSONNumber).GetDouble ()); } } else if (colType == typeof(JSONBoolean)) { if (_ascending) { return _list.OrderBy (line => (line [_sortCol] as JSONBoolean).GetBool ()); } else { return _list.OrderByDescending (line => (line [_sortCol] as JSONBoolean).GetBool ()); } } else { if (_ascending) { return _list.OrderBy (line => line [_sortCol].ToString ()); } else { return _list.OrderByDescending (line => line [_sortCol].ToString ()); } } } return _list; } private bool NearlyEqual(double a, double b, double epsilon) { double absA = Math.Abs(a); double absB = Math.Abs(b); double diff = Math.Abs(a - b); if (a == b) { // shortcut, handles infinities return true; } else if (a == 0 || b == 0 || diff < Double.Epsilon) { // a or b is zero or both are extremely close to it // relative error is less meaningful here return diff < epsilon; } else { // use relative error return diff / (absA + absB) < epsilon; } } } }