source: binary-improvements/MapRendering/Web/Web.cs@ 410

Last change on this file since 410 was 369, checked in by alloc, 3 years ago

Preparations for A20 release
Changes usage of "SteamID" to "UserID" in console commands
Also changes a bunch of the WebAPI stuff to show / use UserIDs

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