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:
+10
-3
@@ -147,10 +147,17 @@ fn draw_input(f: &mut Frame, area: ratatui::layout::Rect, app: &App, theme: &The
|
||||
]))
|
||||
.block(
|
||||
Block::bordered()
|
||||
.border_style(Style::default().fg(theme.border))
|
||||
.border_style(Style::default().fg(if app.pending_offer.is_some() {
|
||||
theme.accent
|
||||
} else {
|
||||
theme.border
|
||||
}))
|
||||
.title(Span::styled(
|
||||
" message · enter send · esc quit ",
|
||||
Style::default().fg(theme.dim),
|
||||
match &app.pending_offer {
|
||||
Some(o) => format!(" ⛧ incoming: {} — /accept or /reject ", o.name),
|
||||
None => " message · enter send · esc quit ".to_string(),
|
||||
},
|
||||
Style::default().fg(theme.title),
|
||||
)),
|
||||
);
|
||||
f.render_widget(input, area);
|
||||
|
||||
Reference in New Issue
Block a user