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
Line 
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Net.Sockets;
5using Cookie = System.Net.Cookie;
6using HttpStatusCode = System.Net.HttpStatusCode;
7using IPEndPoint = System.Net.IPEndPoint;
8using System.Text;
9using System.Threading;
10using AllocsFixes.FileCache;
11using AllocsFixes.NetConnections.Servers.Web.Handlers;
12using AllocsFixes.NetConnections.Servers.Web.SSE;
13using SpaceWizards.HttpListener;
14using UnityEngine;
15
16namespace AllocsFixes.NetConnections.Servers.Web {
17 public class Web : IConsoleServer {
18 private const int guestPermissionLevel = 2000;
19 private const string indexPagePath = "/app";
20
21 public static int handlingCount;
22 public static int currentHandlers;
23 public static long totalHandlingTime = 0;
24 private readonly List<AbsHandler> handlers = new List<AbsHandler> ();
25 private readonly ConnectionHandler connectionHandler;
26
27 private readonly HttpListener listener = new HttpListener ();
28 private readonly Version httpProtocolVersion = new Version(1, 1);
29
30 public Web (string _modInstancePath) {
31 try {
32 int webPort = GamePrefs.GetInt (EnumUtils.Parse<EnumGamePrefs> ("ControlPanelPort"));
33 if (webPort < 1 || webPort > 65533) {
34 Log.Out ("Webserver not started (ControlPanelPort not within 1-65533)");
35 return;
36 }
37
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)");
43 return;
44 }
45
46 // TODO: Read from config
47 bool useStaticCache = false;
48
49 string webfilesFolder = _modInstancePath + "/webserver";
50 string webfilesFolderLegacy = _modInstancePath + "/weblegacy";
51
52 connectionHandler = new ConnectionHandler ();
53
54 RegisterPathHandler ("/", new RewriteHandler ("/files/"));
55
56 // React virtual routing
57 RegisterPathHandler ("/app", new RewriteHandler ("/files/index.html", true));
58
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));
67 RegisterPathHandler ("/userstatus", new UserStatusHandler ());
68 RegisterPathHandler ("/files/", new StaticHandler (
69 webfilesFolder,
70 useStaticCache ? (AbstractCache) new SimpleCache () : new DirectAccess (),
71 false)
72 );
73 RegisterPathHandler ("/itemicons/", new ItemIconHandler (true));
74 RegisterPathHandler ("/map/", new StaticHandler (
75 GameIO.GetSaveGameDir () + "/map",
76 MapRendering.MapRendering.GetTileCache (),
77 false,
78 "web.map")
79 );
80 RegisterPathHandler ("/api/", new ApiHandler ());
81 RegisterPathHandler ("/sse/", new SseHandler ());
82
83 listener.Prefixes.Add ($"http://+:{webPort}/");
84 // listener.Prefixes.Add ($"http://[::1]:{webPort}/");
85 listener.Start ();
86 listener.BeginGetContext (HandleRequest, listener);
87
88 SdtdConsole.Instance.RegisterServer (this);
89
90 Log.Out ("Started Webserver on " + webPort);
91 } catch (Exception e) {
92 Log.Error ("Error in Web.ctor: ");
93 Log.Exception (e);
94 }
95 }
96
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 }
103 }
104
105 handlers.Add (_handler);
106 _handler.SetBasePathAndParent (this, _urlBasePath);
107 }
108
109 public void Disconnect () {
110 try {
111 listener.Stop ();
112 listener.Close ();
113 } catch (Exception e) {
114 Log.Out ("Error in Web.Disconnect: " + e);
115 }
116 }
117
118 public void Shutdown () {
119 foreach (AbsHandler handler in handlers) {
120 handler.Shutdown ();
121 }
122 }
123
124 public void SendLine (string _line) {
125 connectionHandler.SendLine (_line);
126 }
127
128 public void SendLog (string _formattedMessage, string _plainMessage, string _trace, LogType _type, DateTime _timestamp, long _uptime) {
129 // Do nothing, handled by LogBuffer internally
130 }
131
132 public static bool IsSslRedirected (HttpListenerRequest _req) {
133 string proto = _req.Headers ["X-Forwarded-Proto"];
134 return !string.IsNullOrEmpty (proto) && proto.Equals ("https", StringComparison.OrdinalIgnoreCase);
135 }
136
137#if ENABLE_PROFILER
138 private readonly UnityEngine.Profiling.CustomSampler authSampler = UnityEngine.Profiling.CustomSampler.Create ("Auth");
139 private readonly UnityEngine.Profiling.CustomSampler handlerSampler = UnityEngine.Profiling.CustomSampler.Create ("Handler");
140#endif
141
142 private void HandleRequest (IAsyncResult _result) {
143 HttpListener listenerInstance = (HttpListener)_result.AsyncState;
144 if (!listenerInstance.IsListening) {
145 return;
146 }
147
148 Interlocked.Increment (ref handlingCount);
149 Interlocked.Increment (ref currentHandlers);
150
151#if ENABLE_PROFILER
152 UnityEngine.Profiling.Profiler.BeginThreadProfiling ("AllocsMods", "WebRequest");
153 HttpListenerContext ctx = listenerInstance.EndGetContext (_result);
154 try {
155#else
156 HttpListenerContext ctx = listenerInstance.EndGetContext (_result);
157 listenerInstance.BeginGetContext (HandleRequest, listenerInstance);
158#endif
159 try {
160 HttpListenerRequest request = ctx.Request;
161 HttpListenerResponse response = ctx.Response;
162 response.SendChunked = false;
163
164 response.ProtocolVersion = httpProtocolVersion;
165
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
177#if ENABLE_PROFILER
178 authSampler.Begin ();
179#endif
180 int permissionLevel = DoAuthentication (request, out WebConnection conn);
181#if ENABLE_PROFILER
182 authSampler.End ();
183#endif
184
185 //Log.Out ("Login status: conn!=null: {0}, permissionlevel: {1}", conn != null, permissionLevel);
186
187 if (conn != null) {
188 Cookie cookie = new Cookie ("sid", conn.SessionID, "/") {
189 Expired = false,
190 Expires = DateTime.MinValue,
191 HttpOnly = true,
192 Secure = false
193 };
194 response.AppendCookie (cookie);
195 }
196
197 string requestPath = request.Url.AbsolutePath;
198
199 if (requestPath.Length < 2) {
200 response.Redirect (indexPagePath);
201 return;
202 }
203
204 ApplyPathHandler (requestPath, request, response, conn, permissionLevel);
205
206 } catch (IOException e) {
207 if (e.InnerException is SocketException) {
208 Log.Out ("Error in Web.HandleRequest(): Remote host closed connection: " + e.InnerException.Message);
209 } else {
210 Log.Out ("Error (IO) in Web.HandleRequest(): " + e);
211 }
212 } catch (Exception e) {
213 Log.Error ("Error in Web.HandleRequest(): ");
214 Log.Exception (e);
215 } finally {
216 if (!ctx.Response.SendChunked) {
217 ctx.Response.Close ();
218 }
219 Interlocked.Decrement (ref currentHandlers);
220 }
221#if ENABLE_PROFILER
222 } finally {
223 listenerInstance.BeginGetContext (HandleRequest, listenerInstance);
224 UnityEngine.Profiling.Profiler.EndThreadProfiling ();
225 }
226#endif
227 }
228
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
259 private int DoAuthentication (HttpListenerRequest _req, out WebConnection _con) {
260 _con = null;
261
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;
268 }
269
270 if (!string.IsNullOrEmpty (sessionId)) {
271 _con = connectionHandler.IsLoggedIn (sessionId, reqRemoteEndPoint.Address);
272 if (_con != null) {
273 return GameManager.Instance.adminTools.GetUserPermissionLevel (_con.UserId);
274 }
275 }
276
277 string remoteEndpointString = reqRemoteEndPoint.ToString ();
278
279 if (_req.QueryString ["adminuser"] != null && _req.QueryString ["admintoken"] != null) {
280 WebPermissions.AdminToken admin = WebPermissions.Instance.GetWebAdmin (_req.QueryString ["adminuser"],
281 _req.QueryString ["admintoken"]);
282 if (admin != null) {
283 return admin.permissionLevel;
284 }
285
286 Log.Warning ("Invalid Admintoken used from " + remoteEndpointString);
287 }
288
289 return guestPermissionLevel;
290 }
291
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);
298 }
299 }
300}
Note: See TracBrowser for help on using the repository browser.