diff --git a/Webfront Plugin/Framework.cs b/Webfront Plugin/Framework.cs new file mode 100644 index 000000000..b166b62f8 --- /dev/null +++ b/Webfront Plugin/Framework.cs @@ -0,0 +1,565 @@ +using System; +using System.Collections.Generic; +using SharedLibrary; +using System.Text; +using System.Text.RegularExpressions; +using System.Linq; +using System.Collections.Specialized; + +namespace Webfront_Plugin +{ + class Framework + { + private List activeServers; + + public Framework() + { + activeServers = new List(); + } + + public void addServer(Server S) + { + activeServers.Add(S); + } + + public void removeServer(Server S) + { + if (activeServers.Contains(S)) + activeServers.Remove(S); + } + + private String processTemplate(String Input, String Param) + { + try + { + Server requestedServer = null; + int requestPageNum = 0; + int ID = 0; + String Query = ""; + + if (Param != null) + { + NameValueCollection querySet = System.Web.HttpUtility.ParseQueryString(Param); + + if (querySet["server"] != null) + requestedServer = activeServers.Find(x => x.pID() == Int32.Parse(querySet["server"])); + + if (querySet["page"] != null) + requestPageNum = Int32.Parse(querySet["page"]); + + if (querySet["id"] != null) + ID = Int32.Parse(querySet["id"]); + + if (querySet["query"] != null) + Query = querySet["query"]; + } + + String Pattern = @"\{\{.+\}\}"; + Regex Search = new Regex(Pattern, RegexOptions.IgnoreCase); + + MatchCollection Matches = Search.Matches(Input); + + foreach (Match match in Matches) + { + Input = processReplacements(Input, match.Value, requestPageNum, ID, Query, requestedServer); + } + + return Input; + } + + catch (Exception E) + { + Page Error = new error(); + return Error.Load().Replace("{{ERROR}}", E.Message); + } + } + + private String parsePagination(int totalItems, int itemsPerPage, int currentPage, String Page) + { + StringBuilder output = new StringBuilder(); + + output.Append("
"); + + if (currentPage > 0) + output.AppendFormat("PREV", Page, currentPage - 1); + + double totalPages = Math.Ceiling(((float)totalItems / itemsPerPage)); + + output.Append("" + (currentPage + 1) + "/" + totalPages + ""); + + if ((currentPage + 1) < totalPages) + output.AppendFormat("NEXT", Page, currentPage + 1); + + output.Append("
"); + + return output.ToString(); + } + + private String processReplacements(String Input, String Macro, int curPage, int ID, String Query, params Server[] Servers) + { + if (Macro.Length < 5) + return ""; + + String Looking = Macro.Substring(2, Macro.Length - 4); + + if (Looking == "SERVERS") + { + int cycleFix = 0; + StringBuilder buffer = new StringBuilder(); + + foreach (Server S in activeServers) + { + StringBuilder players = new StringBuilder(); + if (S.getClientNum() < 1) + players.Append("

No Players

"); + else + { + int count = 0; + double currentPlayers = S.statusPlayers.Count; + + foreach (Player P in S.getPlayers()) + { + if (P == null) + continue; + + if (count % 2 == 0) + { + switch (cycleFix) + { + case 0: + players.Append(""); + cycleFix = 1; + break; + case 1: + players.Append(""); + cycleFix = 0; + break; + } + } + + players.AppendFormat("{1}", P.databaseID, SharedLibrary.Utilities.nameHTMLFormatted(P)); + + if (count % 2 != 0) + { + players.Append(""); + } + + count++; + + } + } + buffer.AppendFormat(@" + + + + + + + + + +
{0}{1}{2}{3}StatsBansHistory
+ + {5} +
", + S.getName(), S.getMap(), S.getClientNum() + "/" + S.getMaxClients(), SharedLibrary.Utilities.gametypeLocalized(S.getGametype()), S.pID(), players.ToString()); + buffer.AppendFormat("
", S.pID(), '\"'); + //if (S.getClientNum() > 0) + // buffer.AppendFormat("
", S.pID(), '\"'); + buffer.Append("
"); + } + return Input.Replace(Macro, buffer.ToString()); + } + + if(Looking == "CHAT") + { + StringBuilder chatMessages = new StringBuilder(); + chatMessages.Append(""); + if (Servers.Length > 0 && Servers[0] != null) + { + foreach (Chat Message in Servers[0].chatHistory) + chatMessages.AppendFormat("", SharedLibrary.Utilities.nameHTMLFormatted(Message.Origin), Message.Message, Message.timeString()); + } + + chatMessages.Append("
{0}{1}{2}
"); + return chatMessages.ToString(); + } + + if (Looking == "PLAYER") + { + StringBuilder buffer = new StringBuilder(); + Server S = activeServers[0]; + + buffer.Append(""); + List matchingPlayers = new List(); + + if (ID > 0) + matchingPlayers.Add(S.clientDB.getPlayer(ID)); + + else if (Query.Length > 2) + { + matchingPlayers = S.clientDB.findPlayers(Query); + + if (matchingPlayers == null) + matchingPlayers = new List(); + + List matchedDatabaseIDs = new List(); + + foreach (Aliases matchingAlias in S.aliasDB.findPlayers(Query)) + matchedDatabaseIDs.Add(matchingAlias.Number); + + foreach (Player matchingP in S.clientDB.getPlayers(matchedDatabaseIDs)) + { + if (matchingPlayers.Find(x => x.databaseID == matchingP.databaseID) == null) + matchingPlayers.Add(matchingP); + } + } + + if (matchingPlayers == null) + buffer.Append("
NameAliasesIPRatingLevelConnectionsLast SeenProfile
"); + + else + { + foreach (Player Player in matchingPlayers) + { + if (Player == null) + continue; + + buffer.Append(""); + StringBuilder str = new StringBuilder(); + + List allAlliases = S.getAliases(Player); + List nameAlias = new List(); + + foreach (Aliases A in allAlliases) + { + foreach (String Name in A.Names.Distinct()) + nameAlias.Add(Name); + } + + foreach (String Name in nameAlias.Distinct()) + str.AppendFormat("{0}
", Utilities.stripColors(Name)); + + + StringBuilder IPs = new StringBuilder(); + + if (false) + { + /*foreach (Player a in aliases) + { + foreach (String ip in a.Alias.IPS) + { + if (!IPs.ToString().Contains(ip)) + IPs.AppendFormat("{0}
", ip); + } + }*/ + + } + else + IPs.Append("Hidden"); + + Int64 forumID = 0; + if (Player.npID.Length == 16) + { + forumID = Int64.Parse(Player.npID.Substring(0, 16), System.Globalization.NumberStyles.AllowHexSpecifier); + forumID = forumID - 76561197960265728; + } + + String Screenshot = String.Empty; + + //if (logged) + Screenshot = String.Format("
", forumID, Player.Name); + + buffer.AppendFormat("{0}{10}{1}{2}{3}{4}{5}{6} ago{8}", Player.Name, str, IPs, 0, SharedLibrary.Utilities.levelHTMLFormatted(Player.Level), Player.Connections, Player.getLastConnection(), forumID, Player.Name, "/player?id=" + Player.databaseID, Screenshot); + buffer.Append(""); + } + + buffer.Append(""); + return Input.Replace(Macro, buffer.ToString()); + } + } + + if (Looking == "BANS") + { + StringBuilder buffer = new StringBuilder(); + Server S = activeServers[0]; + + buffer.Append(""); + int limitPerPage = 30; + int Pagination = curPage; + int totalBans = S.Bans.Count; + int range; + int start = Pagination * limitPerPage; + int cycleFix = 0; + + if (totalBans <= limitPerPage) + range = totalBans - 1; + else if ((totalBans - start) < limitPerPage) + range = (totalBans - start); + else + range = limitPerPage; + + List Bans = new List(); + + if (totalBans > 0) + Bans = S.Bans.GetRange(start, range).OrderByDescending(x => x.When).ToList(); + + if (Bans.Count == 0) + buffer.Append("No bans yet."); + + else + { + buffer.Append("

{{TIME}}


"); + + if (Bans[0] != null) + buffer = buffer.Replace("{{TIME}}", "From " + SharedLibrary.Utilities.timePassed(Bans[0].When) + " ago" + " — " + totalBans + " total"); + + List npIDs = new List(); + + foreach (Ban B in Bans) + npIDs.Add(B.npID); + + + Player[] bannedPlayers = S.clientDB.getPlayers(npIDs).ToArray(); + + for (int i = 0; i < Bans.Count-1; i++) + { + if (Bans[i] == null) + continue; + + Player P = bannedPlayers[i]; + Player B; + + if (Bans[i].bannedByID == Bans[i].npID) + B = new Player("IW4MAdmin", "", 0, SharedLibrary.Player.Permission.Banned, 0, "", 0, ""); + + else + B = S.clientDB.getPlayer(Bans[i].bannedByID, -1); + + if (P == null) + P = new Player("Unknown", "n/a", 0, 0, 0, "Unknown", 0, ""); + if (B == null) + B = new Player("Unknown", "n/a", 0, 0, 0, "Unknown", 0, ""); + + if (P.lastOffense == String.Empty) + P.lastOffense = "Evade"; + + if (P != null && B != null) + { + + String Prefix; + if (cycleFix % 2 == 0) + Prefix = "class=row-grey"; + else + Prefix = "class=row-white"; + + String Link = "/player?id=" + P.databaseID; + buffer.AppendFormat("", P.Name, P.lastOffense, SharedLibrary.Utilities.nameHTMLFormatted(B), Bans[i].getWhen(), Prefix, Link); + cycleFix++; + } + } + } + buffer.Append("
NameOffenseBanned ByTime
{0}{1}{2}{3}

"); + + if (totalBans > limitPerPage) + buffer.Append(parsePagination(totalBans, limitPerPage, Pagination, "bans")); + + return Input.Replace(Macro, buffer.ToString()); + } + + if (Looking == "TITLE") + return Input.Replace(Macro, "IW4MAdmin by RaidMax"); + + if (Looking == "VERSION") + return Input.Replace(Macro, "0.9.5"); + + return "PLACEHOLDER"; + + } + + public String processRequest(Kayak.Http.HttpRequestHead request) + { + Page requestedPage = new notfound(); + Page Header = new header(); + Page Footer = new footer(); + + if (request.Path == "/") + requestedPage = new main(); + + else + { + string p = request.Path.ToLower().Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)[0]; + switch (p) + { + case "bans": + requestedPage = new bans(); + break; + case "player": + requestedPage = new player(); + break; + case "graph": + requestedPage = new graph(); + break; + case "stats": + requestedPage = new stats(); + break; + case "chat": + requestedPage = new chat(); + return processTemplate(requestedPage.Load(), request.QueryString); + case "error": + requestedPage = new error(); + break; + default: + requestedPage = new notfound(); + break; + } + } + + return processTemplate(Header.Load(), null) + processTemplate(requestedPage.Load(), request.QueryString) + processTemplate(Footer.Load(), null); + } + } + + abstract class Page + { + public abstract String Load(); + public abstract String Name { get; } + + protected String loadHTML() + { + IFile HTML = new IFile("webfront\\" + this.Name + ".html"); + String Contents = HTML.getLines(); + HTML.Close(); + return Contents; + } + } + + class notfound : Page + { + public override String Name + { + get { return "notfound"; } + } + + public override String Load() + { + return loadHTML(); + } + } + + class main : Page + { + public override String Name + { + get { return "main"; } + } + + public override String Load() + { + return loadHTML(); + } + } + + class bans : Page + { + public override String Name + { + get { return "bans"; } + } + + public override String Load() + { + return loadHTML(); + } + } + + class header : Page + { + public override String Name + { + get { return "header"; } + } + + public override String Load() + { + return loadHTML(); + } + } + + class footer : Page + { + public override String Name + { + get { return "footer"; } + } + + public override String Load() + { + return loadHTML(); + } + } + + class player : Page + { + public override String Name + { + get { return "player"; } + } + + public override String Load() + { + return loadHTML(); + } + } + + class stats : Page + { + public override String Name + { + get { return "stats"; } + } + + public override String Load() + { + return loadHTML(); + } + } + + class graph : Page + { + public override String Name + { + get { return "graph"; } + } + + public override String Load() + { + return loadHTML(); + } + } + + class chat : Page + { + public override String Name + { + get { return "chat"; } + } + + public override String Load() + { + return "{{CHAT}}"; + } + } + + class error : Page + { + public override String Name + { + get { return "error"; } + } + + public override String Load() + { + return loadHTML(); + } + } +} diff --git a/Webfront Plugin/Main.cs b/Webfront Plugin/Main.cs new file mode 100644 index 000000000..5d57812cf --- /dev/null +++ b/Webfront Plugin/Main.cs @@ -0,0 +1,37 @@ +using System; +using SharedLibrary; +using System.Threading; + +namespace Webfront_Plugin +{ + public class Webfront : Notify + { + private static Manager webManager; + + public override void onEvent(Event E) + { + if (webManager != null) + { + if (E.Type == Event.GType.Start) + { + Manager.webFront.addServer(E.Owner); + E.Owner.Log.Write("Webfront now has access to server on port " + E.Owner.getPort(), Log.Level.Production); + } + if (E.Type == Event.GType.Stop) + { + Manager.webFront.removeServer(E.Owner); + E.Owner.Log.Write("Webfront has lost access to server on port " + E.Owner.getPort(), Log.Level.Production); + } + } + } + + public override void onLoad() + { + webManager = new Manager(); + Thread webManagerThread = new Thread(new ThreadStart(webManager.Init)); + webManagerThread.Name = "Webfront"; + + webManagerThread.Start(); + } + } +} diff --git a/Webfront Plugin/Manager.cs b/Webfront Plugin/Manager.cs new file mode 100644 index 000000000..159002091 --- /dev/null +++ b/Webfront Plugin/Manager.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Kayak; +using Kayak.Http; +using System.Net; + +namespace Webfront_Plugin +{ + class Manager + { + public IScheduler webScheduler { get; private set; } + public static Framework webFront { get; private set; } + + public Manager() + { + + } + + public void Init() + { + webScheduler = KayakScheduler.Factory.Create(new SchedulerDelegate()); + var server = KayakServer.Factory.CreateHttp(new RequestDelegate(), webScheduler); + webFront = new Framework(); + + using (server.Listen(new IPEndPoint(IPAddress.Any, 1624))) + webScheduler.Start(); + } + } + + class SchedulerDelegate : ISchedulerDelegate + { + public void OnException(IScheduler scheduler, Exception e) + { + + } + + public void OnStop(IScheduler scheduler) + { + + } + } + + class RequestDelegate : IHttpRequestDelegate + { + public void OnRequest(HttpRequestHead request, IDataProducer requestBody, IHttpResponseDelegate response) + { + /*if (request.Method.ToUpperInvariant() == "POST" && request.Uri.StartsWith("/bufferedecho")) + { + // when you subecribe to the request body before calling OnResponse, + // the server will automatically send 100-continue if the client is + // expecting it. + requestBody.Connect(new BufferedConsumer(bufferedBody => + { + var headers = new HttpResponseHead() + { + Status = "200 OK", + Headers = new Dictionary() + { + { "Content-Type", "text/plain" }, + { "Content-Length", request.Headers["Content-Length"] }, + { "Connection", "close" } + } + }; + response.OnResponse(headers, new BufferedProducer(bufferedBody)); + }, error => + { + // XXX + // uh oh, what happens? + })); + } + else if (request.Method.ToUpperInvariant() == "POST" && request.Uri.StartsWith("/echo")) + { + var headers = new HttpResponseHead() + { + Status = "200 OK", + Headers = new Dictionary() + { + { "Content-Type", "text/plain" }, + { "Connection", "close" } + } + }; + if (request.Headers.ContainsKey("Content-Length")) + headers.Headers["Content-Length"] = request.Headers["Content-Length"]; + + // if you call OnResponse before subscribing to the request body, + // 100-continue will not be sent before the response is sent. + // per rfc2616 this response must have a 'final' status code, + // but the server does not enforce it. + response.OnResponse(headers, requestBody); + }*/ + + + string body = Manager.webFront.processRequest(request); + var headers = new HttpResponseHead() + { + Status = "200 OK", + Headers = new Dictionary() + { + { "Content-Type", "text/html" }, + { "Content-Length", body.Length.ToString() }, + } + }; + + response.OnResponse(headers, new BufferedProducer(body)); + + /* + if (request.Uri.StartsWith("/")) + { + var body = string.Format( + "Hello world.\r\nHello.\r\n\r\nUri: {0}\r\nPath: {1}\r\nQuery:{2}\r\nFragment: {3}\r\n", + request.Uri, + request.Path, + request.QueryString, + request.Fragment); + + var headers = new HttpResponseHead() + { + Status = "200 OK", + Headers = new Dictionary() + { + { "Content-Type", "text/plain" }, + { "Content-Length", body.Length.ToString() }, + } + }; + response.OnResponse(headers, new BufferedProducer(body)); + } + else + { + var responseBody = "The resource you requested ('" + request.Uri + "') could not be found."; + var headers = new HttpResponseHead() + { + Status = "404 Not Found", + Headers = new Dictionary() + { + { "Content-Type", "text/plain" }, + { "Content-Length", responseBody.Length.ToString() } + } + }; + var body = new BufferedProducer(responseBody); + + response.OnResponse(headers, body); + }*/ + } + } + + class BufferedProducer : IDataProducer + { + ArraySegment data; + + public BufferedProducer(string data) : this(data, Encoding.UTF8) { } + public BufferedProducer(string data, Encoding encoding) : this(encoding.GetBytes(data)) { } + public BufferedProducer(byte[] data) : this(new ArraySegment(data)) { } + public BufferedProducer(ArraySegment data) + { + this.data = data; + } + + public IDisposable Connect(IDataConsumer channel) + { + channel.OnData(data, null); + channel.OnEnd(); + return null; + } + } + + class BufferedConsumer : IDataConsumer + { + List> buffer = new List>(); + Action resultCallback; + Action errorCallback; + + public BufferedConsumer(Action resultCallback, Action errorCallback) + { + this.resultCallback = resultCallback; + this.errorCallback = errorCallback; + } + + public bool OnData(ArraySegment data, Action continuation) + { + buffer.Add(data); + return false; + } + + public void OnError(Exception error) + { + errorCallback(error); + } + + public void OnEnd() + { + var str = buffer + .Select(b => Encoding.UTF8.GetString(b.Array, b.Offset, b.Count)) + .Aggregate((result, next) => result + next); + + resultCallback(str); + } + } +} diff --git a/Webfront Plugin/Webfront Plugin.csproj b/Webfront Plugin/Webfront Plugin.csproj new file mode 100644 index 000000000..524085258 --- /dev/null +++ b/Webfront Plugin/Webfront Plugin.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {99E36EBD-1FA1-494C-8A66-BECE64EFF378} + Library + Properties + Webfront_Plugin + Webfront Plugin + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\Admin\bin\Release\lib\Kayak.dll + + + ..\SharedLibary\bin\Release\SharedLibary.dll + + + + + + + + + + + + + + + + + + copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)Admin\plugins\$(TargetName).dll" + + + \ No newline at end of file