#include "web_handlers.h" #include "../app.h" #include "esp_log.h" #include "network/network.h" #include "common/system_context.h" #include "esp_random.h" #include #include static const char* TAG = "DiscordWebHandler"; WebHandler::~WebHandler() { stop_web_server(); } esp_err_t WebHandler::start_web_server() { if (web_server_ && web_server_->is_running()) { ESP_LOGI(TAG, "Web server already running"); return ESP_OK; } auth_key_ = generate_auth_key_(); uint16_t port = web_server_->start( auth_key_, 8080 ); if (port == 0) { ESP_LOGE(TAG, "Failed to start web server"); return ESP_FAIL; } esp_err_t ret = register_web_endpoints_(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to register web endpoints"); web_server_->stop(); return ret; } ESP_LOGI(TAG, "Web server started"); return ESP_OK; } esp_err_t WebHandler::stop_web_server() { if (web_server_) { web_server_->stop(); ESP_LOGI(TAG, "Web server stopped"); } auth_key_.clear(); return ESP_OK; } std::string WebHandler::get_url() const { if (web_server_ && web_server_->is_running()) { NetworkHandler* network_handler = SystemContext::instance().get_network_handler(); if (!network_handler) { ESP_LOGE(TAG, "Network handler not available in system context"); return ""; } WifiHandler& wifi_handler = network_handler->get_wifi_handler(); std::string device_ip = wifi_handler.get_current_ip(); if (device_ip.empty()) { ESP_LOGW(TAG, "Device not connected to WiFi"); return ""; } uint16_t port = web_server_->get_port(); std::ostringstream url; url << "http://" << device_ip << ":" << port << "/?auth=" << auth_key_; return url.str(); } return ""; } std::string WebHandler::get_device_ip() const { if (web_server_ && web_server_->is_running()) { NetworkHandler* network_handler = SystemContext::instance().get_network_handler(); if (!network_handler) { ESP_LOGE(TAG, "Network handler not available in system context"); return ""; } WifiHandler& wifi_handler = network_handler->get_wifi_handler(); return wifi_handler.get_current_ip(); } return ""; } uint16_t WebHandler::get_port() const { if (web_server_ && web_server_->is_running()) { return web_server_->get_port(); } return 0; } // // // std::string WebHandler::generate_auth_key_() { // Generate 128-bit random key using ESP32 hardware RNG uint32_t rand_values[4]; for (int i = 0; i < 4; i++) { rand_values[i] = esp_random(); } // Convert to hex string std::ostringstream oss; oss << std::hex << std::setfill('0'); for (int i = 0; i < 4; i++) { oss << std::setw(8) << rand_values[i]; } return oss.str(); } esp_err_t WebHandler::register_web_endpoints_() { if (!web_server_ || !web_server_->is_running()) { return ESP_FAIL; } // GET / - Serve settings page httpd_uri_t settings_page_uri = { .uri = "/", .method = HTTP_GET, .handler = settings_page_handler_, .user_ctx = this }; web_server_->register_uri_handler(&settings_page_uri); // POST /save - Save settings httpd_uri_t save_settings_uri = { .uri = "/save", .method = HTTP_POST, .handler = save_settings_handler_, .user_ctx = this }; web_server_->register_uri_handler(&save_settings_uri); // POST /test - Test connection httpd_uri_t test_connection_uri = { .uri = "/test", .method = HTTP_POST, .handler = test_connection_handler_, .user_ctx = this }; web_server_->register_uri_handler(&test_connection_uri); return ESP_OK; } esp_err_t WebHandler::settings_page_handler_(httpd_req_t* req) { WebHandler* self = static_cast(req->user_ctx); // Validate auth size_t query_len = httpd_req_get_url_query_len(req); if (query_len == 0) { httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized"); return ESP_FAIL; } char* query = new char[query_len + 1]; if (httpd_req_get_url_query_str(req, query, query_len + 1) != ESP_OK) { delete[] query; httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Bad Request"); return ESP_FAIL; } if (!self->web_server_->validate_auth(query)) { delete[] query; httpd_resp_send_err(req, HTTPD_401_UNAUTHORIZED, "Unauthorized"); return ESP_FAIL; } delete[] query; // Get current settings (access private members via friend) std::string current_ip = self->setting_handler_->get_remote_ip(); uint16_t current_port = self->setting_handler_->get_remote_port(); uint16_t current_local_port = self->setting_handler_->get_local_port(); // Build HTML page std::ostringstream html; html << "" << "" << "Discord Bridge Settings" << "" << "

