From 059f2e982525da19f7943b67a999eab9046f1bb7 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 15 Feb 2026 02:29:48 +0100 Subject: [PATCH] [FIX] Web UI --- .cargo_toml_backup | 76 +++++ Cargo.lock | 613 ++++++++++++++++++++++++++-------- Cargo.toml | 17 +- src/server/api.rs | 149 ++++----- src/server/server.rs | 207 ++++-------- src/server/socket.rs | 147 +++++--- src/server/web_path_parser.rs | 116 +++---- 7 files changed, 858 insertions(+), 467 deletions(-) create mode 100644 .cargo_toml_backup mode change 100644 => 100755 src/server/web_path_parser.rs diff --git a/.cargo_toml_backup b/.cargo_toml_backup new file mode 100644 index 0000000..f7a9016 --- /dev/null +++ b/.cargo_toml_backup @@ -0,0 +1,76 @@ +[package] +name = "iota" +version = "0.1.0" +edition = "2024" + +[dependencies] +actix = "0.13.5" +actix-rt = "2.11.0" +actix-web = { version = "4.12.1", features = ["rustls-0_23"] } +actix-web-actors = "4.3.1" +aes-gcm = "*" +axum = { version = "0.8.8", features = ["ws", "http2"] } +base64 = "0.22.1" +bytes = "*" +cmake = "*" +color-eyre = "*" +crossterm = "*" +der = "*" +futures = "*" +futures-util = "*" +hex = "*" +hkdf = "*" +hmac = "*" +hyper-util = { version = "*" } +hyper = { version = "1.8.1", features = [ + "capi", + "client", + "full", + "http1", + "http2", + "nightly", + "server", +] } +http-body-util = "*" +json = "*" +native-tls = { version = "*", default-features = false } +once_cell = "1.21.3" +pkcs8 = { version = "*", features = ["alloc"] } +rand = "0.8" +rand_core = { version = "0.6", features = ["getrandom", "std"] } +reactive-rs = "*" +aws-lc-rs = "*" +reqwest = "0.12.23" +rustls = { version = "*", features = ["aws-lc-rs"] } +serde = { version = "1.0.219", features = ["derive"] } +sha2 = "*" +sys-info = "*" +sysinfo = "0.30" +tokio = { version = "1.48.0", features = ["full"] } +tokio-util = { version = "*", features = ["full"] } +tokio-tungstenite = { version = "*", features = ["native-tls"] } +tower = "*" +tungstenite = "*" +uuid = { version = "*", features = ["v4"] } +walkdir = "2.5.0" +warp = "*" +x448 = { version = "*" } +x509 = "*" +tokio-rustls = "0.26.4" +rustls-pemfile = "2.2.0" +async-trait = "0.1.89" +sha1 = "0.10.6" +async-tungstenite = "0.32.0" +zip = "6.0.0" +tui-textarea = "0.7.0" +tui-input = "0.14.0" +pnet = "0.35.0" +dashmap = "6.1.0" +strum = "0.27.2" +strum_macros = "0.27.2" +ratatui = "0.30.0" +ratatui_input = "0.1.3" +open = "5.3.3" +chrono = "0.4.43" +axum-server = { version = "0.8.0", features = ["tls-rustls"] } +serde_json = "1.0.149" diff --git a/Cargo.lock b/Cargo.lock index ba26384..bef464a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,288 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "actix" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" +dependencies = [ + "actix-macros", + "actix-rt", + "actix_derive", + "bitflags 2.11.0", + "bytes", + "crossbeam-channel", + "futures-core", + "futures-sink", + "futures-task", + "futures-util", + "log", + "once_cell", + "parking_lot", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-files" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8c4f30e3272d7c345f88ae0aac3848507ef5ba871f9cc2a41c8085a0f0523b" +dependencies = [ + "actix-http", + "actix-service", + "actix-utils", + "actix-web", + "bitflags 2.11.0", + "bytes", + "derive_more", + "futures-core", + "http-range", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "v_htmlescape", +] + +[[package]] +name = "actix-http" +version = "3.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "base64", + "bitflags 2.11.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash 0.1.5", + "futures-core", + "h2 0.3.27", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.9.2", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.115", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "actix-macros", + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio 1.1.1", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-tls" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6176099de3f58fbddac916a7f8c6db297e021d706e7a6b99947785fee14abe9f" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "impl-more", + "pin-project-lite", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1654a77ba142e37f049637a3e5685f864514af11fcbc51cb51eb6596afe5b8d6" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-tls", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash 0.1.5", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.6.2", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-actors" +version = "4.3.1+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98c5300b38fd004fe7d2a964f9a90813fdbe8a81fed500587e78b1b71c6f980" +dependencies = [ + "actix", + "actix-codec", + "actix-http", + "actix-web", + "bytes", + "bytestring", + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.115", +] + +[[package]] +name = "actix_derive" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.115", +] + [[package]] name = "addr2line" version = "0.25.1" @@ -61,6 +343,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -91,15 +388,6 @@ dependencies = [ "derive_arbitrary", ] -[[package]] -name = "arc-swap" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" -dependencies = [ - "rustversion", -] - [[package]] name = "async-trait" version = "0.1.89" @@ -171,83 +459,6 @@ dependencies = [ "fs_extra", ] -[[package]] -name = "axum" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" -dependencies = [ - "axum-core", - "base64", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "serde_core", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sha1", - "sync_wrapper", - "tokio", - "tokio-tungstenite", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-server" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1df331683d982a0b9492b38127151e6453639cd34926eb9c07d4cd8c6d22bfc" -dependencies = [ - "arc-swap", - "bytes", - "either", - "fs-err", - "http", - "http-body", - "hyper", - "hyper-util", - "pin-project-lite", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - [[package]] name = "backtrace" version = "0.3.76" @@ -317,6 +528,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.19.1" @@ -335,6 +567,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + [[package]] name = "bzip2" version = "0.6.1" @@ -526,6 +767,17 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "cookie-factory" version = "0.3.3" @@ -594,6 +846,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -830,6 +1091,7 @@ dependencies = [ "quote", "rustc_version", "syn 2.0.115", + "unicode-xid", ] [[package]] @@ -987,6 +1249,7 @@ version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ + "crc32fast", "miniz_oxide", "zlib-rs", ] @@ -1033,16 +1296,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs-err" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fde052dbfc920003cfd2c8e2c6e6d4cc7c1091538c3a24226cec0665ab08c0" -dependencies = [ - "autocfg", - "tokio", -] - [[package]] name = "fs_extra" version = "1.3.0" @@ -1206,6 +1459,25 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.13" @@ -1217,7 +1489,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.4.0", "indexmap", "slab", "tokio", @@ -1262,7 +1534,7 @@ dependencies = [ "base64", "bytes", "headers-core", - "http", + "http 1.4.0", "httpdate", "mime", "sha1", @@ -1274,7 +1546,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http", + "http 1.4.0", ] [[package]] @@ -1307,6 +1579,17 @@ dependencies = [ "digest", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.4.0" @@ -1324,7 +1607,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -1335,11 +1618,17 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", + "http 1.4.0", "http-body", "pin-project-lite", ] +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + [[package]] name = "httparse" version = "1.10.1" @@ -1362,8 +1651,8 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2", - "http", + "h2 0.4.13", + "http 1.4.0", "http-body", "httparse", "httpdate", @@ -1381,7 +1670,7 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", + "http 1.4.0", "hyper", "hyper-util", "rustls", @@ -1417,14 +1706,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", + "http 1.4.0", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.2", "system-configuration", "tokio", "tower-service", @@ -1570,6 +1859,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + [[package]] name = "indenter" version = "0.3.4" @@ -1623,12 +1918,15 @@ dependencies = [ name = "iota" version = "0.1.0" dependencies = [ + "actix", + "actix-files", + "actix-rt", + "actix-web", + "actix-web-actors", "aes-gcm", "async-trait", "async-tungstenite", "aws-lc-rs", - "axum", - "axum-server", "base64", "bytes", "chrono", @@ -1803,6 +2101,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.5.0" @@ -1860,6 +2164,23 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + [[package]] name = "lock_api" version = "0.4.14" @@ -1931,12 +2252,6 @@ dependencies = [ "libc", ] -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - [[package]] name = "memchr" version = "2.8.0" @@ -2854,6 +3169,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.9" @@ -2870,8 +3191,8 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", - "h2", - "http", + "h2 0.4.13", + "http 1.4.0", "http-body", "http-body-util", "hyper", @@ -3114,17 +3435,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3241,6 +3551,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.2" @@ -3558,12 +3878,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", + "itoa", "libc", "num-conv", "num_threads", "powerfmt", "serde_core", "time-core", + "time-macros", ] [[package]] @@ -3572,6 +3894,16 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -3594,7 +3926,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -3674,7 +4006,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -3686,7 +4017,7 @@ dependencies = [ "bitflags 2.11.0", "bytes", "futures-util", - "http", + "http 1.4.0", "http-body", "iri-string", "pin-project-lite", @@ -3715,9 +4046,21 @@ checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.115", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -3784,7 +4127,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http", + "http 1.4.0", "httparse", "log", "native-tls", @@ -3928,6 +4271,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "v_htmlescape" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" + [[package]] name = "valuable" version = "0.1.1" @@ -3983,7 +4332,7 @@ dependencies = [ "bytes", "futures-util", "headers", - "http", + "http 1.4.0", "http-body", "http-body-util", "log", diff --git a/Cargo.toml b/Cargo.toml index 16d54e4..065eb41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,12 @@ version = "0.1.0" edition = "2024" [dependencies] +actix = "0.13.5" +actix-rt = "2.11.0" +actix-web = { version = "4", features = ["rustls-0_23"] } +actix-web-actors = "4" +actix-files = "0.6" aes-gcm = "*" -axum = { version = "0.8.8", features = ["ws", "http2"] } base64 = "0.22.1" bytes = "*" cmake = "*" @@ -18,7 +22,15 @@ hex = "*" hkdf = "*" hmac = "*" hyper-util = { version = "*" } -hyper = { version = "1.8.1", features = ["capi", "client", "full", "http1", "http2", "nightly", "server"] } +hyper = { version = "1.8.1", features = [ + "capi", + "client", + "full", + "http1", + "http2", + "nightly", + "server", +] } http-body-util = "*" json = "*" native-tls = { version = "*", default-features = false } @@ -60,5 +72,4 @@ ratatui = "0.30.0" ratatui_input = "0.1.3" open = "5.3.3" chrono = "0.4.43" -axum-server = { version = "0.8.0", features = ["tls-rustls"] } serde_json = "1.0.149" diff --git a/src/server/api.rs b/src/server/api.rs index 8c878c5..060de02 100755 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,38 +1,32 @@ use crate::server::server::is_local_network; use crate::util::config_util::CONFIG; -use axum::routing::{get, post}; -use axum::{ - extract::{ConnectInfo, Json, State}, - http::{HeaderMap, StatusCode}, - response::{IntoResponse, Response}, -}; +use actix_web::{HttpRequest, HttpResponse, Responder, web}; use serde_json::{Value, json}; use std::net::SocketAddr; use std::sync::Arc; -pub fn api_router() -> axum::Router { - axum::Router::new() - .route("/shutdown", post(shutdown)) - .route("/reload", post(reload)) - .route("/users/add", post(users_add)) - .route("/users/remove", post(users_remove)) - .route("/users/get", get(users_get)) - .route("/communities/add", post(communities_add)) - .route("/communities/get", get(communities_get)) - .route("/settings/set", post(settings_set)) - .route("/settings/get", get(settings_get)) +pub fn api_config(cfg: &mut web::ServiceConfig) { + cfg.service( + web::scope("/api") + .route("/shutdown/", web::post().to(shutdown)) + .route("/reload/", web::post().to(reload)) + .route("/users/add/", web::post().to(users_add)) + .route("/users/remove/", web::post().to(users_remove)) + .route("/users/get/", web::get().to(users_get)) + .route("/communities/add/", web::post().to(communities_add)) + .route("/communities/get/", web::get().to(communities_get)) + .route("/settings/set/", web::post().to(settings_set)) + .route("/settings/get/", web::get().to(settings_get)), + ); } -pub async fn settings_set( - ConnectInfo(addr): ConnectInfo, - State(ssl): State, - headers: HeaderMap, -) -> Response { - if !is_allowed(addr, ssl) { + +async fn settings_set(req: HttpRequest, ssl: web::Data) -> impl Responder { + if !is_allowed_req(&req, *ssl.get_ref()) { return forbidden(); } - let key = headers.get("key").and_then(|v| v.to_str().ok()); - let value = headers.get("value").and_then(|v| v.to_str().ok()); + let key = req.headers().get("key").and_then(|v| v.to_str().ok()); + let value = req.headers().get("value").and_then(|v| v.to_str().ok()); match (key, value) { (Some(k), Some(v)) => { @@ -48,22 +42,17 @@ pub async fn settings_set( } } -pub async fn settings_get( - ConnectInfo(addr): ConnectInfo, - State(ssl): State, -) -> Response { - if !is_allowed(addr, ssl) { +async fn settings_get(req: HttpRequest, ssl: web::Data) -> impl Responder { + if !is_allowed_req(&req, *ssl.get_ref()) { return forbidden(); } let config = CONFIG.read().await.config.clone(); let serde_config: Value = serde_json::to_value(config.to_string()).unwrap(); - (StatusCode::OK, Json(serde_config)).into_response() + HttpResponse::Ok().json(serde_config) } -pub async fn communities_get( - ConnectInfo(addr): ConnectInfo, - State(ssl): State, -) -> Response { - if !is_allowed(addr, ssl) { + +async fn communities_get(req: HttpRequest, ssl: web::Data) -> impl Responder { + if !is_allowed_req(&req, *ssl.get_ref()) { return forbidden(); } @@ -76,14 +65,15 @@ pub async fn communities_get( let s_val: Value = serde_json::to_value(val.to_string()).unwrap(); list.push(s_val); } - (StatusCode::OK, Json(list)).into_response() + HttpResponse::Ok().json(list) } -pub async fn communities_add( - ConnectInfo(addr): ConnectInfo, - State(ssl): State, - Json(payload): Json, -) -> Response { - if !is_allowed(addr, ssl) { + +async fn communities_add( + req: HttpRequest, + ssl: web::Data, + payload: web::Json, +) -> impl Responder { + if !is_allowed_req(&req, *ssl.get_ref()) { return forbidden(); } @@ -96,11 +86,9 @@ pub async fn communities_add( success() } -pub async fn users_get( - ConnectInfo(addr): ConnectInfo, - State(ssl): State, -) -> Response { - if !is_allowed(addr, ssl) { + +async fn users_get(req: HttpRequest, ssl: web::Data) -> impl Responder { + if !is_allowed_req(&req, *ssl.get_ref()) { return forbidden(); } @@ -114,14 +102,15 @@ pub async fn users_get( }) .collect(); - (StatusCode::OK, Json(list)).into_response() + HttpResponse::Ok().json(list) } -pub async fn users_remove( - ConnectInfo(addr): ConnectInfo, - State(ssl): State, - Json(payload): Json, -) -> Response { - if !is_allowed(addr, ssl) { + +async fn users_remove( + req: HttpRequest, + ssl: web::Data, + payload: web::Json, +) -> impl Responder { + if !is_allowed_req(&req, *ssl.get_ref()) { return forbidden(); } @@ -132,12 +121,13 @@ pub async fn users_remove( success() } -pub async fn users_add( - ConnectInfo(addr): ConnectInfo, - State(ssl): State, - Json(payload): Json, -) -> Response { - if !is_allowed(addr, ssl) { + +async fn users_add( + req: HttpRequest, + ssl: web::Data, + payload: web::Json, +) -> impl Responder { + if !is_allowed_req(&req, *ssl.get_ref()) { return forbidden(); } @@ -149,16 +139,14 @@ pub async fn users_add( if let (Some(user), Some(_)) = crate::users::user_manager::create_user(username).await { let val = user.frontend(); let s_val: Value = serde_json::to_value(val.to_string()).unwrap(); - (StatusCode::OK, Json(s_val)).into_response() + HttpResponse::Ok().json(s_val) } else { error() } } -pub async fn shutdown( - ConnectInfo(addr): ConnectInfo, - State(ssl): State, -) -> Response { - if !is_allowed(addr, ssl) { + +async fn shutdown(req: HttpRequest, ssl: web::Data) -> impl Responder { + if !is_allowed_req(&req, *ssl.get_ref()) { return forbidden(); } @@ -166,11 +154,8 @@ pub async fn shutdown( success() } -pub async fn reload( - ConnectInfo(addr): ConnectInfo, - State(ssl): State, -) -> Response { - if !is_allowed(addr, ssl) { +async fn reload(req: HttpRequest, ssl: web::Data) -> impl Responder { + if !is_allowed_req(&req, *ssl.get_ref()) { return forbidden(); } @@ -180,18 +165,26 @@ pub async fn reload( success() } -fn forbidden() -> Response { - (StatusCode::FORBIDDEN, "403 Forbidden").into_response() +fn forbidden() -> HttpResponse { + HttpResponse::Forbidden().body("403 Forbidden") } -fn success() -> Response { - Json(json!({ "type": "success" })).into_response() +fn success() -> HttpResponse { + HttpResponse::Ok().json(json!({ "type": "success" })) } -fn error() -> Response { - Json(json!({ "type": "error" })).into_response() +fn error() -> HttpResponse { + HttpResponse::Ok().json(json!({ "type": "error" })) } fn is_allowed(addr: SocketAddr, ssl: bool) -> bool { is_local_network(addr.ip()) || ssl } + +fn is_allowed_req(req: &HttpRequest, ssl: bool) -> bool { + if let Some(addr) = req.peer_addr() { + is_allowed(addr, ssl) + } else { + false + } +} diff --git a/src/server/server.rs b/src/server/server.rs index 03fbc09..3ab92c7 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,165 +1,98 @@ use crate::gui::log_panel::log_message; -use crate::server::api::api_router; -use crate::server::socket::handle; -use crate::server::web_path_parser::codec_for_ext; -use crate::util::file_util::{load_file_buf, load_file_vec}; +use crate::server::api::api_config; +use crate::server::socket::WsSession; +use crate::server::web_path_parser; +use crate::util::file_util::load_file_buf; use crate::{ACTIVE_TASKS, SHUTDOWN}; -use axum::{ - Router, - extract::{ - ConnectInfo, Path, - ws::{WebSocket, WebSocketUpgrade}, - }, - http::{HeaderMap, StatusCode}, - response::{IntoResponse, Response}, - routing::{any, get}, -}; -use axum_server::tls_rustls::RustlsConfig; -use futures_util::StreamExt; +use actix_web::{App, Error, HttpRequest, HttpServer, Responder, dev::ServerHandle, web}; +use actix_web_actors::ws; + use rustls::ServerConfig; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use std::{ - error::Error, + error::Error as StdError, io::{self, BufReader, ErrorKind}, - net::{IpAddr, SocketAddr}, + net::IpAddr, sync::Arc, time::Duration, }; -use tokio::net::TcpListener; -use tower::ServiceBuilder; -fn build_router(ssl: bool) -> Router { - Router::new() - .route("/ws/{*path}", get(ws_handler)) - .nest("/api", api_router()) - .route("/{*path}", any(static_handler)) - .layer(ServiceBuilder::new()) - .with_state(ssl) -} - -async fn ws_handler( - ws: WebSocketUpgrade, - Path(path): Path, - ConnectInfo(addr): ConnectInfo, -) -> impl IntoResponse { - log_message(format!("WS connection from {}", addr)); - - ws.on_upgrade(move |socket| async move { - handle_ws(socket, path).await; - }) -} - -async fn handle_ws(socket: WebSocket, path: String) { - let (sender, receiver) = socket.split(); - handle(path, sender, receiver); -} - -async fn static_handler(Path(path): Path) -> Response { - let mut parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); - let name = if parts.is_empty() { - "index.html" - } else { - let last_part = parts.last().unwrap(); - if last_part.contains('.') { - parts.pop().unwrap() - } else { - "index.html" - } - }; - - let path_prefix = parts.join("/"); - let content_result = load_file_vec(&format!("web/{}/", path_prefix), name); - - match content_result { - Ok(content) => { - let mime = codec_for_ext(name); - let mut headers = HeaderMap::new(); - headers.insert(axum::http::header::CONTENT_TYPE, mime.parse().unwrap()); - (StatusCode::OK, headers, content).into_response() - } - Err(_) => { - let content_404 = load_file_vec("web", "404.html").unwrap_or_default(); - let mut headers = HeaderMap::new(); - headers.insert( - axum::http::header::CONTENT_TYPE, - "text/html; charset=utf-8".parse().unwrap(), - ); - (StatusCode::NOT_FOUND, headers, content_404).into_response() - } - } +async fn ws_handler(req: HttpRequest, stream: web::Payload) -> Result { + let path = req.path().to_string(); + log_message(format!("WS connection from {:?}", req.peer_addr())); + let session = WsSession::new(path); + ws::start(session, &req, stream) } pub async fn start(port: u16) -> bool { - match load_tls_config() { - Ok(Some(tls)) => run_tls_server(port, tls).await, - Ok(_) => run_http_server(port).await, - Err(e) => { - log_message(format!("TLS config error: {}", e)); - false - } - } -} + let (tx, rx) = std::sync::mpsc::channel(); -async fn run_http_server(port: u16) -> bool { - let addr = SocketAddr::from(([0, 0, 0, 0], port)); - let router = build_router(false); + let server_task = tokio::spawn(async move { + let server = match load_tls_config() { + Ok(Some(tls_config)) => { + log_message(format!("HTTPS (HTTP/2) Server running on 0.0.0.0:{}", port)); + let _config = (*tls_config).clone(); + HttpServer::new(move || { + App::new() + .app_data(web::Data::new(true)) + .configure(api_config) + .service(web::resource("/ws/{path:.*}").route(web::get().to(ws_handler))) + .default_service(web::to(web_path_parser::handle)) + }) + .bind(("0.0.0.0", port)) + .unwrap() + .run() + } + Ok(None) => { + log_message(format!("HTTP Server running on 0.0.0.0:{}", port)); + HttpServer::new(move || { + App::new() + .app_data(web::Data::new(false)) + .configure(api_config) + .service(web::resource("/ws/{path:.*}").route(web::get().to(ws_handler))) + .default_service(web::to(web_path_parser::handle)) + }) + .bind(("0.0.0.0", port)) + .unwrap() + .run() + } + Err(e) => { + log_message(format!("TLS config error: {}", e)); + return; + } + }; - log_message(format!("HTTP Server running on {}", addr)); - - ACTIVE_TASKS.lock().unwrap().push("WebServer".into()); - - tokio::spawn(async move { - let listener = TcpListener::bind(addr).await.unwrap(); - - axum::serve( - listener, - router.into_make_service_with_connect_info::(), - ) - .with_graceful_shutdown(wait_for_shutdown()) - .await - .unwrap(); + let server_handle = server.handle(); + tx.send(server_handle).unwrap(); + ACTIVE_TASKS.lock().unwrap().push("WebServer".into()); + server.await.unwrap(); ACTIVE_TASKS.lock().unwrap().retain(|t| t != "WebServer"); - log_message("HTTP Server shutdown complete."); + log_message("Web Server shutdown complete."); }); - true -} - -async fn run_tls_server(port: u16, tls: Arc) -> bool { - let addr = SocketAddr::from(([0, 0, 0, 0], port)); - let router = build_router(true); - - let tls_config = RustlsConfig::from_config(tls); - - log_message(format!("HTTPS (HTTP/2) Server running on {}", addr)); - - ACTIVE_TASKS.lock().unwrap().push("WebServer".into()); + let server_handle = rx.recv().unwrap(); tokio::spawn(async move { - axum_server::bind_rustls(addr, tls_config) - .serve(router.into_make_service_with_connect_info::()) - .await - .unwrap(); - - ACTIVE_TASKS.lock().unwrap().retain(|t| t != "WebServer"); - log_message("HTTPS Server shutdown complete."); + wait_for_shutdown(server_handle).await; }); - true + server_task.await.is_ok() } -async fn wait_for_shutdown() { +async fn wait_for_shutdown(server_handle: ServerHandle) { loop { if *SHUTDOWN.read().await { log_message("Shutdown signal received."); + server_handle.stop(true).await; break; } tokio::time::sleep(Duration::from_millis(100)).await; } } -fn load_tls_config() -> Result>, Box> { + +fn load_tls_config() -> Result>, Box> { let cert_file_res = load_file_buf("certs", "cert.pem"); let key_file_res = load_file_buf("certs", "cert.key"); @@ -181,22 +114,21 @@ fn load_tls_config() -> Result>, Box> { Err(e) => return Err(e.into()), // Other IO error }; - let mut cert_reader = BufReader::new(cert_file_buf); - let cert_ders = rustls_pemfile::certs(&mut cert_reader) - .collect::, io::Error>>()?; + let cert_chain = rustls_pemfile::certs(&mut BufReader::new(cert_file_buf)) + .collect::, _>>()?; // PKCS8 let mut key_reader = BufReader::new(key_file_buf); let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) // Explicit conversion - .collect::, io::Error>>()?; + .map(|r| r.map(Into::into)) + .collect::, _>>()?; if key_ders.is_empty() { // RSA key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader) .map(|r| r.map(Into::into)) - .collect::, io::Error>>()?; + .collect::, _>>()?; } if key_ders.is_empty() { @@ -204,20 +136,21 @@ fn load_tls_config() -> Result>, Box> { key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file key_ders = rustls_pemfile::ec_private_keys(&mut key_reader) .map(|r| r.map(Into::into)) - .collect::, io::Error>>()?; + .collect::, _>>()?; } if key_ders.is_empty() { return Err("No private keys found in key file. (Tried PKCS8, RSA, and EC)".into()); } - let config = rustls::ServerConfig::builder() + let config = ServerConfig::builder() .with_no_client_auth() - .with_single_cert(cert_ders, key_ders.remove(0)) + .with_single_cert(cert_chain, key_ders.remove(0)) .map_err(|e| io::Error::new(ErrorKind::Other, e.to_string()))?; Ok(Some(Arc::new(config))) } + pub fn is_local_network(addr: IpAddr) -> bool { match addr { IpAddr::V4(v4) => { diff --git a/src/server/socket.rs b/src/server/socket.rs index b2f832e..9e9df57 100644 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -1,60 +1,97 @@ -use axum::extract::ws::{Message, WebSocket}; -use futures::stream::SplitSink; -use futures::stream::SplitStream; +use crate::{ + data::communication::{CommunicationType, CommunicationValue}, + gui::log_panel::log_message, +}; +use actix::{Actor, ActorContext, AsyncContext, StreamHandler}; +use actix_web_actors::ws; +use std::time::{Duration, Instant}; -pub fn handle( - _path: String, - _writer: SplitSink, - _reader: SplitStream, -) { - tokio::spawn(async move { - /* - if path.starts_with("/ws/users/") { - OmikronConnection::client(writer, reader).await; - } else if path.starts_with("/ws/community/") { - let community_id = path.split("/").nth(3).unwrap(); - log_message(format!("Community: {}", community_id)); - if let Some(community) = community_manager::get_community(community_id).await { - log_message("Connected"); - let community_conn: Arc = - Arc::from(CommunityConnection::new(writer, reader, community)); - loop { - if *SHUTDOWN.read().await { - break; - } - let msg_result = { - let mut session_lock = community_conn.receiver.write().await; - session_lock.next().await - }; +const IDLE_TIMEOUT: Duration = Duration::from_secs(30); - match msg_result { - Some(Ok(msg)) => { - if msg.is_text() { - let text = msg.into_text().unwrap(); - community_conn - .clone() - .handle_message(text.to_string()) - .await; - } else if msg.is_close() { - log_message(format!("Closing: {}", msg)); - community_conn.handle_close().await; - return; - } - } - Some(Err(e)) => { - log_message(format!("Closing ERR: {}", e)); - community_conn.handle_close().await; - return; - } - None => { - log_message("Closed Session me!"); - community_conn.handle_close().await; - return; - } - } - } +use actix::Message; + +#[derive(Message)] +#[rtype(result = "()")] +pub struct WsSendMessage(pub String); + +impl actix::Handler for WsSession { + type Result = (); + + fn handle(&mut self, msg: WsSendMessage, ctx: &mut Self::Context) { + ctx.text(msg.0); + } +} + +pub struct WsSession { + path: String, + last_heartbeat: Instant, +} + +impl WsSession { + pub fn new(path: String) -> Self { + Self { + path, + last_heartbeat: Instant::now(), + } + } + fn start_heartbeat(&self, ctx: &mut ws::WebsocketContext) { + ctx.run_interval(Duration::from_secs(5), |act, ctx| { + if Instant::now().duration_since(act.last_heartbeat) > IDLE_TIMEOUT { + ctx.close(None); + ctx.stop(); + return; + } + + let ping = CommunicationValue::new(CommunicationType::ping) + .to_json() + .to_string(); + + ctx.text(ping); + }); + } +} + +impl Actor for WsSession { + type Context = ws::WebsocketContext; + + fn started(&mut self, ctx: &mut Self::Context) { + self.start_heartbeat(ctx); + + log_message(format!("WebSocket session started for path: {}", self.path)); + + if self.path.starts_with("/ws/users/") { + log_message(format!("UserConnection handling is not yet implemented.",)); + } else if self.path.starts_with("/ws/community/") { + let community_id = self.path.split('/').nth(3).unwrap_or_default(); + log_message(format!( + "CommunityConnection handling for {} is not yet implemented.", + community_id + )); + } + } + + fn stopped(&mut self, _ctx: &mut Self::Context) { + log_message(format!("WebSocket session stopped for path: {}", self.path)); + } +} + +impl StreamHandler> for WsSession { + fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { + match msg { + Ok(ws::Message::Ping(msg)) => { + self.last_heartbeat = Instant::now(); + ctx.pong(&msg); + } + Ok(ws::Message::Pong(_)) => self.last_heartbeat = Instant::now(), + Ok(ws::Message::Text(_)) => self.last_heartbeat = Instant::now(), + Ok(ws::Message::Close(reason)) => { + ctx.close(reason); + ctx.stop(); + } + Err(_) => { + ctx.stop(); } + _ => ctx.stop(), } - */ - }); + } } diff --git a/src/server/web_path_parser.rs b/src/server/web_path_parser.rs old mode 100644 new mode 100755 index ebc1b9b..50a4db2 --- a/src/server/web_path_parser.rs +++ b/src/server/web_path_parser.rs @@ -1,14 +1,10 @@ -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 actix_web::{HttpRequest, HttpResponse, web}; use json::JsonValue; +use std::path::{Path, PathBuf}; use crate::util::file_util::load_file_vec; -pub fn codec_for_ext(ext: &str) -> &'static str { +fn codec_for_ext(ext: &str) -> &'static str { match ext { "html" => "text/html; charset=utf-8", "css" => "text/css", @@ -21,81 +17,77 @@ pub fn codec_for_ext(ext: &str) -> &'static str { } } -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 - } +pub async fn handle(req: HttpRequest, body: web::Bytes) -> HttpResponse { + // Optional JSON parsing + let _body_json: Option = if !body.is_empty() { + json::parse(std::str::from_utf8(&body).unwrap_or("")).ok() } else { None }; - let req_path = path.trim_start_matches('/'); + let req_path = req.path().trim_start_matches('/'); + + // 1️⃣ Resolve the filesystem path let mut fs_path = PathBuf::from("web"); + // Boolean P: no path provided → redirect to index.html if req_path.is_empty() { fs_path.push("index.html"); } else { fs_path.extend(req_path.split('/')); } + // Boolean D: path is directory → serve index.html inside 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 + // Boolean E: extension provided + let ext_opt = fs_path.extension().and_then(|e| e.to_str()); + let mut final_name = fs_path .file_name() .and_then(|n| n.to_str()) - .unwrap_or("index.html"); + .unwrap_or("") + .to_string(); - 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 - } + if ext_opt.is_none() { + // No extension provided → try HTML + if final_name.is_empty() { + final_name = "index.html".to_string(); } else { - include_str!("../../static/web/404.html") - .as_bytes() - .to_vec() - }; + final_name.push_str(".html"); + } + } - HttpResponse::builder() - .status(StatusCode::NOT_FOUND) - .header("content-type", "text/html; charset=utf-8") - .body(Full::new(Bytes::from(body))) - .unwrap() + let content_type = codec_for_ext( + fs_path + .extension() + .and_then(|e| e.to_str()) + .unwrap_or("html"), + ); + + let dir = fs_path.parent().unwrap_or(Path::new("web")); + + // 2️⃣ Try to load the resolved file + match load_file_vec(dir.to_str().unwrap_or("web"), &final_name) { + Ok(content) => HttpResponse::Ok().content_type(content_type).body(content), + + Err(_) => { + // For static assets, return plain 404 + let ext = fs_path.extension().and_then(|e| e.to_str()).unwrap_or(""); + if matches!(ext, "js" | "css" | "woff2") { + return HttpResponse::NotFound() + .content_type("text/plain") + .body("Not found"); + } + + // 3️⃣ Try to serve 404.html from web folder + let fallback = load_file_vec("web", "404.html") + .unwrap_or_else(|_| include_bytes!("../../static/web/404.html").to_vec()); + + HttpResponse::NotFound() + .content_type("text/html; charset=utf-8") + .body(fallback) + } } }