diff --git a/src/main.rs b/src/main.rs index 764139d..b90cfbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,7 @@ use crate::server::server::start; use crate::terms::consent_state; use crate::users::user_manager; use crate::util::config_util::CONFIG; +use crate::util::file_util::download_and_extract_zip; use crate::util::file_util::has_dir; pub static APP_STATE: LazyLock>> = @@ -135,11 +136,11 @@ async fn main() { } } if !has_dir("web") { - /*download_and_extract_zip( - "weblink", + download_and_extract_zip( + "https://omega.tensamin.net/api/download/iota_frontend", "web", ) - .await;*/ + .await; } loop { if *SHUTDOWN.read().await { diff --git a/src/server/mod.rs b/src/server/mod.rs index e19c91e..003deea 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,3 +1,4 @@ pub mod api; pub mod server; pub mod socket; +pub mod web_path_parser; diff --git a/src/server/server.rs b/src/server/server.rs index 8a26652..c9eba9a 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,7 +1,11 @@ use crate::gui::log_panel::log_message; +<<<<<<< HEAD use crate::server::api::{self, api_router}; +======= +>>>>>>> f0d04474165a8c397b527eedd59263390462af95 use crate::server::socket::handle; -use crate::util::file_util::{load_file_buf, load_file_vec}; +use crate::server::{api, web_path_parser}; +use crate::util::file_util::load_file_buf; use crate::{ACTIVE_TASKS, SHUTDOWN}; use axum::{ @@ -27,6 +31,7 @@ use std::{ time::Duration, }; use tokio::net::TcpListener; +<<<<<<< HEAD use tower::ServiceBuilder; fn build_router(ssl: bool) -> Router { @@ -36,6 +41,16 @@ fn build_router(ssl: bool) -> Router { .route("/*path", any(static_handler)) .layer(ServiceBuilder::new()) .with_state(ssl) +======= +use tokio::sync::broadcast; +use tokio_rustls::TlsAcceptor; +use tokio_tungstenite::WebSocketStream; +use tower::Service; +#[derive(Clone)] +struct HttpService { + peer_addr: SocketAddr, + ssl: bool, +>>>>>>> f0d04474165a8c397b527eedd59263390462af95 } async fn ws_handler( @@ -45,9 +60,140 @@ async fn ws_handler( ) -> impl IntoResponse { log_message(format!("WS connection from {}", addr)); +<<<<<<< HEAD ws.on_upgrade(move |socket| async move { handle_ws(socket, path).await; }) +======= + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(std::io::Result::Ok(())) + } + + fn call(&mut self, req: HttpRequest) -> Self::Future { + let peer_ip = self.peer_addr.ip(); + + let is_acceptable = is_local_network(peer_ip) || self.ssl; + + let (parts, body) = req.into_parts(); + + let method = parts.method.clone(); + let path = parts.uri.path().to_string(); + let headers = parts.headers.clone(); + + let fut = async move { + let is_websocket_upgrade = path.starts_with("/ws") + && method == Method::GET + && headers + .get("connection") + .map(|v| v.to_str().unwrap_or("").contains("Upgrade")) + .unwrap_or(false) + && headers.get("upgrade").map(|v| v.to_str().unwrap_or("")) == Some("websocket"); + + if is_websocket_upgrade { + log_message("Attempting WebSocket upgrade on /ws"); + + if let Some(sec_websocket_key) = headers.get("sec-websocket-key") { + let sec_websocket_key = sec_websocket_key.to_str().unwrap_or("").to_string(); + let sec_websocket_accept = calculate_accept_key(&sec_websocket_key); + + let response = HttpResponse::builder() + .status(StatusCode::SWITCHING_PROTOCOLS) + .header("Upgrade", "websocket") + .header("Connection", "Upgrade") + .header("Sec-WebSocket-Accept", sec_websocket_accept) + .body(Full::new(Bytes::from(""))) + .unwrap(); + let req_for_upgrade = HttpRequest::from_parts(parts, body); + let upgrades = upgrade::on(req_for_upgrade); + + match upgrades.await { + std::result::Result::Ok(upgraded_stream) => { + let raw_stream = TokioIo::new(upgraded_stream); + + let handshake_result = WebSocketStream::from_raw_socket( + raw_stream, + tungstenite::protocol::Role::Server, + None, + ) + .await; + log_message(format!("Handling WebSocket connection",)); + let (writer, reader) = handshake_result.split(); + handle(path.clone(), writer, reader); + } + Err(e) => { + log_message(format!( + "WebSocket upgrade failed after response: {:?}", + e + )); + } + } + Ok(response) + } else { + log_message("No Sec-WebSocket-Key found in request headers"); + let response = HttpResponse::builder() + .status(StatusCode::BAD_REQUEST) + .body(Full::new(Bytes::from("Missing Sec-WebSocket-Key"))) + .unwrap(); + Ok(response) + } + } else if path.starts_with("/api") { + let whole_body = match body.collect().await { + Ok(collected) => collected, + Err(e) => { + log_message(format!("Error collecting body: {}", e)); + return Ok(HttpResponse::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Full::new(Bytes::from(format!( + "Failed to read body: {}", + e + )))) + .unwrap()); + } + }; + let bytes = whole_body.to_bytes(); + + let body_string: Option = match String::from_utf8(bytes.to_vec()) { + Ok(s) => Some(s), + Err(_) => None, + }; + + Ok(api::handle(&path, &is_acceptable, headers.clone(), body_string).await) + } else { + let whole_body = match body.collect().await { + Ok(collected) => collected, + Err(e) => { + log_message(format!("Error collecting body: {}", e)); + return Ok(HttpResponse::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Full::new(Bytes::from(format!( + "Failed to read body: {}", + e + )))) + .unwrap()); + } + }; + let bytes = whole_body.to_bytes(); + + let body_string: Option = match String::from_utf8(bytes.to_vec()) { + Ok(s) => Some(s), + Err(_) => None, + }; + + Ok(web_path_parser::handle(&path, headers, body_string).await) + } + }; + + Box::pin(fut.map_err(|err: color_eyre::eyre::ErrReport| { + io::Error::new( + io::ErrorKind::Other, + format!("Error in request handling: {}", err), + ) + })) + } +>>>>>>> f0d04474165a8c397b527eedd59263390462af95 } async fn handle_ws(socket: WebSocket, path: String) { diff --git a/src/server/web_path_parser.rs b/src/server/web_path_parser.rs new file mode 100644 index 0000000..4438d0e --- /dev/null +++ b/src/server/web_path_parser.rs @@ -0,0 +1,101 @@ +use std::path::{Path, PathBuf}; + +use axum::http::HeaderValue; +use http_body_util::Full; +use hyper::body::Bytes; +use hyper::{HeaderMap, Response as HttpResponse, StatusCode}; +use json::JsonValue; + +use crate::util::file_util::load_file_vec; + +fn codec_for_ext(ext: &str) -> &'static str { + match ext { + "html" => "text/html; charset=utf-8", + "css" => "text/css", + "js" => "application/javascript", + "json" => "application/json", + "png" => "image/png", + "ico" => "image/x-icon", + "woff2" => "font/woff2", + _ => "application/octet-stream", + } +} + +pub async fn handle( + path: &str, + _headers: HeaderMap, + body_string: Option, +) -> HttpResponse> { + let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); + + let _body: Option = if body_string.is_some() { + if let Ok(body_json) = json::parse(&body_string.unwrap()) { + Some(body_json) + } else { + None + } + } else { + None + }; + + let req_path = path.trim_start_matches('/'); + let mut fs_path = PathBuf::from("web"); + + if req_path.is_empty() { + fs_path.push("index.html"); + } else { + fs_path.extend(req_path.split('/')); + } + + if fs_path.is_dir() { + fs_path.push("index.html"); + } + + let ext = fs_path.extension().and_then(|e| e.to_str()).unwrap_or(""); + + let codec = codec_for_ext(ext); + + let dir = fs_path.parent().unwrap_or(Path::new("web")); + let name = fs_path + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or("index.html"); + + let content = load_file_vec(dir.to_str().unwrap_or("web"), name); + if let Ok(content) = content { + HttpResponse::builder() + .status(StatusCode::OK) + .header("content-type", codec) + .body(Full::new(Bytes::from(content))) + .unwrap() + } else { + if matches!(ext, "js" | "css" | "woff2") { + return HttpResponse::builder() + .status(StatusCode::NOT_FOUND) + .header("content-type", "text/plain") + .body(Full::new(Bytes::from("Not found"))) + .unwrap(); + } + + let fallback = load_file_vec("web", "404.html"); + let body = if let Ok(fallback) = fallback { + if fallback.is_empty() { + include_str!("../../static/web/404.html") + .as_bytes() + .to_vec() + } else { + fallback + } + } else { + include_str!("../../static/web/404.html") + .as_bytes() + .to_vec() + }; + + HttpResponse::builder() + .status(StatusCode::NOT_FOUND) + .header("content-type", "text/html; charset=utf-8") + .body(Full::new(Bytes::from(body))) + .unwrap() + } +} diff --git a/src/util/file_util.rs b/src/util/file_util.rs old mode 100644 new mode 100755 index c27f9f9..9f26eb7 --- a/src/util/file_util.rs +++ b/src/util/file_util.rs @@ -3,6 +3,7 @@ use std::fs::{self, File}; use std::io::{self, BufReader, Read}; use std::path::{Path, PathBuf}; use sysinfo::System; +use tokio::io::AsyncWriteExt; use uuid::Uuid; use walkdir::WalkDir; use zip::ZipArchive; @@ -33,7 +34,7 @@ fn delete_dir_recursive(directory: &Path) -> bool { log_message(format!( "[IMPORTANT] Couldn't delete directory {}: {}", directory.display(), - e + e, )); return false; } @@ -124,31 +125,13 @@ pub fn load_file(path: &str, name: &str) -> String { content } -pub fn load_file_vec(path: &str, name: &str) -> Vec { +pub fn load_file_vec(path: &str, name: &str) -> Result, std::io::Error> { let dir = Path::new(&get_directory()).join(path); let file_path = dir.join(name); - if !dir.exists() { - if let Err(e) = fs::create_dir_all(&dir) { - log_message(format!("[IMPORTANT] Couldn't create directories: {}", e)); - return Vec::new(); - } - return Vec::new(); - } - - if !file_path.exists() { - if let Err(e) = File::create(&file_path) { - log_message(format!("[IMPORTANT] Couldn't create file: {}", e)); - } - return Vec::new(); - } - - let mut content = Vec::new(); - if let Ok(mut f) = File::open(&file_path) { - let _ = f.read_to_end(&mut content); - } - content + std::fs::read(file_path) } + pub fn save_file(path: &str, name: &str, value: &str) { let dir = Path::new(&get_directory()).join(path); let file_path = dir.join(name); @@ -190,6 +173,7 @@ pub fn get_directory() -> String { .to_string() } +// Helper to download the zip file content to a file on disk #[allow(dead_code)] pub fn used_space() -> u64 { get_directory_size(&PathBuf::from(get_directory())) @@ -249,19 +233,20 @@ pub fn get_used_ram() -> String { format!("{}/{}", design_byte(used), design_byte(total)) } -// Helper to download the zip file content to a file on disk -#[allow(dead_code)] -async fn download_zip(url: &str, as_name: &Path) -> Result<(), Box> { - let response = reqwest::get(url).await?; +use reqwest::Client; + +pub async fn download_zip(url: &str, zip_path: &Path) -> Result<(), Box> { + let client = Client::new(); + + let response = client.get(url).send().await?; - // Check for successful response status if !response.status().is_success() { return Err(format!("Failed to download file: Status {}", response.status()).into()); } - let mut zip_file = File::create(as_name)?; + let mut zip_file = tokio::fs::File::create(zip_path).await?; let body = response.bytes().await?; - io::copy(&mut &*body, &mut zip_file)?; + zip_file.write_all(&body).await?; Ok(()) } @@ -345,28 +330,31 @@ fn extract_zip_contents_to_folder( #[allow(dead_code)] pub async fn download_and_extract_zip(url: &str, as_name: &str) { + log_message("Downloading ZIP file..."); let base_dir = PathBuf::from(get_directory()); - let zip_filename = format!("{}.zip", Uuid::new_v4()); // Use a unique name for the downloaded ZIP file + let zip_filename = format!("{}.zip", Uuid::new_v4()); let zip_path = base_dir.join(&zip_filename); let target_dir = base_dir.join(as_name); - // Step 1: Download the ZIP file if let Err(e) = download_zip(url, &zip_path).await { log_message(format!("Error downloading file: {}", e)); return; } - // Step 2: Extract and flatten the ZIP file contents into the target directory - if let Err(e) = extract_zip_contents_to_folder(&zip_path, &target_dir) { - log_message(format!("Error extracting ZIP file contents: {}", e)); + let zip_path_clone = zip_path.clone(); + let target_dir_clone = target_dir.clone(); + let extract_result = extract_zip_contents_to_folder(&zip_path_clone, &target_dir_clone); + if let Err(e) = extract_result { + log_message(format!("Panic during ZIP extraction: {}", e)); } - // Step 3: Clean up the downloaded ZIP file - if let Err(e) = fs::remove_file(&zip_path) { + if let Err(e) = tokio::fs::remove_file(&zip_path).await { log_message(format!( "Error cleaning up ZIP file {}: {}", zip_path.display(), e )); + } else { + log_message("Downloaded ZIP file"); } } diff --git a/web b/web deleted file mode 100644 index 51662e0..0000000 Binary files a/web and /dev/null differ