commit 44f8d483561b5692756698e2a11fd0efcdead81f Author: Tomas Date: Sat Nov 22 16:25:58 2025 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08d9f52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,82 @@ +# Snippets taken from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + + +# Visual Studio 2015/2017 cache/options directory +.vs/ + + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ + +[Dd]ebug/x64/ +[Dd]ebugPublic/x64/ +[Rr]elease/x64/ +[Rr]eleases/x64/ +bin/x64/ +obj/x64/ + +[Dd]ebug/x86/ +[Dd]ebugPublic/x86/ +[Rr]elease/x86/ +[Rr]eleases/x86/ +bin/x86/ +obj/x86/ + +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +[Aa][Rr][Mm]64[Ee][Cc]/ +bld/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.idb +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +# but not Directory.Build.rsp, as it configures directory-level build defaults +!Directory.Build.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb \ No newline at end of file diff --git a/Main.cpp b/Main.cpp new file mode 100644 index 0000000..3b22483 --- /dev/null +++ b/Main.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +#include "Windows.h" + +#include "Router.h" +#include "Terminal.h" +#include "Resource.h" + + +int main() +{ + SetConsoleMode((HANDLE)(size_t)STD_OUTPUT_HANDLE, ENABLE_VIRTUAL_TERMINAL_PROCESSING); + + + std::string req_raw = + "GET /url/path HTTP/1.1\r\n" + "Host: example.com\r\n" + "Host: www.example.com\r\n" + "User - Agent : Mozilla / 5.0\r\n" + "Accept : text / html, application / xhtml + xml, application / xml; q = 0.9, image / avif, image / webp, */*;q=0.8\r\n" + "Accept-Language: en-GB,en;q=0.5\r\n" + "Accept-Encoding: gzip, deflate, br\r\n" + "Connection: keep-alive\r\n" + "\r\n"; + std::stringstream req(req_raw); + + + Router router; + + router.on_get("/", [&](Request& req, Response& res, Next next) { + Resource src(INDEX, HTML_FILE); + res.header("Content-Type", "text/html"); + res.send(src.data(), src.size()); + }); + + router.on_get("/url/path", [&](Request& req, Response& res, Next next) { + res.send("Hello, World!"); + }); + + std::stringstream res; + router.handle(req, res); + + std::cout << CYAN << res.str() << RESET << std::endl; + + router.listen(8080); +} \ No newline at end of file diff --git a/RC.h b/RC.h new file mode 100644 index 0000000..3078df3 --- /dev/null +++ b/RC.h @@ -0,0 +1,17 @@ +#pragma once + + +class RC +{ +public: + RC() : _rc(new int[1]) { *_rc = 1; }; + RC(const RC &r) noexcept : _rc(r._rc) { *_rc += 1; } + RC(RC &&r) noexcept : _rc(r._rc) { } + ~RC() { if (!--*_rc) delete[] _rc; }; + + int rc() { return *_rc; } + bool last() { return *_rc == 1; } + +private: + int* _rc; +}; \ No newline at end of file diff --git a/Resource.cpp b/Resource.cpp new file mode 100644 index 0000000..0f99935 --- /dev/null +++ b/Resource.cpp @@ -0,0 +1,106 @@ +#include "Resource.h" + +#include +#include + +#include + + +static HMODULE GetCurrentModule() +{ + HMODULE hModule = NULL; + GetModuleHandleEx( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + (LPCTSTR)GetCurrentModule, + &hModule + ); + + return hModule; +} + +namespace +{ + class ResBuf : public std::streambuf + { + public: + ResBuf(int id, int type) + { + HMODULE hMod = GetCurrentModule(); + + HRSRC hRes = FindResource(hMod, MAKEINTRESOURCE(id), MAKEINTRESOURCE(type)); + if (!hRes) + { + throw std::exception("Could not find resource"); + } + + HGLOBAL hResLoad = LoadResource(hMod, hRes); + if (!hResLoad) + { + throw std::exception("Could not load resource"); + } + + LPVOID lpResLock = LockResource(hResLoad); + if (!lpResLock) + { + throw std::exception("Could not lock resource"); + } + + DWORD dwResSize = SizeofResource(hMod, hRes); + if (dwResSize == 0) + { + throw std::exception("Could not aquire resource size"); + } + + _hRes = hRes; + _buff = (char*)lpResLock; + _size = dwResSize; + + setg(_buff, _buff, _buff + _size); + } + + virtual ~ResBuf() + { + FreeResource(_hRes); + } + + virtual int underflow() + { + return EOF; + } + + char* buff() + { + return _buff; + } + + size_t size() + { + return _size; + } + + private: + HRSRC _hRes; + char* _buff; + size_t _size; + }; +} + + +Resource::Resource(int id, int type) : std::istream(new ResBuf(id, type)) +{ +} + +Resource::~Resource() +{ + delete rdbuf(); +} + +const char* Resource::data() +{ + return (const char*)(((ResBuf*)rdbuf())->buff()); +} + +size_t Resource::size() +{ + return ((ResBuf*)rdbuf())->size(); +} \ No newline at end of file diff --git a/Resource.h b/Resource.h new file mode 100644 index 0000000..366d0c6 --- /dev/null +++ b/Resource.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "_resource.h" + + +class Resource : public std::istream +{ +public: + Resource(int id, int type); + virtual ~Resource(); + + Resource(const Resource&) = delete; + Resource(Resource&&) = delete; + + const char* data(); + size_t size(); +}; \ No newline at end of file diff --git a/Router.cpp b/Router.cpp new file mode 100644 index 0000000..50add5c --- /dev/null +++ b/Router.cpp @@ -0,0 +1,507 @@ +#include "Router.h" + +#include +#include +#include // Only for warning when handling client error +#include +#include +#include +#include +#include +#include +#include + +#include "Socket.h" +#include "StringUtil.h" + + +static std::map STATUS_STR = +{ + { 100, "Continue" }, + { 101, "Switching Protocols" }, + { 102, "Processing" }, + { 103, "Early Hints" }, + { 104, "Upload Resumption Supported" }, + { 200, "OK" }, + { 201, "Created" }, + { 202, "Accepted" }, + { 203, "Non - Authoritative Information" }, + { 204, "No Content" }, + { 205, "Reset Content" }, + { 206, "Partial Content" }, + { 207, "Multi - Status" }, + { 208, "Already Reported" }, + { 226, "IM Used" }, + { 300, "Multiple Choices" }, + { 301, "Moved Permanently" }, + { 302, "Found" }, + { 303, "See Other" }, + { 304, "Not Modified" }, + { 305, "Use Proxy" }, + { 306, "Switch Proxy" }, + { 307, "Temporary Redirect" }, + { 308, "Permanent Redirect" }, + { 400, "Bad Request" }, + { 401, "Unauthorized" }, + { 402, "Payment Required" }, + { 403, "Forbidden" }, + { 404, "Not Found" }, + { 405, "Method Not Allowed" }, + { 406, "Not Acceptable" }, + { 407, "Proxy Authentication Required" }, + { 408, "Request Timeout" }, + { 409, "Conflict" }, + { 410, "Gone" }, + { 411, "Length Required" }, + { 412, "Precondition Failed" }, + { 413, "Content Too Large" }, + { 414, "URI Too Long" }, + { 415, "Unsupported Media Type" }, + { 416, "Range Not Satisfiable" }, + { 417, "Expectation Failed" }, + { 418, "I’m a Teapot" }, + { 421, "Misdirected Request" }, + { 422, "Unprocessable Content" }, + { 423, "Locked" }, + { 424, "Failed Dependency" }, + { 425, "Too Early" }, + { 426, "Upgrade Required" }, + { 427, "Unassigned" }, + { 428, "Precondition Required" }, + { 429, "Too Many Requests" }, + { 430, "Unassigned" }, + { 431, "Request Header Fields Too Large" }, + { 451, "Unavailable For Legal Reasons" }, + { 500, "Internal Server Error" }, + { 501, "Not Implemented" }, + { 502, "Bad Gateway" }, + { 503, "Service Unavailable" }, + { 504, "Gateway Timeout" }, + { 505, "HTTP Version Not Supported" }, + { 506, "Variant Also Negotiates" }, + { 507, "Insufficient Storage" }, + { 508, "Loop Detected" }, + { 509, "Unassigned" }, + { 510, "Not Extended" }, + { 511, "Network Authentication Required" }, +}; + + +namespace +{ + class ClientError : public std::exception + { + public: + ClientError(const char* what) : std::exception(what) + { + } + }; + + + class ReqBuf : public std::streambuf + { + public: + ReqBuf(std::istream& stream) : _stream(stream) + { + constexpr int header_max = 64; + constexpr int line_max = 512; + + for (int n = 0;; n++) + { + if (n == header_max) + { + throw ClientError("Too many headers"); + } + + std::string line; + line += (char)_stream.get(); + line += (char)_stream.get(); + while (line.substr(line.size() - 2, 2) != "\r\n") + { + int byte = _stream.get(); + if (byte == EOF) + { + throw ClientError("Connection closed prmaturely"); + } + if (line.size() > line_max) + { + throw ClientError("Header field too long"); + } + line += (char)byte; + } + + if (line == "\r\n") + break; + + line = line.substr(0, line.size() - 2); + + if (n == 0) + { + std::vector req = StringUtil::splitClean(line, ' '); + method = req[0]; + url = req[1]; + http_version = req[2]; + + std::transform(method.begin(), method.end(), method.begin(), ::toupper); + } + else + { + size_t sep = line.find(':'); + std::string name = StringUtil::strip(line.substr(0, sep)); + std::string value = StringUtil::strip(line.substr(sep + 1)); + headers[name] = value; + } + } + } + + virtual int underflow() + { + return _stream.get(); + } + + std::string method, url, http_version; + std::map headers; + + private: + std::istream& _stream; + }; +} + + +#define buf ((ReqBuf*)rdbuf()) +Request::Request(std::istream& stream) : std::istream(new ReqBuf(stream)) +{ +} + +Request::~Request() +{ + delete rdbuf(); +} + +std::string Request::url() const +{ + return buf->url; +} + +std::string Request::method() const +{ + return buf->method; +} + +const std::string* Request::header(std::string name) const +{ + auto field = buf->headers.find(name); + if (field == buf->headers.end()) + return nullptr; + return &field->second; +} +#undef buf + + +namespace +{ + class ResBuf : public std::streambuf + { + public: + ResBuf(std::ostream& stream) : _stream(stream) + { + } + + void send_head() + { + if (head_sent) + throw std::exception("Head has already been sent"); + head_sent = true; + + _stream + << "HTTP/1.1 " << status << ' ' << STATUS_STR[status] << "\r\n"; + + for (auto& pair : headers) + _stream << pair.first << ": " << pair.second << "\r\n"; + + _stream << "\r\n"; + } + + int status = 200; + bool head_sent = false; + std::map headers = { + {"Connection", "Close"}, + }; + + protected: + virtual int overflow(int c) override + { + if (!head_sent) + send_head(); + _stream.put(c); + return c; + } + + virtual int sync() override + { + _stream.flush(); + return 0; + } + + private: + std::ostream& _stream; + }; +} + + +#define buf ((ResBuf*)rdbuf()) +Response::Response(std::ostream& stream) : std::ostream(new ResBuf(stream)) +{ +} + +Response::~Response() +{ + delete rdbuf(); +} + +void Response::status(int status) +{ + buf->status = status; +} + +void Response::header(std::string name, std::string value) +{ + if (buf->head_sent) + throw std::exception("Headers already sent."); + buf->headers[name] = value; +} + +void Response::send(std::string text) +{ + if (buf->headers.find("Content-Type") == buf->headers.end()) + header("Content-Type", "text/plain"); + header("Content-Length", text.size()); + + *this << text; + + flush(); +} + +void Response::send(const void* data, size_t size) +{ + if (buf->headers.find("Content-Type") == buf->headers.end()) + header("Content-Type", "application/octet-stream"); + header("Content-Length", size); + + write((const char*)data, size); + + flush(); +} + +void Response::sendFile(std::string filename) +{ + std::ifstream fs(filename, std::ifstream::binary | std::ifstream::ate); + + if (!fs.is_open()) + throw std::exception(("Could not open \""+filename+"\"").c_str()); + + header("Content-Length", (size_t)fs.tellg()); + fs.seekg(0, std::ifstream::beg); + + *this << fs.rdbuf(); + + flush(); +} +#undef buf + + +static void default_404(Request& req, Response& res) +{ + std::stringstream ss; + + ss << "" + << "" + << "Not Found" + << "" + << "" + << "

