From 3bbfe03789ad7cd80f7cd3055a96d236a1196084 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sat, 28 Nov 2020 22:27:20 +1100 Subject: [PATCH] Help in GUI should also use Chat.Format and should still show ampersands that are not followed by a valid colour code --- GUI/PropertyWindow/PropertyWindow.cs | 3 +- MCGalaxy/Chat/Colors.cs | 16 +- MCGalaxy/Network/Sockets.cs | 22 ++- MCGalaxy/Network/WebSocketProtocol.cs | 217 ++++++++++++++++++++++++++ 4 files changed, 236 insertions(+), 22 deletions(-) create mode 100644 MCGalaxy/Network/WebSocketProtocol.cs diff --git a/GUI/PropertyWindow/PropertyWindow.cs b/GUI/PropertyWindow/PropertyWindow.cs index 2c5f29447..683c51647 100644 --- a/GUI/PropertyWindow/PropertyWindow.cs +++ b/GUI/PropertyWindow/PropertyWindow.cs @@ -112,7 +112,7 @@ namespace MCGalaxy.Gui { void GetHelp(string toHelp) { ConsoleHelpPlayer p = new ConsoleHelpPlayer(); Command.Find("Help").Use(p, toHelp); - Popup.Message(Colors.Strip(p.Messages), "Help for /" + toHelp); + Popup.Message(Colors.StripUsed(p.Messages), "Help for /" + toHelp); } } @@ -125,6 +125,7 @@ namespace MCGalaxy.Gui { } public override void Message(byte type, string message) { + message = Chat.Format(message, this); Messages += message + "\r\n"; } } diff --git a/MCGalaxy/Chat/Colors.cs b/MCGalaxy/Chat/Colors.cs index 3a02283bc..1bcf282f0 100644 --- a/MCGalaxy/Chat/Colors.cs +++ b/MCGalaxy/Chat/Colors.cs @@ -245,8 +245,8 @@ namespace MCGalaxy { } } - /// Removes all instances of % or & and the character that follows. - /// Does *not* check if the character pair makes a real color + /// Removes all occurrences of % or & and the following character. + /// Does NOT check if the following character is actually a valid color code. public static string Strip(string value) { if (value.IndexOf('%') == -1 && value.IndexOf('&') == -1) return value; char[] output = new char[value.Length]; @@ -255,7 +255,7 @@ namespace MCGalaxy { for (int i = 0; i < value.Length; i++) { char token = value[i]; if (token == '%' || token == '&') { - i++; // Skip over the following color code. + i++; // Skip over the following color code } else { output[usedChars++] = token; } @@ -263,8 +263,7 @@ namespace MCGalaxy { return new string(output, 0, usedChars); } - /// Removes all colors that are currently used. - /// This means % or & may be kept if they are not followed a used color code + /// Removes all occurrences of % and & that are followed by a used color code. public static string StripUsed(string message) { if (message.IndexOf('%') == -1 && message.IndexOf('&') == -1) return message; char[] output = new char[message.Length]; @@ -272,10 +271,9 @@ namespace MCGalaxy { for (int i = 0; i < message.Length; i++) { char c = message[i]; - if ( (i < message.Length-1) && - (c == '%' || c == '&') && - (IsStandard(message[i+1]) || IsDefined(message[i+1])) ) { - i++; // Skip over the following color code. + if ((c == '%' || c == '&') && (i < message.Length - 1) && + (IsStandard(message[i+1]) || IsDefined(message[i+1]))) { + i++; // Skip over the following color code } else { output[usedChars++] = c; } diff --git a/MCGalaxy/Network/Sockets.cs b/MCGalaxy/Network/Sockets.cs index 892a893ab..aea487652 100644 --- a/MCGalaxy/Network/Sockets.cs +++ b/MCGalaxy/Network/Sockets.cs @@ -27,13 +27,12 @@ namespace MCGalaxy.Network { /// Abstracts sending to/receiving from a network socket. public abstract class INetSocket { - protected INetProtocol protocol; - + protected INetProtocol protocol; /// Whether the socket has been closed/disconnected. public bool Disconnected; - /// The IP address of the remote end (i.e. client) of the socket. - public string IP; + /// Gets the IP address of the remote end (i.e. client) of the socket. + public abstract string IP { get; } /// Sets whether this socket operates in low-latency mode (e.g. for TCP, disabes nagle's algorithm). public abstract bool LowLatency { set; } @@ -129,8 +128,6 @@ namespace MCGalaxy.Network { public TcpSocket(Socket s) { socket = s; - IP = ((IPEndPoint)s.RemoteEndPoint).Address.ToString(); - recvArgs.UserToken = this; recvArgs.SetBuffer(recvBuffer, 0, recvBuffer.Length); sendArgs.UserToken = this; @@ -143,6 +140,9 @@ namespace MCGalaxy.Network { ReceiveNextAsync(); } + public override string IP { + get { return ((IPEndPoint)socket.RemoteEndPoint).Address.ToString(); } + } public override bool LowLatency { set { socket.NoDelay = value; } } @@ -266,13 +266,11 @@ namespace MCGalaxy.Network { public sealed class WebSocket : INetSocket, INetProtocol { readonly INetSocket s; - public WebSocket(INetSocket socket) { - IP = socket.IP; - s = socket; - } + public WebSocket(INetSocket socket) { s = socket; } // Init taken care by underlying socket public override void Init() { } + public override string IP { get { return s.IP; } } public override bool LowLatency { set { s.LowLatency = value; } } bool readingHeaders = true; @@ -501,7 +499,6 @@ namespace MCGalaxy.Network { SslStream ssl; public SecureSocket(INetSocket socket) { - IP = socket.IP; raw = socket; wrapper = new WrapperStream(); @@ -512,7 +509,8 @@ namespace MCGalaxy.Network { } // Init taken care by underlying socket - public override void Init() { } + public override void Init() { } + public override string IP { get { return raw.IP; } } public override bool LowLatency { set { raw.LowLatency = value; } } public override void Close() { raw.Close(); } public void Disconnect() { Close(); } diff --git a/MCGalaxy/Network/WebSocketProtocol.cs b/MCGalaxy/Network/WebSocketProtocol.cs new file mode 100644 index 000000000..1c89d6be1 --- /dev/null +++ b/MCGalaxy/Network/WebSocketProtocol.cs @@ -0,0 +1,217 @@ +/* +Copyright 2010 MCSharp team (Modified for use with MCZall/MCLawl/MCGalaxy) +Dual-licensed under the Educational Community License, Version 2.0 and +the GNU General Public License, Version 3 (the "Licenses"); you may +not use this file except in compliance with the Licenses. You may +obtain a copy of the Licenses at +http://www.opensource.org/licenses/ecl2.php +http://www.gnu.org/licenses/gpl-3.0.html +Unless required by applicable law or agreed to in writing, +software distributed under the Licenses are distributed on an "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +or implied. See the Licenses for the specific language governing +permissions and limitations under the Licenses. + */ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Text; +using System.IO; +using System.Net.Security; +using System.Threading; +using System.Security.Cryptography.X509Certificates; + +namespace MCGalaxy.Network { + + /// Abstracts WebSocket protocol handling. + public abstract class WebSocket2 : INetProtocol { + bool readingHeaders = true; + bool conn, upgrade, version, proto; + string verKey; + + void AcceptConnection() { + const string fmt = + "HTTP/1.1 101 Switching Protocols\r\n" + + "Upgrade: websocket\r\n" + + "Connection: Upgrade\r\n" + + "Sec-WebSocket-Accept: {0}\r\n" + + "Sec-WebSocket-Protocol: ClassiCube\r\n" + + "\r\n"; + + string key = verKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + SHA1 sha = SHA1.Create(); + byte[] raw = sha.ComputeHash(Encoding.ASCII.GetBytes(key)); + + string headers = String.Format(fmt, Convert.ToBase64String(raw)); + SendData(Encoding.ASCII.GetBytes(headers)); + readingHeaders = false; + } + + void ProcessHeader(string raw) { + // end of headers + if (raw.Length == 0) { + if (conn && upgrade && version && proto && verKey != null) { + AcceptConnection(); + } else { + // don't pretend to be a http server (so IP:port isn't marked as one by bots) + Close(); + } + } + + int sep = raw.IndexOf(':'); + if (sep == -1) return; // not a proper header + string key = raw.Substring(0, sep); + string val = raw.Substring(sep + 1).Trim(); + + if (key.CaselessEq("Connection")) { + conn = val.CaselessContains("Upgrade"); + } else if (key.CaselessEq("Upgrade")) { + upgrade = val.CaselessEq("websocket"); + } else if (key.CaselessEq("Sec-WebSocket-Version")) { + version = val.CaselessEq("13"); + } else if (key.CaselessEq("Sec-WebSocket-Key")) { + verKey = val; + } else if (key.CaselessEq("Sec-WebSocket-Protocol")) { + proto = val.CaselessEq("ClassiCube"); + } + } + + int ReadHeaders(byte[] buffer, int bufferLen) { + int i; + for (i = 0; i < bufferLen - 1; ) { + int end = -1; + + // find end of header + for (int j = i; j < bufferLen - 1; j++) { + if (buffer[j] != '\r' || buffer[j + 1] != '\n') continue; + end = j; break; + } + + if (end == -1) break; + string value = Encoding.ASCII.GetString(buffer, i, end - i); + ProcessHeader(value); + i = end + 2; + } + return i; + } + + int state, opcode, frameLen, maskRead, frameRead; + byte[] mask = new byte[4], frame; + + const int state_header1 = 0; + const int state_header2 = 1; + const int state_extLen1 = 2; + const int state_extLen2 = 3; + const int state_mask = 4; + const int state_data = 5; + + void DecodeFrame() { + for (int i = 0; i < frameLen; i++) { + frame[i] ^= mask[i & 3]; + } + + switch (opcode) { + // TODO: reply to ping frames + case 0x00: + case 0x02: + if (frameLen == 0) return; + HandleReceived(frame, frameLen); + break; + case 0x08: + // Connection is getting closed + Disconnect(1000); break; + default: + Disconnect(1003); break; + } + } + + int ProcessData(byte[] data, int offset, int len) { + switch (state) { + case state_header1: + if (offset >= len) break; + opcode = data[offset++] & 0x0F; + state = state_header2; + goto case state_header2; + + case state_header2: + if (offset >= len) break; + int flags = data[offset++] & 0x7F; + + if (flags == 127) { + // unsupported 8 byte extended length + Disconnect(1009); + return len; + } else if (flags == 126) { + // two byte extended length + state = state_extLen1; + goto case state_extLen1; + } else { + // length is inline + frameLen = flags; + state = state_mask; + goto case state_mask; + } + + case state_extLen1: + if (offset >= len) break; + frameLen = data[offset++] << 8; + state = state_extLen2; + goto case state_extLen2; + + case state_extLen2: + if (offset >= len) break; + frameLen |= data[offset++]; + state = state_mask; + goto case state_mask; + + case state_mask: + for (; maskRead < 4; maskRead++) { + if (offset >= len) return offset; + mask[maskRead] = data[offset++]; + } + + state = state_data; + goto case state_data; + + case state_data: + if (frame == null || frameLen > frame.Length) frame = new byte[frameLen]; + int copy = Math.Min(len - offset, frameLen - frameRead); + + Buffer.BlockCopy(data, offset, frame, frameRead, copy); + offset += copy; frameRead += copy; + + if (frameRead == frameLen) { + DecodeFrame(); + maskRead = 0; + frameRead = 0; + state = state_header1; + } + break; + } + return offset; + } + + int INetProtocol.ProcessReceived(byte[] buffer, int bufferLen) { + int offset = 0; + if (readingHeaders) { + offset = ReadHeaders(buffer, bufferLen); + if (readingHeaders) return offset; + } + + while (offset < bufferLen) { + offset = ProcessData(buffer, offset, bufferLen); + } + return offset; + } + + public void Disconnect() { Disconnect(1000); } + + protected abstract void HandleReceived(byte[] data, int len); + + protected abstract void Disconnect(int reason); + + protected abstract void SendData(byte[] data); + } +}