-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHttpServer-King.cpp
More file actions
311 lines (265 loc) · 11.6 KB
/
HttpServer-King.cpp
File metadata and controls
311 lines (265 loc) · 11.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
// HttpServer-King.cpp : Defines the entry point for the application.
// Wednesday, 26 August 2025, 11:13 PM
// Edited on Friday, 07 November 2025, 11:34 AM
// Dimmed to be heavily refactored later
//
// #define DEBUG_MODE
// More #ifdef controls added later...
#include "HttpMethod.hpp"
#include "HttpRequest.hpp"
#include "HttpResponse.hpp"
#include "HttpServer.hpp"
#include "HttpStatus.hpp"
#include "json.h"
#include "MimeType.hpp"
#include <chrono>
#include <ctime>
#include <exception>
#include <filesystem>
#include <format>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <sstream>
#include <string>
namespace fs = std::filesystem;
using json = nlohmann::json;
static std::mutex mutexLogging;
std::string Page404 = "<h3 style='color: red;'>404 - File Not found</h3>";
std::string PageEmpty = "<h3 style='color: red;'>404 - File was found but is empty</h3>";
std::string Page403 = "<h3 style='color: red;'>403 - File Forbidden</h3>";
std::string Server_IP = "0.0.0.0";
std::string Server_Config_Path = (fs::current_path() / "ServerConfig.json").string();
std::vector<std::string> BlackListPaths;
bool useConfig = true;
bool useFileLogging = true;
bool hidePublicIp = false;
int Server_Port = 6432;
HttpServer httpServer_King;
std::regex privateIpRegexp(R"(^(?:10\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]?[0-9])\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]?[0-9])\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]?[0-9])|192\.168\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]?[0-9])\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]?[0-9])|172\.(?:1[6-9]|2[0-9]|3[0-1])\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]?[0-9])\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]?[0-9]))$)");
std::array version = {
1,
0,
0
};
class ServerUtils
{
public:
static std::string readFile(const std::string &filePath)
{
std::ostringstream contents;
std::ifstream file(filePath, std::ios::in | std::ios::binary);
if (!file)
return "";
contents << file.rdbuf();
return contents.str();
};
static void Write_log(const std::string &logType, const std::string &message)
{
static const std::unordered_map<std::string, std::string> validTypes = {
{"INFO", "\033[32m"},
{"WARNING", "\033[33m"},
{"ERROR", "\033[31m"},
{"DEBUG", "\033[36m"}
};
std::lock_guard<std::mutex> lock(mutexLogging);
std::time_t now_time_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::ofstream logFile("./ServerLogs.log", std::ios::app);
std::tm now_tm;
#ifdef _WIN32
localtime_s(&now_tm, &now_time_t);
#else
localtime_r(&now_time_t, &now_tm);
#endif
std::ostringstream currentTime;
currentTime << std::put_time(&now_tm, "%d-%m-%y %H:%M:%S");
std::string formattedLog = std::format("[{}] [{}] - {}", currentTime.str(), logType, message);
std::cout << validTypes.at(logType) << formattedLog << "\u001b[0m\n"
<< std::endl;
if (useFileLogging == true)
logFile << formattedLog << "\n";
logFile.close();
};
static void handleConfig()
{
if (fs::exists(Server_Config_Path) && readFile(Server_Config_Path).empty() == false)
{
try
{
Write_log("INFO", "Config found!");
std::ifstream dataFile(Server_Config_Path);
json data = json::parse(dataFile);
std::string Page404Custom = data.value("Page404Custom", "");
std::string Page403Custom = data.value("Page403Custom", "");
Server_IP = data.value("ip", Server_IP);
Server_Port = data.value("port", Server_Port);
BlackListPaths = data.value("BlackListPaths", BlackListPaths);
useFileLogging = data.value("useFileLogging", useFileLogging);
hidePublicIp = data.value("hidePublicIp", hidePublicIp);
if (data.contains("MimeTypesCustom") && data["MimeTypesCustom"].is_object() && data["MimeTypesCustom"].size() > 0)
{
Write_log("INFO", "Custom mime types loaded.");
MimeType::sMimeTypeMap.clear();
for (auto &[ext, type] : data["MimeTypesCustom"].items())
{
MimeType::sMimeTypeMap[ext] = type;
};
};
if (!Page404Custom.empty())
{
if (fs::exists(Page404Custom))
Page404 = readFile(Page404Custom);
else
Write_log("WARNING", "The provided 404 page was not found");
};
if (!Page403Custom.empty())
{
if (fs::exists(Page403Custom))
Page403 = readFile(Page403Custom);
else
Write_log("WARNING", "The provided 403 page was not found");
};
Write_log("INFO", std::format("Custom 404 page is {}", fs::exists(Page404Custom) ? "enabled" : "disabled"));
Write_log("INFO", std::format("Custom 403 page is {}", fs::exists(Page403Custom) ? "enabled" : "disabled"));
Write_log("INFO", std::format("Logging output to file is {}", useFileLogging ? "enabled" : "disabled"));
Write_log("INFO", std::format("Redact public IP logging is {}", hidePublicIp ? "enabled" : "disabled"));
}
catch (json::parse_error error)
{
Write_log("ERROR", std::format("Failed to parse config data: {}", error.what()));
};
}
else
{
Write_log("INFO", "A server config data was not found. A new one has been written");
std::ofstream file("ServerConfig.json");
json configData = json::parse(R"(
{
"helpInfo": {
"BlackListPath": "An array that contains files and or paths that are forbidden to access",
"MimeTypesCustom": "An array that contains custom defined file mime types",
"useFileLogging": "A boolean to tell the server to write to a log file on the disk or not",
"Page403Custom": "A string path that tells the server to use a custom 403 page, avoid a forward slash at char[0]",
"Page404Custom": "A string path that tells the server to use a custom 403 page, avoid a forward slash at char[0]",
"ip": "A string that tells the server where to bind, use 0.0.0.0 to bind to all interfaces",
"port": "A number that tells the server what port to bind to, 80 is the standard for HTTP",
"hidePublicIp": "Tells the server to hide the public IP addresses of clients for privacy reasons"
},
"BlackListPaths": [
"/ServerConfig.json",
"/ServerLogs.log"
],
"MimeTypesCustom": { },
"useFileLogging": true,
"hidePublicIp": false,
"Page403Custom": "",
"Page404Custom": "",
"ip": "0.0.0.0",
"port": 6432
}
)");
file << configData.dump(4);
file.close();
};
};
};
int main(int argc, char *argv[])
{
#ifdef DEBUG_MODE
Write_log("DEBUG", "Debug mode is enabled");
#endif
if (argc > 1)
{
for (int i = 1; i < argc; i++)
{
char *arg = argv[i];
if (std::strcmp(arg, "-help") == 0)
{
ServerUtils::Write_log("INFO", "=======Help=======");
ServerUtils::Write_log("INFO", " -nc Flag that stops the server from using or creating a config file");
ServerUtils::Write_log("INFO", " -nl Flag that stopsa the server from writing to a log file");
ServerUtils::Write_log("INFO", "==================");
return 0;
};
if (std::strcmp(arg, "-v") == 0)
{
ServerUtils::Write_log("INFO", std::format("King HTTP Server Version: {}.{}.{}", version[0], version[1], version[2]));
return 0;
};
if (std::strcmp(arg, "--nc") == 0)
useConfig = false;
if (std::strcmp(arg, "--nl") == 0)
useFileLogging = false;
};
};
if (useConfig == true)
ServerUtils::handleConfig();
httpServer_King.use(R"(/.*)", HttpMethod::GET, [&](const HttpRequest &req, HttpResponse &res)
{
std::string clientIp = req.getRemoteAddr();
std::string path = req.getPath();
std::string filePath = path == "/" ? fs::current_path().string() + "/index.html" : fs::current_path().string() + path;
std::string file_contents = ServerUtils::readFile(filePath);
std::string content_type = MimeType::getMimeType(filePath);
std::string method = HttpMethod::toString(req.getMethod());
bool isBlackListed = false;
HttpStatus::Code statusCode = HttpStatus::Code::InternalServerError;
if (!std::regex_match(clientIp, privateIpRegexp) && hidePublicIp)
clientIp = "(IP hidden)";
for (const std::string& blacklistedPath : BlackListPaths)
{
if (path.rfind(blacklistedPath, 0) == 0)
{
isBlackListed = true;
break;
};
};
if (isBlackListed) { // File is blacklisted
res.setStatus(HttpStatus::Code::Forbidden);
res.setHeader("Content-Type", "text/html");
res.send(Page403);
statusCode = HttpStatus::Code::Forbidden;
}
else if (fs::exists(filePath)) // File is found
{
if (file_contents.empty() && !fs::is_directory(filePath)) // File is empty
{
res.setStatus(HttpStatus::Code::NotFound);
res.setHeader("Content-Type", "text/html");
res.send(PageEmpty);
statusCode = HttpStatus::Code::NotFound;
}
else if (fs::exists(filePath) && fs::is_directory(filePath)) // File is a directory
{
res.setStatus(HttpStatus::Code::NotFound);
res.setHeader("Content-Type", "text/html");
res.send(Page404);
statusCode = HttpStatus::Code::NotFound;
}
else // File is ok and ready
{
res.setStatus(HttpStatus::Code::OK);
res.setHeader("Content-Type", content_type);
res.send(file_contents);
statusCode = HttpStatus::Code::OK;
};
}
else // File is not found
{
res.setStatus(HttpStatus::Code::NotFound);
res.setHeader("Content-Type", "text/html");
res.send(Page404);
statusCode = HttpStatus::Code::NotFound;
};
ServerUtils::Write_log("INFO", std::format("Client {} {} {} {}", clientIp, method, path, static_cast<int>(statusCode))); });
try
{
ServerUtils::ServerUtils::Write_log("INFO", std::format("Server listening on http://{}:{}", Server_IP, Server_Port));
httpServer_King.listen(Server_IP.c_str(), Server_Port);
}
catch (std::exception &error)
{
ServerUtils::Write_log("ERROR", std::format("An error occured while running the server: {}", error.what()));
};
};