Not Found

" + << "

" + << '"' << req.url() << '"' + << " could not be found." + << "

" + << "" + << ""; + + res.status(404); + res.header("Content-Type", "text/html"); + res.send(ss.str()); +} + + +static void default_500(Request& req, Response& res, std::string msg) +{ + std::stringstream ss; + + ss << "" + << "" + << "Error" + << "" + << "" + << "

Error

" + << "

" + << msg + << "

" + << "" + << ""; + + res.status(500); + res.header("Content-Type", "text/html"); + res.send(ss.str()); +} + + +Router::Router() +{ +} + + +void Router::handle(std::istream& req_s, std::ostream& res_s) +{ + Request req(req_s); + Response res(res_s); + + auto f = _handlers.find(req.url()); + + if (f == _handlers.end()) + { + default_404(req, res); + return; + } + else + { + std::vector& handlers = f->second; + auto h = handlers.begin(); + + jmp_buf next; + setjmp(next); + + if (h == handlers.end()) + { + default_404(req, res); + return; + } + else + { + try + { + (*h)(req, res, [&]() { h += 1; longjmp(next, 0); }); + } + catch (std::exception& ex) + { + default_500(req, res, std::string(ex.what())); + } + catch (std::string& str) + { + default_500(req, res, str); + } + catch (const char* str) + { + default_500(req, res, std::string(str)); + } + catch (...) + { + default_500(req, res, "Unhandeled exception has accrued"); + } + } + } +} + + +void Router::on(std::string url, Handler handler) +{ + // if vector already existed it is returned. + // If not, a new one is created and returned. + auto pair = _handlers.emplace(std::piecewise_construct, std::make_tuple(url), std::make_tuple()); + + // First is the key-value pair iterator, + // second is true if new vector was created. + auto& iter = pair.first; + + iter + // First is the key, second is the vector. + ->second + .push_back(handler); +} + +void Router::on_get(std::string url, Handler handler) +{ + on(url, [=](Request& req, Response& res, Next next) { if (req.method() == "GET") handler(req, res, next); else next(); }); +} + +void Router::on_head(std::string url, Handler handler) +{ + on(url, [=](Request& req, Response& res, Next next) { if (req.method() == "HEAD") handler(req, res, next); else next(); }); +} + +void Router::on_options(std::string url, Handler handler) +{ + on(url, [=](Request& req, Response& res, Next next) { if (req.method() == "OPTIONS") handler(req, res, next); else next(); }); +} + +void Router::on_trace(std::string url, Handler handler) +{ + on(url, [=](Request& req, Response& res, Next next) { if (req.method() == "TRACE") handler(req, res, next); else next(); }); +} + +void Router::on_put(std::string url, Handler handler) +{ + on(url, [=](Request& req, Response& res, Next next) { if (req.method() == "PUT") handler(req, res, next); else next(); }); +} + +void Router::on_delete(std::string url, Handler handler) +{ + on(url, [=](Request& req, Response& res, Next next) { if (req.method() == "DELETE") handler(req, res, next); else next(); }); +} + +void Router::on_post(std::string url, Handler handler) +{ + on(url, [=](Request& req, Response& res, Next next) { if (req.method() == "POST") handler(req, res, next); else next(); }); +} + +void Router::on_patch(std::string url, Handler handler) +{ + on(url, [=](Request& req, Response& res, Next next) { if (req.method() == "PATCH") handler(req, res, next); else next(); }); +} + +void Router::on_connect(std::string url, Handler handler) +{ + on(url, [=](Request& req, Response& res, Next next) { if (req.method() == "CONNECT") handler(req, res, next); else next(); }); +} + +void Router::listen(std::string address, short port) +{ + Socket server; + server.bind(address.c_str(), port); + server.listen(); + + while (true) + { + Socket client = server.accept(); + SocketStream stream(client); + handle(stream, stream); + } +} + +void Router::listen(short port) +{ + Socket server; + server.bind(port); + server.listen(); + + while (true) try + { + Socket client = server.accept(); + SocketStream stream(client); + handle(stream, stream); + } + catch (ClientError& err) + { + // Nothing major, ignore + std::cout << "Client Error: " << err.what() << std::endl; + } +} \ No newline at end of file diff --git a/Router.h b/Router.h new file mode 100644 index 0000000..3db6fea --- /dev/null +++ b/Router.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +class Request : public std::istream +{ +public: + Request(std::istream& stream); + virtual ~Request(); + + Request(const Request&) = delete; + Request(Request&&) = delete; + + std::string url() const; + std::string method() const; + const std::string* header(std::string name) const; +}; + + +class Response : public std::ostream +{ +public: + Response(std::ostream& stream); + virtual ~Response(); + + Response(const Response&) = delete; + Response(Response&&) = delete; + + void status(int status); + + template::value, bool> = true> + void header(std::string name, T value) { header(name, std::to_string(value)); } + void header(std::string name, std::string value); + + void send(std::string text); + void send(const void* data, size_t size); + void sendFile(std::string filename); +}; + + +using Next = std::function; +class Router +{ +public: + using Handler = std::function; + + Router(); + + void handle(std::iostream& req) { handle(req, req); }; + void handle(std::istream& req, std::ostream& res); + + void on(std::string url, Handler handler); + + void on_get(std::string url, Handler handler); + void on_head(std::string url, Handler handler); + void on_options(std::string url, Handler handler); + void on_trace(std::string url, Handler handler); + void on_put(std::string url, Handler handler); + void on_delete(std::string url, Handler handler); + void on_post(std::string url, Handler handler); + void on_patch(std::string url, Handler handler); + void on_connect(std::string url, Handler handler); + + void listen(std::string address, short port); + void listen(short port); + +private: + std::map> _handlers; +}; \ No newline at end of file diff --git a/Socket.cpp b/Socket.cpp new file mode 100644 index 0000000..5453903 --- /dev/null +++ b/Socket.cpp @@ -0,0 +1,133 @@ +#include "Socket.h" + +#include "WinSock2.h" +#include "WS2tcpip.h" +#include "Windows.h" + +#include +#include + + +static bool wsa_initialized = false; + + +Socket::Socket() +{ + // Move out to a separate function + if (!wsa_initialized) + { + WSAData data; + if (WSAStartup(MAKEWORD(2, 2), &data)) + throw std::exception("Failed to initialize WSA"); + wsa_initialized = true; + } + + _sock = socket( + AF_INET, + SOCK_STREAM, + 0 // IPPROTO_TCP + ); + + if (_sock == INVALID_SOCKET) + throw std::exception("Failed to create socket"); +} + +Socket::~Socket() +{ + if (!_rc.last()) + return; + // Flush comunication before closing + shutdown(_sock, SD_SEND); + char buffer[512]; + while (read(buffer, sizeof(buffer))) {} + shutdown(_sock, SD_RECEIVE); + + closesocket(_sock); +} + +void Socket::bind(const char *address, unsigned short port) +{ + sockaddr_in addr = { 0 }; + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (address) + inet_pton(AF_INET, address, &addr.sin_addr); + else + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + if (::bind(_sock, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) + throw std::exception("Failed to bind socket"); +} + +void Socket::listen(int backlog) +{ + if (::listen(_sock, backlog) == SOCKET_ERROR) + throw std::exception("Failed to listen on socket"); +} + +Socket Socket::accept() +{ + SOCKET sock = ::accept(_sock, nullptr, nullptr); + if (sock == INVALID_SOCKET) + throw std::exception("Failed to accept socket"); + Socket socket(sock); + return std::move(socket); +} + +int Socket::read(void* buffer, int size) +{ + int ret = ::recv(_sock, (char*)buffer, size, 0); + return max(0, ret); // Clamp to 0, as socket is always synchronious +} + +int Socket::write(const char *string) +{ + return write(string, (int)strlen(string)); +} + +int Socket::write(const void *data, int size) +{ + return ::send(_sock, (const char *)data, size, 0); +} + +void Socket::end() +{ + ::shutdown(_sock, SD_SEND); +} + + +SocketStreamBuf::~SocketStreamBuf() +{ + sync(); +} + +int SocketStreamBuf::underflow() +{ + // System should be buffering received data + if (!_sock.read(&_c, 1)) + return EOF; + setg(&_c, &_c, &_c+1); + return _c; +} + +int SocketStreamBuf::overflow(int c) +{ + if (_buf_size >= _buf_size_max) + sync(); + + // This check is not required, but it muffles a warning in VisualStudio + if (_buf_size < _buf_size_max) + _buf[_buf_size++] = c; + else + assert(0); + + return c; +} + +int SocketStreamBuf::sync() +{ + _sock.write(_buf, _buf_size); + _buf_size = 0; + return 0; +} \ No newline at end of file diff --git a/Socket.h b/Socket.h new file mode 100644 index 0000000..0e01fac --- /dev/null +++ b/Socket.h @@ -0,0 +1,86 @@ +#pragma once + +#include "RC.h" + +#include +#include +#include + + +class SocketStreamBuf; +class ISocketStream; +class OSocketStream; +class SocketStream; + + +class Socket +{ +public: + Socket(); + Socket(size_t socket) : _sock(socket) {}; + virtual ~Socket(); + + void bind(unsigned short port) { bind(nullptr, port); } + void bind(const char* address, unsigned short port); + void listen(int backlog = 1024); + Socket accept(); + + int read(void* buffer, int size); + + int write(const char* string); + int write(const void* data, int size); + void end(); + + size_t handle() const { return _sock; } + +private: + size_t _sock; + RC _rc; +}; + + +class SocketStreamBuf : public std::streambuf +{ +public: + SocketStreamBuf(Socket sock) : _sock(sock), _buf() {} + virtual ~SocketStreamBuf(); + + virtual int underflow(); + virtual int overflow(int c); + virtual int sync(); + +private: + Socket _sock; + + // Read buffer + char _c = 0; + + // Send buffer + enum { _buf_size_max = 1024 }; + char _buf[_buf_size_max] = { 0 }; + int _buf_size = 0; +}; + + +class ISocketStream : public std::istream +{ +public: + ISocketStream(Socket& sock) : std::istream(new SocketStreamBuf(sock)) {} + virtual ~ISocketStream() { delete rdbuf(); } +}; + + +class OSocketStream : public std::ostream +{ +public: + OSocketStream(Socket& sock) : std::ostream(new SocketStreamBuf(sock)) {} + virtual ~OSocketStream() { delete rdbuf(); } +}; + + +class SocketStream : public std::iostream +{ +public: + SocketStream(Socket& sock) : std::iostream(new SocketStreamBuf(sock)) {} + virtual ~SocketStream() { delete rdbuf(); } +}; \ No newline at end of file diff --git a/StringUtil.cpp b/StringUtil.cpp new file mode 100644 index 0000000..e5919b1 --- /dev/null +++ b/StringUtil.cpp @@ -0,0 +1,100 @@ +#include "StringUtil.h" + + +std::vector StringUtil::split(std::string str, std::string sep) +{ + std::vector vec; + + while (str.size() != 0) + { + size_t index = str.find(sep); + if (index) + vec.push_back(str.substr(0, index)); + else + vec.push_back(""); + str = str.substr(index + sep.size()); + } + + return vec; +} + +std::vector StringUtil::split(std::string str, char sep) +{ + std::vector vec; + + int i, j; + for (i = 0, j = 1; j < str.size(); j++) + { + if (str[j] == sep) + { + if (i == j) + vec.push_back(""); + else + vec.push_back(str.substr(i, j - i)); + i = j + 1; + } + } + + if (j + 1 != i) + vec.push_back(str.substr(i, j - 1)); + + return vec; +} + +std::vector StringUtil::splitClean(std::string str, std::string sep) +{ + std::vector vec; + + while (str.size() != 0) + { + size_t index = str.find(sep); + if (index) + vec.push_back(str.substr(0, index)); + str = str.substr(index + sep.size()); + } + + return vec; +} + +std::vector StringUtil::splitClean(std::string str, char sep) +{ + std::vector vec; + + int i, j; + for (i = 0, j = 1; j < str.size(); j++) + { + if (str[j] == sep && i != j) + { + vec.push_back(str.substr(i, j - i)); + i = j + 1; + } + } + + if (j + 1 != i) + vec.push_back(str.substr(i, j - 1)); + + return vec; +} + +std::string StringUtil::strip(std::string str, char sep) +{ + return stripLeft(stripRight(str, sep), sep); +} + +std::string StringUtil::stripLeft(std::string str, char sep) +{ + int n; + for (n = 0; n < str.size(); n++) + if (str[n] != sep) + break; + return str.substr(n); +} + +std::string StringUtil::stripRight(std::string str, char sep) +{ + int n; + for (n = (int)str.size()-1; n > -1; n--) + if (str[n] != sep) + break; + return str.substr(0, n+1); +} \ No newline at end of file diff --git a/StringUtil.h b/StringUtil.h new file mode 100644 index 0000000..523ba2c --- /dev/null +++ b/StringUtil.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + + +namespace StringUtil +{ + std::vector split(std::string str, std::string sep); + std::vector split(std::string str, char sep); + + std::vector splitClean(std::string str, std::string sep); + std::vector splitClean(std::string str, char sep); + + std::string strip(std::string str, char c = ' '); + std::string stripLeft(std::string str, char c = ' '); + std::string stripRight(std::string str, char c = ' '); +} \ No newline at end of file diff --git a/Terminal.h b/Terminal.h new file mode 100644 index 0000000..5887037 --- /dev/null +++ b/Terminal.h @@ -0,0 +1,42 @@ +#pragma once + +#define __TERM_INT2STR(n) #n + +#define FG(r, g, b) ("\x1B[38;2;" __TERM_INT2STR(r) ";" __TERM_INT2STR(g) ";" __TERM_INT2STR(b) "m") +#define BG(r, g, b) ("\x1B[48;2;" __TERM_INT2STR(r) ";" __TERM_INT2STR(g) ";" __TERM_INT2STR(b) "m") + +#define RESET "\x1B[0m" + +#define BLACK "\x1B[30m" +#define RED "\x1B[31m" +#define GREEN "\x1B[32m" +#define YELLOW "\x1B[33m" +#define BLUE "\x1B[34m" +#define MAGENTA "\x1B[35m" +#define CYAN "\x1B[36m" +#define WHITE "\x1B[37m" +#define BRIGHT_BLACK "\x1B[90m" +#define BRIGHT_RED "\x1B[91m" +#define BRIGHT_GREEN "\x1B[92m" +#define BRIGHT_YELLOW "\x1B[93m" +#define BRIGHT_BLUE "\x1B[94m" +#define BRIGHT_MAGENTA "\x1B[95m" +#define BRIGHT_CYAN "\x1B[96m" +#define BRIGHT_WHITE "\x1B[97m" + +#define BBLACK "\x1B[40m" +#define BRED "\x1B[41m" +#define BGREEN "\x1B[42m" +#define BYELLOW "\x1B[43m" +#define BBLUE "\x1B[44m" +#define BMAGENTA "\x1B[45m" +#define BCYAN "\x1B[46m" +#define BWHITE "\x1B[47m" +#define BBRIGHT_BLACK "\x1B[100m" +#define BBRIGHT_RED "\x1B[101m" +#define BBRIGHT_GREEN "\x1B[102m" +#define BBRIGHT_YELLOW "\x1B[103m" +#define BBRIGHT_BLUE "\x1B[104m" +#define BBRIGHT_MAGENTA "\x1B[105m" +#define BBRIGHT_CYAN "\x1B[106m" +#define BBRIGHT_WHITE "\x1B[107m" \ No newline at end of file diff --git a/_resource.h b/_resource.h new file mode 100644 index 0000000..c9bf563 --- /dev/null +++ b/_resource.h @@ -0,0 +1,19 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by hate.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif + + +#define INDEX 1 + +#define HTML_FILE 256 diff --git a/hate.rc b/hate.rc new file mode 100644 index 0000000..a955d58 Binary files /dev/null and b/hate.rc differ diff --git a/hate.sln b/hate.sln new file mode 100644 index 0000000..7ea14e4 --- /dev/null +++ b/hate.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36511.14 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hate", "hate.vcxproj", "{0181C661-83F0-48E2-BBAF-D802CCFB2104}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0181C661-83F0-48E2-BBAF-D802CCFB2104}.Debug|x64.ActiveCfg = Debug|x64 + {0181C661-83F0-48E2-BBAF-D802CCFB2104}.Debug|x64.Build.0 = Debug|x64 + {0181C661-83F0-48E2-BBAF-D802CCFB2104}.Debug|x86.ActiveCfg = Debug|Win32 + {0181C661-83F0-48E2-BBAF-D802CCFB2104}.Debug|x86.Build.0 = Debug|Win32 + {0181C661-83F0-48E2-BBAF-D802CCFB2104}.Release|x64.ActiveCfg = Release|x64 + {0181C661-83F0-48E2-BBAF-D802CCFB2104}.Release|x64.Build.0 = Release|x64 + {0181C661-83F0-48E2-BBAF-D802CCFB2104}.Release|x86.ActiveCfg = Release|Win32 + {0181C661-83F0-48E2-BBAF-D802CCFB2104}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {69BFFB14-18F1-4AF0-80B7-BBF80722B98A} + EndGlobalSection +EndGlobal diff --git a/hate.vcxproj b/hate.vcxproj new file mode 100644 index 0000000..b98598d --- /dev/null +++ b/hate.vcxproj @@ -0,0 +1,151 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {0181c661-83f0-48e2-bbaf-d802ccfb2104} + hate + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + ws2_32.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + ws2_32.lib;%(AdditionalDependencies) + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + ws2_32.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + ws2_32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hate.vcxproj.filters b/hate.vcxproj.filters new file mode 100644 index 0000000..b340bbd --- /dev/null +++ b/hate.vcxproj.filters @@ -0,0 +1,62 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/hate.vcxproj.user b/hate.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/hate.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/res/index.html b/res/index.html new file mode 100644 index 0000000..51a91d8 --- /dev/null +++ b/res/index.html @@ -0,0 +1,10 @@ + + + + Hate + + +

H.A.T.E.

+

Time to KILL those pesky little CPU sucking processes!

+ + \ No newline at end of file