1 /*  Copyright (C) 2013, 2015, 2018, 2020  Vladimir Panteleev <vladimir@thecybershadow.net>
2  *
3  *  This program is free software: you can redistribute it and/or modify
4  *  it under the terms of the GNU Affero General Public License as
5  *  published by the Free Software Foundation, either version 3 of the
6  *  License, or (at your option) any later version.
7  *
8  *  This program is distributed in the hope that it will be useful,
9  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  *  GNU Affero General Public License for more details.
12  *
13  *  You should have received a copy of the GNU Affero General Public License
14  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 module http;
18 
19 import core.time;
20 
21 import std.algorithm;
22 import std.array;
23 import std.datetime : SysTime, Clock;
24 import std.exception;
25 import std.conv;
26 import std.file;
27 import std.string;
28 
29 import ae.net.http.server;
30 import ae.net.http.responseex;
31 import ae.utils.array;
32 
33 import common;
34 
35 alias std..string.indexOf indexOf;
36 
37 class WormNETHttpServer
38 {
39 	HttpServer server;
40 
41 	this()
42 	{
43 		server = new HttpServer;
44 		server.handleRequest = &onRequest;
45 	}
46 
47 	struct Game
48 	{
49 		int id;
50 		string name;
51 		string host;
52 		string address;
53 		string password;
54 		string channel;
55 		string location;
56 		string type;
57 		SysTime created;
58 	}
59 	Game[] games;
60 	int gameCounter;
61 
62 	enum gameTimeout = 5.minutes;
63 
64 	void onRequest(HttpRequest request, HttpServerConnection conn)
65 	{
66 		auto response = new HttpResponseEx();
67 		auto now = Clock.currTime();
68 
69 		try
70 		{
71 			auto pathStr = request.resource;
72 			enforce(pathStr.startsWith('/'), "Invalid path");
73 			UrlParameters parameters;
74 			if (pathStr.indexOf('?') >= 0)
75 			{
76 				auto p = pathStr.indexOf('?');
77 				parameters = decodeUrlParameters(pathStr[p+1..$]);
78 				pathStr = pathStr[0..p];
79 			}
80 			auto path = pathStr[1..$].split("/");
81 
82 			if (path.length == 2 && path[0] == "wormageddonweb")
83 			{
84 				string html = "<NOTHING>";
85 				switch (path[1])
86 				{
87 					case "Login.asp":
88 						html = "<CONNECT %s%s>".format(
89 							configuration.irc.IP ? configuration.irc.IP : conn.tcp.localAddress.toAddrString,
90 							configuration.http.connectPort ? ":" ~ configuration.http.connectPort : "",
91 						);
92 						if (configuration.http.newsFileName.exists)
93 							html ~= "\r\n<MOTD>\r\n%s\r\n</MOTD>".format(configuration.http.newsFileName.readText);
94 						break;
95 					case "RequestChannelScheme.asp":
96 						html = "<SCHEME=%s>".format(configuration.channels.aaGet(parameters.aaGet("Channel")).scheme);
97 						break;
98 					case "Game.asp":
99 						switch (parameters.aaGet("Cmd"))
100 						{
101 							case "Create":
102 								gameCounter++;
103 								games ~= Game(
104 									gameCounter,
105 									parameters.aaGet("Name")[0..min($, 29)],
106 									parameters.aaGet("Nick"),
107 									parameters.aaGet("HostIP"),
108 									parameters.get("Pwd", null),
109 									parameters.aaGet("Chan"),
110 									parameters.aaGet("Loc"),
111 									parameters.aaGet("Type"),
112 									now,
113 								);
114 								response.headers["SetGameId"] = ": %d".format(gameCounter);
115 								break;
116 							case "Close":
117 							{
118 								auto id = parameters.aaGet("GameID").to!int;
119 								games = games.filter!(game => game.id != id).array;
120 								break;
121 							}
122 							case "Failed":
123 								break;
124 							default:
125 								return conn.sendResponse(response.writeError(HttpStatusCode.BadRequest));
126 						}
127 						break;
128 					case "GameList.asp":
129 						games = games.filter!(game => now - game.created < gameTimeout).array;
130 						html = "<GAMELISTSTART>\r\n" ~
131 							games
132 								.filter!(game => game.channel == parameters.aaGet("Channel"))
133 								.map!(game => "<GAME %s %s %s %s %d %d %d %s><BR>\r\n".format(
134 									game.name, game.host, game.address, game.location, 1 /* open */, game.password ? 1 : 0, game.id, game.type
135 								))
136 								.join() ~
137 							"<GAMELISTEND>\r\n";
138 						break;
139 					case "UpdatePlayerInfo.asp":
140 						break;
141 					default:
142 						return conn.sendResponse(response.writeError(HttpStatusCode.NotFound));
143 				}
144 				return conn.sendResponse(response.serveData(html));
145 			}
146 			else
147 				return conn.sendResponse(response.serveFile(pathStr[1..$], "wwwroot/"));
148 		}
149 		catch (FileException e)
150 			return conn.sendResponse(response.writeError(HttpStatusCode.NotFound           , e.msg));
151 		catch (Exception e)
152 			return conn.sendResponse(response.writeError(HttpStatusCode.InternalServerError, e.toString));
153 	}
154 }