#ifndef TLS_NETWORK_HPP_ #define TLS_NETWORK_HPP_ #include #include #include #include #include #include #include using std::string, std::cout, std::endl, nlohmann::json; class NetworkManager { private: WebSocket& web; SSL_CTX* ctx; int sock = -1; std::unique_ptr ssl = { nullptr, &SSL_free }; void handleSSLInitErrors() { ERR_print_errors_fp(stderr); abort(); } void createSSLConnection(const std::string& hostname, const std::string& port) { Logs::create(INFO, NETWORK, "SSL connection"); addrinfo hints = { 0 }, *res = { 0 }; hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(hostname.c_str(), port.c_str(), &hints, &res) != 0) { Logs::create(ERROR, NETWORK, "Failed to get address info for " + hostname + ":" + port); handleSSLInitErrors(); } sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock == -1) { Logs::create(ERROR, NETWORK, "Socket error"); close(sock); handleSSLInitErrors(); } if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) { Logs::create(ERROR, NETWORK, "Failed to connect to " + hostname + ":" + port); close(sock); handleSSLInitErrors(); } ssl.reset(SSL_new(ctx)); if (!ssl) { Logs::create(ERROR, NETWORK, "Failed to create SSL structure"); close(sock); handleSSLInitErrors(); } SSL_set_fd(ssl.get(), sock); if (SSL_connect(ssl.get()) != 1) { Logs::create(ERROR, NETWORK, "SSL connection failed"); close(sock); handleSSLInitErrors(); } freeaddrinfo(res); } NetworkManager& operator=(const NetworkManager&) = delete; NetworkManager(const NetworkManager&) = delete; NetworkManager() : web(WebSocket::getInstance()) { Logs::create(INFO, NETWORK, "Network init"); if (!ctx) { ctx = SSL_CTX_new(TLS_client_method()); if (!ctx) { Logs::create(ERROR, NETWORK, "Failed to create SSL context"); handleSSLInitErrors(); } OPENSSL_init_ssl(0, 0); OPENSSL_add_all_algorithms_noconf(); SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION); createSSLConnection("discord.com", "443"); } } std::string extract(const std::string& httpResponse) { unsigned long pos = httpResponse.find("\r\n\r\n"); if (pos != std::string::npos) { std::string json, chunkSizeStr; std::istringstream stream(httpResponse.substr(pos + 4)); while (std::getline(stream, chunkSizeStr)) { unsigned long chunkSize = std::stoul(chunkSizeStr, nullptr, 16); if (chunkSize == 0) break; std::string chunk(chunkSize, '\0'); stream.read(&chunk[0], chunkSize); json.append(chunk); stream.ignore(2); } return json; } return ""; } public: static NetworkManager& getInstance() { Logs::create(WARNING, NETWORK, "Instance event"); static NetworkManager instance; return instance; } ~NetworkManager() { if (ctx) SSL_CTX_free(ctx); close(sock); } std::string request(const std::string& method, const std::string& path, const std::string& data = "", bool closeConnection = true) { if (closeConnection) createSSLConnection("discord.com", "443"); auto net = [this, method, path, data, closeConnection, result = std::string("")]() mutable -> std::string { std::string request = method + " " + path + " HTTP/1.1\r\n"; request += "Host: discord.com\r\n"; request += "Accept: application/json\r\n"; request += "Content-Type: application/json\r\n"; request += "Authorization: " + web.getToken() + "\r\n"; request += "Content-Length: " + std::to_string(data.length()) + "\r\n"; if (closeConnection) { request += "Connection: close\r\n\r\n"; } else { request += "Connection: keep-alive\r\n\r\n"; } request += data; if (SSL_write(ssl.get(), request.c_str(), request.length()) <= 0) { Logs::create(ERROR, NETWORK, "Failed to send request"); handleSSLInitErrors(); return ""; } Logs::create(INFO, NETWORK, "Request " + method + " " + path); if (closeConnection) { std::vector buffer(1024); int bytesRead; while ((bytesRead = SSL_read(ssl.get(), buffer.data(), buffer.size())) > 0) { result.append(buffer.data(), bytesRead); if (bytesRead == buffer.size()) { buffer.resize(buffer.size() * 2); } } } return extract(result); }; return net(); } }; #endif