Discord Bridge Settings

" << "
" << "" << "" << "" << "" << "" << "" << "" << "" << "
" << "
" << ""; std::string html_str = html.str(); httpd_resp_set_type(req, "text/html"); httpd_resp_send(req, html_str.c_str(), html_str.length()); return ESP_OK; } esp_err_t WebHandler::save_settings_handler_(httpd_req_t* req) { WebHandler* self = static_cast(req->user_ctx); // Read POST data char* buf = new char[req->content_len + 1]; int ret = httpd_req_recv(req, buf, req->content_len); if (ret <= 0) { delete[] buf; httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Failed to read request"); return ESP_FAIL; } buf[ret] = '\0'; // Parse form data char ip[64] = { 0 }; char port_str[8] = { 0 }; char local_port_str[8] = { 0 }; httpd_query_key_value(buf, "ip", ip, sizeof(ip)); httpd_query_key_value(buf, "port", port_str, sizeof(port_str)); httpd_query_key_value(buf, "localPort", local_port_str, sizeof(local_port_str)); delete[] buf; if (strlen(ip) == 0 || strlen(port_str) == 0 || strlen(local_port_str) == 0) { const char* resp = "{\"success\":false,\"message\":\"Missing fields\"}"; httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, resp, strlen(resp)); return ESP_OK; } uint16_t port = static_cast(atoi(port_str)); uint16_t local_port = static_cast(atoi(local_port_str)); if (port == 0 || local_port == 0) { const char* resp = "{\"success\":false,\"message\":\"Invalid port\"}"; httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, resp, strlen(resp)); return ESP_OK; } // Save settings if (self && self->setting_handler_) { self->setting_handler_->save_settings(std::string(ip), port, local_port); } const char* resp = "{\"success\":true,\"message\":\"Settings saved successfully\"}"; httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, resp, strlen(resp)); ESP_LOGI(TAG, "Settings saved via web interface: %s:%u (local port: %u)", ip, port, local_port); return ESP_OK; } esp_err_t WebHandler::test_connection_handler_(httpd_req_t* req) { WebHandler* self = static_cast(req->user_ctx); IotDisBridge* bridge = self ? self->bridge_ : nullptr; // Read POST data char* buf = new char[req->content_len + 1]; int ret = httpd_req_recv(req, buf, req->content_len); if (ret <= 0) { delete[] buf; httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Failed to read request"); return ESP_FAIL; } buf[ret] = '\0'; // Parse form data char ip[64] = { 0 }; char port_str[8] = { 0 }; char local_port_str[8] = { 0 }; httpd_query_key_value(buf, "ip", ip, sizeof(ip)); httpd_query_key_value(buf, "port", port_str, sizeof(port_str)); httpd_query_key_value(buf, "localPort", local_port_str, sizeof(local_port_str)); delete[] buf; if (strlen(ip) == 0 || strlen(port_str) == 0 || strlen(local_port_str) == 0) { const char* resp = "{\"success\":false,\"message\":\"Missing fields\"}"; httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, resp, strlen(resp)); return ESP_OK; } uint16_t port = static_cast(atoi(port_str)); uint16_t local_port = static_cast(atoi(local_port_str)); if (port == 0 || local_port == 0) { const char* resp = "{\"success\":false,\"message\":\"Invalid port\"}"; httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, resp, strlen(resp)); return ESP_OK; } // Test connection bool success = false; if (bridge) { success = bridge->test_connection(std::string(ip), port, local_port); } else { ESP_LOGE(TAG, "IotDisBridge pointer is null, cannot test connection"); } const char* resp = success ? "{\"success\":true,\"message\":\"Connection successful!\"}" : "{\"success\":false,\"message\":\"No response from bridge\"}"; httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, resp, strlen(resp)); ESP_LOGI(TAG, "Connection test via web interface: %s:%u (local port: %u) - %s", ip, port, local_port, success ? "SUCCESS" : "FAILED"); return ESP_OK; }