using System.Net; using System.Net.WebSockets; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; namespace CollabVM.Server { // https://github.com/paulbatum/WebSocket-Samples/blob/master/HttpListenerWebSocketEcho/Server/Server.cs public class CollabVMHttpServer { private HttpListener _listener = new(); private Dictionary _servers = new(); private readonly IPEndPoint _serverEndPoint; private readonly Boolean _allowList = false; public CollabVMHttpServer(IPEndPoint endPoint, Boolean allowServerList = false) { this._serverEndPoint = endPoint; this._allowList = allowServerList; } public CollabVMHttpServer(IPAddress address, UInt16 port = 6004, Boolean allowList = false) : this(new(address, port), allowList) { } public CollabVMHttpServer(CollabVMv1_2GuacamoleServer server, IPEndPoint endPoint) { this._serverEndPoint = endPoint; this._addServer("/", server); } public CollabVMHttpServer(CollabVMv1_2GuacamoleServer server, IPAddress address, UInt16 port = 6004) : this(server, new(address, port)) { } private void ListenPath(String path) { String tmp = $"http://{this._serverEndPoint.Address}:{this._serverEndPoint.Port}{path}"; //String tmp = $"http://+:{this._serverEndPoint.Port}/{path}"; Console.Error.WriteLine($"Listen: {tmp}"); this._listener.Prefixes.Add(tmp); } private void _addServer(String path, CollabVMv1_2GuacamoleServer server) { this._servers[path] = server; //this.ListenPath(path); } public void AddServer(String name, CollabVMv1_2GuacamoleServer server) { Regex nameRegex = new Regex(@"^[a-zA-Z0-9_-]+$"); if (this._servers.ContainsKey("/")) { throw new InvalidOperationException("Cannot add servers to a single-server instance"); } if (name.Contains("/")) { throw new ArgumentException("Server name must only include latin letters, numbers, underscores and hyphens", nameof(name)); } this._addServer($"/{name}", server); } public async void Start() { this.ListenPath("/"); this._listener.Start(); while (true) { HttpListenerContext context = await this._listener.GetContextAsync(); Console.Error.WriteLine($"<< {context.Request.HttpMethod} {context.Request.UserHostAddress} {context.Request.UserHostName} {context.Request.RawUrl}"); if (context.Request.RawUrl is not null) { if (context.Request.IsWebSocketRequest) { if (this._servers.TryGetValue(context.Request.RawUrl, out CollabVMv1_2GuacamoleServer? server)) { WebSocketContext wsc; try { wsc = await context.AcceptWebSocketAsync(subProtocol: "guacamole"); } catch (Exception) { context.Response.StatusCode = (Int32)HttpStatusCode.InternalServerError; context.Response.Close(); continue; } this.HandleWebSocket(wsc, server, context.Request.RemoteEndPoint); continue; } else { context.Response.StatusCode = (Int32)HttpStatusCode.NotFound; context.Response.Close(); continue; } } else if (this._allowList && context.Request.RawUrl == "/") { context.Response.StatusCode = (Int32)HttpStatusCode.OK; context.Response.ContentType = "application/json"; context.Response.Close(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(this._servers.Keys.Select((path) => $"ws://{context.Request.UserHostAddress}{path}").ToList())), false); continue; } } context.Response.StatusCode = (Int32)HttpStatusCode.BadRequest; context.Response.Close(); } } private async void HandleWebSocket(WebSocketContext wsc, CollabVMv1_2GuacamoleServer server, IPEndPoint remote) { server.HandleSocket(wsc.WebSocket, remote); } } }