feat(hh): P5 — file & directory uploads ⛧

Wire-compatible with the Python client's _ft protocol (offer/accept/reject/
chunk/done, 64KB chunks, SHA-256 verified), over the encrypted channel:
- ft.rs: read_payload (file or tar'd directory), save/extract with a zip-slip
  guard (relative-only, no .. / absolute; unpack_in double-checks), SHA-256.
- /send <file> and /sendd <dir>; receiver sees an offer banner, /accept or
  /reject; chunks stream in the background and the result is verified + saved
  to ./downloads (directories extracted in place).
- Refactor: all outgoing ws frames now funnel through an mpsc channel drained
  (batched) by the run loop, so the background chunk-sender and the PTY relay
  transmit without owning the socket.
- ui.rs: pending-offer banner on the input bar.

Tests: 7 cargo tests (file + dir tar round-trip, traversal guard, + crypto/sbx).
Verified live: two TUIs, file and directory transfer, SHA-256 verified, saved.

Note: dropping accepted files into the active sandbox VM dir is a future add-on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-05-30 19:39:55 -07:00
parent f73c23bf57
commit 1dfc614cc5
10 changed files with 553 additions and 155 deletions
Generated
+53 -2
View File
@@ -281,7 +281,7 @@ dependencies = [
"futures-core",
"mio",
"parking_lot",
"rustix",
"rustix 0.38.44",
"signal-hook",
"signal-hook-mio",
"winapi",
@@ -426,6 +426,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "filetime"
version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
dependencies = [
"cfg-if",
"libc",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
@@ -588,6 +598,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"tar",
"tokio",
"tokio-tungstenite",
"toml",
@@ -958,6 +969,12 @@ version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "litemap"
version = "0.8.2"
@@ -1442,10 +1459,23 @@ dependencies = [
"bitflags 2.11.1",
"errno",
"libc",
"linux-raw-sys",
"linux-raw-sys 0.4.15",
"windows-sys 0.52.0",
]
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags 2.11.1",
"errno",
"libc",
"linux-raw-sys 0.12.1",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.40"
@@ -1782,6 +1812,17 @@ dependencies = [
"syn",
]
[[package]]
name = "tar"
version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]]
name = "termios"
version = "0.2.2"
@@ -2493,6 +2534,16 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "xattr"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix 1.1.4",
]
[[package]]
name = "yoke"
version = "0.8.2"