source: binary-improvements2/MapRendering/Web/Web.cs@ 383

Last change on this file since 383 was 383, checked in by alloc, 2 years ago

Fixed a bunch of warnings

File size: 9.4 KB
RevLine 
[230]1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Net.Sockets;
[382]5using Cookie = System.Net.Cookie;
6using HttpStatusCode = System.Net.HttpStatusCode;
7using IPEndPoint = System.Net.IPEndPoint;
[230]8using System.Text;
9using System.Threading;
[325]10using AllocsFixes.FileCache;
11using AllocsFixes.NetConnections.Servers.Web.Handlers;
[367]12using AllocsFixes.NetConnections.Servers.Web.SSE;
[382]13using SpaceWizards.HttpListener;
[230]14using UnityEngine;
15
[325]16namespace AllocsFixes.NetConnections.Servers.Web {
[230]17 public class Web : IConsoleServer {
[382]18 private const int guestPermissionLevel = 2000;
19 private const string indexPagePath = "/app";
20
[325]21 public static int handlingCount;
22 public static int currentHandlers;
23 public static long totalHandlingTime = 0;
[382]24 private readonly List<AbsHandler> handlers = new List<AbsHandler> ();
25 private readonly ConnectionHandler connectionHandler;
26
[367]27 private readonly HttpListener listener = new HttpListener ();
[382]28 private readonly Version httpProtocolVersion = new Version(1, 1);
[230]29
[382]30 public Web (string _modInstancePath) {
[230]31 try {
[351]32 int webPort = GamePrefs.GetInt (EnumUtils.Parse<EnumGamePrefs> ("ControlPanelPort"));
[230]33 if (webPort < 1 || webPort > 65533) {
[244]34 Log.Out ("Webserver not started (ControlPanelPort not within 1-65533)");
[230]35 return;
36 }
[325]37
[382]38 // TODO: Remove once this becomes the default control panel
39 webPort += 2;
40
41 if (!HttpListener.IsSupported) {
42 Log.Out ("Webserver not started (needs Windows XP SP2, Server 2003 or later or Mono)");
[230]43 return;
44 }
45
[244]46 // TODO: Read from config
[367]47 bool useStaticCache = false;
[244]48
[382]49 string webfilesFolder = _modInstancePath + "/webserver";
50 string webfilesFolderLegacy = _modInstancePath + "/weblegacy";
[230]51
[382]52 connectionHandler = new ConnectionHandler ();
53
54 RegisterPathHandler ("/", new RewriteHandler ("/files/"));
[325]55
[382]56 // React virtual routing
57 RegisterPathHandler ("/app", new RewriteHandler ("/files/index.html", true));
[367]58
[382]59 // Legacy web page
60 RegisterPathHandler ("/weblegacy", new StaticHandler (
61 webfilesFolderLegacy,
62 useStaticCache ? (AbstractCache)new SimpleCache () : new DirectAccess (),
63 false)
64 );
65
66 RegisterPathHandler ("/session/", new SessionHandler (webfilesFolder, connectionHandler));
[367]67 RegisterPathHandler ("/userstatus", new UserStatusHandler ());
[382]68 RegisterPathHandler ("/files/", new StaticHandler (
69 webfilesFolder,
[367]70 useStaticCache ? (AbstractCache) new SimpleCache () : new DirectAccess (),
71 false)
[244]72 );
[367]73 RegisterPathHandler ("/itemicons/", new ItemIconHandler (true));
74 RegisterPathHandler ("/map/", new StaticHandler (
[369]75 GameIO.GetSaveGameDir () + "/map",
[230]76 MapRendering.MapRendering.GetTileCache (),
[244]77 false,
78 "web.map")
[230]79 );
[367]80 RegisterPathHandler ("/api/", new ApiHandler ());
81 RegisterPathHandler ("/sse/", new SseHandler ());
[230]82
[382]83 listener.Prefixes.Add ($"http://+:{webPort}/");
84 // listener.Prefixes.Add ($"http://[::1]:{webPort}/");
[367]85 listener.Start ();
[382]86 listener.BeginGetContext (HandleRequest, listener);
[230]87
88 SdtdConsole.Instance.RegisterServer (this);
89
[382]90 Log.Out ("Started Webserver on " + webPort);
[230]91 } catch (Exception e) {
[382]92 Log.Error ("Error in Web.ctor: ");
93 Log.Exception (e);
[230]94 }
95 }
96
[382]97 public void RegisterPathHandler (string _urlBasePath, AbsHandler _handler) {
98 foreach (AbsHandler handler in handlers) {
99 if (handler.UrlBasePath == _urlBasePath) {
100 Log.Error ($"Web: Handler for relative path {_urlBasePath} already registerd.");
101 return;
102 }
[367]103 }
104
[382]105 handlers.Add (_handler);
[367]106 _handler.SetBasePathAndParent (this, _urlBasePath);
107 }
108
[325]109 public void Disconnect () {
110 try {
[367]111 listener.Stop ();
112 listener.Close ();
[325]113 } catch (Exception e) {
114 Log.Out ("Error in Web.Disconnect: " + e);
115 }
116 }
117
[367]118 public void Shutdown () {
[382]119 foreach (AbsHandler handler in handlers) {
120 handler.Shutdown ();
[367]121 }
122 }
123
[351]124 public void SendLine (string _line) {
125 connectionHandler.SendLine (_line);
[325]126 }
127
[369]128 public void SendLog (string _formattedMessage, string _plainMessage, string _trace, LogType _type, DateTime _timestamp, long _uptime) {
[325]129 // Do nothing, handled by LogBuffer internally
130 }
131
[367]132 public static bool IsSslRedirected (HttpListenerRequest _req) {
[351]133 string proto = _req.Headers ["X-Forwarded-Proto"];
[367]134 return !string.IsNullOrEmpty (proto) && proto.Equals ("https", StringComparison.OrdinalIgnoreCase);
[325]135 }
[332]136
137#if ENABLE_PROFILER
[367]138 private readonly UnityEngine.Profiling.CustomSampler authSampler = UnityEngine.Profiling.CustomSampler.Create ("Auth");
139 private readonly UnityEngine.Profiling.CustomSampler handlerSampler = UnityEngine.Profiling.CustomSampler.Create ("Handler");
[332]140#endif
[325]141
[351]142 private void HandleRequest (IAsyncResult _result) {
[382]143 HttpListener listenerInstance = (HttpListener)_result.AsyncState;
144 if (!listenerInstance.IsListening) {
[326]145 return;
146 }
[325]147
[326]148 Interlocked.Increment (ref handlingCount);
149 Interlocked.Increment (ref currentHandlers);
150
[332]151#if ENABLE_PROFILER
[367]152 UnityEngine.Profiling.Profiler.BeginThreadProfiling ("AllocsMods", "WebRequest");
[382]153 HttpListenerContext ctx = listenerInstance.EndGetContext (_result);
[332]154 try {
155#else
[382]156 HttpListenerContext ctx = listenerInstance.EndGetContext (_result);
157 listenerInstance.BeginGetContext (HandleRequest, listenerInstance);
[332]158#endif
[326]159 try {
160 HttpListenerRequest request = ctx.Request;
161 HttpListenerResponse response = ctx.Response;
162 response.SendChunked = false;
[230]163
[382]164 response.ProtocolVersion = httpProtocolVersion;
[230]165
[383]166 // No game yet -> fail request
167 if (GameManager.Instance.World == null) {
168 response.StatusCode = (int) HttpStatusCode.ServiceUnavailable;
169 return;
170 }
171
172 if (request.Url == null) {
173 response.StatusCode = (int) HttpStatusCode.BadRequest;
174 return;
175 }
176
[332]177#if ENABLE_PROFILER
178 authSampler.Begin ();
179#endif
[367]180 int permissionLevel = DoAuthentication (request, out WebConnection conn);
[332]181#if ENABLE_PROFILER
182 authSampler.End ();
183#endif
[244]184
[326]185 //Log.Out ("Login status: conn!=null: {0}, permissionlevel: {1}", conn != null, permissionLevel);
[244]186
[326]187 if (conn != null) {
[367]188 Cookie cookie = new Cookie ("sid", conn.SessionID, "/") {
189 Expired = false,
190 Expires = DateTime.MinValue,
191 HttpOnly = true,
192 Secure = false
193 };
[326]194 response.AppendCookie (cookie);
195 }
[244]196
[382]197 string requestPath = request.Url.AbsolutePath;
198
199 if (requestPath.Length < 2) {
200 response.Redirect (indexPagePath);
[326]201 return;
202 }
[382]203
204 ApplyPathHandler (requestPath, request, response, conn, permissionLevel);
[230]205
[326]206 } catch (IOException e) {
207 if (e.InnerException is SocketException) {
[382]208 Log.Out ("Error in Web.HandleRequest(): Remote host closed connection: " + e.InnerException.Message);
[326]209 } else {
210 Log.Out ("Error (IO) in Web.HandleRequest(): " + e);
211 }
212 } catch (Exception e) {
[360]213 Log.Error ("Error in Web.HandleRequest(): ");
214 Log.Exception (e);
[326]215 } finally {
[382]216 if (!ctx.Response.SendChunked) {
[326]217 ctx.Response.Close ();
218 }
219 Interlocked.Decrement (ref currentHandlers);
[230]220 }
[332]221#if ENABLE_PROFILER
222 } finally {
[382]223 listenerInstance.BeginGetContext (HandleRequest, listenerInstance);
[367]224 UnityEngine.Profiling.Profiler.EndThreadProfiling ();
[332]225 }
226#endif
[230]227 }
228
[382]229 public void ApplyPathHandler (string _requestPath, HttpListenerRequest _req, HttpListenerResponse _resp, WebConnection _con,
230 int _permissionLevel) {
231 for (int i = handlers.Count - 1; i >= 0; i--) {
232 AbsHandler handler = handlers [i];
233
234 if (_requestPath.StartsWith (handler.UrlBasePath)) {
235 if (!handler.IsAuthorizedForHandler (_con, _permissionLevel)) {
236 _resp.StatusCode = (int)HttpStatusCode.Forbidden;
237 if (_con != null) {
238 //Log.Out ("Web.HandleRequest: user '{0}' not allowed to access '{1}'", _con.SteamID, handler.ModuleName);
239 }
240 } else {
241#if ENABLE_PROFILER
242 handlerSampler.Begin ();
243#endif
244 handler.HandleRequest (_requestPath, _req, _resp, _con, _permissionLevel);
245#if ENABLE_PROFILER
246 handlerSampler.End ();
247#endif
248 }
249
250 return;
251 }
252 }
253
254 // Not really relevant for non-debugging purposes:
255 //Log.Out ("Error in Web.HandleRequest(): No handler found for path \"" + _requestPath + "\"");
256 _resp.StatusCode = (int) HttpStatusCode.NotFound;
257 }
258
[244]259 private int DoAuthentication (HttpListenerRequest _req, out WebConnection _con) {
260 _con = null;
261
[382]262 string sessionId = _req.Cookies ["sid"]?.Value;
263
264 IPEndPoint reqRemoteEndPoint = _req.RemoteEndPoint;
265 if (reqRemoteEndPoint == null) {
266 Log.Warning ("No RemoteEndPoint on web request");
267 return guestPermissionLevel;
[230]268 }
[382]269
[244]270 if (!string.IsNullOrEmpty (sessionId)) {
[382]271 _con = connectionHandler.IsLoggedIn (sessionId, reqRemoteEndPoint.Address);
272 if (_con != null) {
[369]273 return GameManager.Instance.adminTools.GetUserPermissionLevel (_con.UserId);
[244]274 }
275 }
276
[382]277 string remoteEndpointString = reqRemoteEndPoint.ToString ();
[367]278
[244]279 if (_req.QueryString ["adminuser"] != null && _req.QueryString ["admintoken"] != null) {
[325]280 WebPermissions.AdminToken admin = WebPermissions.Instance.GetWebAdmin (_req.QueryString ["adminuser"],
281 _req.QueryString ["admintoken"]);
[244]282 if (admin != null) {
283 return admin.permissionLevel;
284 }
[325]285
[367]286 Log.Warning ("Invalid Admintoken used from " + remoteEndpointString);
[244]287 }
288
[382]289 return guestPermissionLevel;
[230]290 }
291
[351]292 public static void SetResponseTextContent (HttpListenerResponse _resp, string _text) {
293 byte[] buf = Encoding.UTF8.GetBytes (_text);
294 _resp.ContentLength64 = buf.Length;
295 _resp.ContentType = "text/html";
296 _resp.ContentEncoding = Encoding.UTF8;
297 _resp.OutputStream.Write (buf, 0, buf.Length);
[230]298 }
299 }
[325]300}
Note: See TracBrowser for help on using the repository browser.